From c12f035c3b72ce39befb42d455683e9c06e155b5 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Wed, 12 Feb 2020 11:07:19 -0700 Subject: [PATCH 001/224] Squashed commit of the following: commit 089d810f17ec9349d6064cb95d610845a6f4d1f1 Author: Sander Claeys Date: Wed Jan 22 20:22:01 2020 -0700 parser working commit 63676a6264d1bc44e8c5f4db407bd4146965da3d Author: Sander Claeys Date: Fri Jan 17 12:25:00 2020 -0700 parser rewrite init commit 0e7358f1856c211a1a22dee6239d608c109a144f Author: Sander Claeys Date: Fri Jan 17 10:32:18 2020 -0700 pu prototyped commit efa631d95b8b4b0e43fa23cbb651556d795d697b Author: Sander Claeys Date: Thu Jan 16 20:52:24 2020 -0700 _calc_vbase moved and improved commit d5e668b76f82b63257e47b3e0ba071ff5c981977 Author: Sander Claeys Date: Thu Jan 16 19:47:32 2020 -0700 moved transformer decomposition commit 786570d29ce75667f6696e10d11bd904f6f45301 Author: Sander Claeys Date: Thu Jan 16 15:06:01 2020 -0700 mappings prototype commit 110de04744cb8a0a5e60dc309cbab20925199f9d Author: Sander Claeys Date: Wed Jan 15 12:52:43 2020 -0700 update commit d1b889ad39ac71e60277291ae35fa552002206b2 Author: Sander Claeys Date: Tue Jan 14 09:26:26 2020 -0700 user-facing data model prototype --- src/PowerModelsDistribution.jl | 8 + src/io/common_dm.jl | 30 + src/io/data_model_components.jl | 574 ++++++++++++++ src/io/data_model_mapping.jl | 341 ++++++++ src/io/data_model_pu.jl | 278 +++++++ src/io/data_model_test.jl | 88 +++ src/io/data_model_util.jl | 76 ++ src/io/dss_structs.jl | 4 +- src/io/opendss_dm.jl | 1282 +++++++++++++++++++++++++++++++ 9 files changed, 2680 insertions(+), 1 deletion(-) create mode 100644 src/io/common_dm.jl create mode 100644 src/io/data_model_components.jl create mode 100644 src/io/data_model_mapping.jl create mode 100644 src/io/data_model_pu.jl create mode 100644 src/io/data_model_test.jl create mode 100644 src/io/data_model_util.jl create mode 100644 src/io/opendss_dm.jl diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 80ee7ff68..b3336aa30 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -45,6 +45,14 @@ module PowerModelsDistribution include("io/dss_structs.jl") include("io/opendss.jl") + include("io/common_dm.jl") + include("io/opendss_dm.jl") + include("io/data_model_components.jl") + include("io/data_model_mapping.jl") + include("io/data_model_pu.jl") + include("io/data_model_test.jl") + include("io/data_model_util.jl") + include("prob/mld.jl") include("prob/opf.jl") include("prob/opf_iv.jl") diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl new file mode 100644 index 000000000..0405c5ba3 --- /dev/null +++ b/src/io/common_dm.jl @@ -0,0 +1,30 @@ +""" + parse_file(io) + +Parses the IOStream of a file into a Three-Phase PowerModels data structure. +""" +function parse_file_dm(io::IO; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, filetype::AbstractString="json", bank_transformers::Bool=true) + if filetype == "m" + pmd_data = PowerModelsDistribution.parse_matlab(io) + elseif filetype == "dss" + Memento.warn(_LOGGER, "Not all OpenDSS features are supported, currently only minimal support for lines, loads, generators, and capacitors as shunts. Transformers and reactors as transformer branches are included, but value translation is not fully supported.") + pmd_data = PowerModelsDistribution.parse_opendss_dm(io; import_all=import_all, vmin=vmin, vmax=vmax, bank_transformers=bank_transformers) + elseif filetype == "json" + pmd_data = PowerModels.parse_json(io; validate=false) + else + Memento.error(_LOGGER, "only .m and .dss files are supported") + end + + #correct_network_data!(pmd_data) + + return pmd_data +end + + +"" +function parse_file_dm(file::String; kwargs...) + pmd_data = open(file) do io + parse_file_dm(io; filetype=split(lowercase(file), '.')[end], kwargs...) + end + return pmd_data +end diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl new file mode 100644 index 000000000..727e55fec --- /dev/null +++ b/src/io/data_model_components.jl @@ -0,0 +1,574 @@ +#TODO +# Can buses in a voltage zone have different terminals? +# Add current/power bounds to data model + + +function copy_kwargs_to_dict_if_present!(dict, kwargs, args) + for arg in args + if haskey(kwargs, arg) + dict[string(arg)] = kwargs[arg] + end + end +end + +function add_kwarg!(dict, kwargs, name, default) + if haskey(kwargs, name) + dict[string(name)] = kwargs[name] + else + dict[string(name)] = default + end +end + +function component_dict_from_list!(list) + dict = Dict{String, Any}() + for comp_dict in list + dict[comp_dict["id"]] = comp_dict + end + return dict +end + +DTYPES = Dict{Symbol, Any}() +CHECKS = Dict{Symbol, Any}() + +function check_dtypes(dict, dtypes, comp_type, id) + for key in keys(dict) + symb = Symbol(key) + if haskey(dtypes, symb) + @assert(isa(dict[key], dtypes[symb]), "$comp_type $id: the property $key should be a $(dtypes[symb]), not a $(typeof(dict[key])).") + else + @assert(false, "$comp_type $id: the property $key is unknown.") + end + end +end + +function check_data_model(data) + for component in [:bus, :linecode, :line, :load, :generator, :transformer_nph3w_lossy, :transformer_2w_ideal, :capacitor, :shunt] + if haskey(data, string(component)) + for (id, comp_dict) in data[string(component)] + if haskey(DTYPES, component) + check_dtypes(comp_dict, DTYPES[component], component, id) + end + if haskey(CHECKS, component) + CHECKS[component](data, comp_dict) + end + end + end + end +end + + +function create_data_model(;kwargs...) + data_model = Dict{String, Any}() + data_model["quantity_scalars"] = Dict{String, Any}() + add_kwarg!(data_model, kwargs, :v_var_scalar, 1E3) + return data_model +end + +# COMPONENTS +#*################# + +DTYPES[:ev] = Dict() +DTYPES[:storage] = Dict() +DTYPES[:pv] = Dict() +DTYPES[:wind] = Dict() +DTYPES[:switch] = Dict() +DTYPES[:shunt] = Dict() +DTYPES[:autotransformer] = Dict() +DTYPES[:synchronous_generator] = Dict() +DTYPES[:zip_load] = Dict() +DTYPES[:grounding] = Dict( + :bus => String, + :rg => Real, + :xg => Real, +) +DTYPES[:synchronous_generator] = Dict() +DTYPES[:boundary] = Dict() +DTYPES[:meter] = Dict() + + + +# Linecode + +DTYPES[:linecode] = Dict( + :id => AbstractString, + :n_conductors => Int, + :rs => Array{<:Real, 2}, + :xs => Array{<:Real, 2}, + :g_fr => Array{<:Real, 2}, + :g_to => Array{<:Real, 2}, + :b_fr => Array{<:Real, 2}, + :b_to => Array{<:Real, 2}, +) + + +CHECKS[:linecode] = function check_line(data, linecode) +end + + +function create_linecode(id, n_conductors; kwargs...) + linecode = Dict{String,Any}() + linecode["id"] = id + linecode["n_conductors"] = n_conductors + add_kwarg!(linecode, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) + return linecode +end + + +# line + +DTYPES[:line] = Dict( + :id => AbstractString, + :status => Int, + :f_bus => String, + :t_bus => String, + :n_conductors => Int, + :f_terminals => Vector{<:Int}, + :t_terminals => Vector{<:Int}, + :linecode => Int, + :length => Real, + :rs =>Matrix{<:Real}, + :xs =>Matrix{<:Real}, + :g_fr => Matrix{<:Real}, + :b_fr =>Matrix{<:Real}, + :g_to =>Matrix{<:Real}, + :b_to =>Matrix{<:Real}, + :b_to =>Matrix{<:Real}, + :c_rating =>Vector{<:Real}, +) + + +CHECKS[:line] = function check_line(data, line) + i = line["id"] + + if haskey(line, "linecode") + # line is defined with a linecode + @assert(haskey(line, "length"), "line $i: a line defined through a linecode, should have a length property.") + + linecode_id = line["linecode"] + @assert(haskey(data, "linecode") && haskey(data["linecode"], "$linecode_id"), "line $i: the linecode $linecode_id is not defined.") + linecode = data["linecode"]["$linecode_id"] + + for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + @assert(!haskey(line, key), "line $i: a line with a linecode, should not specify $key; this is already done by the linecode.") + end + + N = linecode["n_conductors"] + @assert(length(line["f_terminals"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") + @assert(length(line["t_terminals"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") + else + # normal line + @assert(!haskey(line, "length"), "line $i: length only makes sense for linees defined through linecodes.") + for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + @assert(haskey(line, key), "line $i: a line without linecode, should specify $key.") + end + end +end + + +function create_line(id, f_bus, t_bus, n_conductors; kwargs...) + line = Dict{String,Any}() + line["id"] = id + line["f_bus"] = f_bus + line["t_bus"] = t_bus + line["n_conductors"] = n_conductors + add_kwarg!(line, kwargs, :f_terminals, collect(1:n_conductors)) + add_kwarg!(line, kwargs, :t_terminals, collect(1:n_conductors)) + add_kwarg!(line, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) + return line +end + + +function create_line_with_linecode(id, f_bus, t_bus, linecode, length; kwargs...) + line = Dict{String,Any}() + line["id"] = id + line["f_bus"] = f_bus + line["t_bus"] = t_bus + line["linecode"] = linecode["id"] + line["length"] = length + n_conductors = linecode["n_conductors"] + add_kwarg!(line, kwargs, :f_terminals, collect(1:n_conductors)) + add_kwarg!(line, kwargs, :t_terminals, collect(1:n_conductors)) + return line +end + + +# Voltage zone + +DTYPES[:voltage_zone] = Dict( + :id => AbstractString, + :buses => Array{String, 1}, + :vnom => Real, + :vm_ln_min => Array{<:Real, 1}, + :vm_ln_max => Array{<:Real, 1}, + :vm_lg_min => Array{<:Real, 1}, + :vm_lg_max => Array{<:Real, 1}, + :vm_ng_min => Array{<:Real, 1}, + :vm_ng_max => Array{<:Real, 1}, + :vm_ll_min => Array{<:Real, 1}, + :vm_ll_max => Array{<:Real, 1}, +) + + +CHECKS[:voltage_zone] = function check_voltage_zone(data, bus) + i = bus["id"] + @assert(haskey(bus, "vnom"), "Voltage zone $i: a voltage zone should specify a vnom.") +end + + +function create_voltage_zone(id, vnom; kwargs...) + voltage_zone = Dict{String, Any}( + "id" => id, + "vnom" => vnom, + "buses" => Array{String, 1}(), + ) +end + +# Bus + +DTYPES[:bus] = Dict( + :id => AbstractString, + :bus => String, + :bus_type => Int, + :terminals => Array{<:Int}, + :phases => Array{<:Int}, + :neutral => Union{Int, Missing}, + :grounded => Bool, + :voltage_zone => String, + :rg => Real, + :xg => Real, + :vnom => Real, + :vm_ln_min => Array{<:Real, 1}, + :vm_ln_max => Array{<:Real, 1}, + :vm_lg_min => Array{<:Real, 1}, + :vm_lg_max => Array{<:Real, 1}, + :vm_ng_min => Array{<:Real, 1}, + :vm_ng_max => Array{<:Real, 1}, + :vm_ll_min => Array{<:Real, 1}, + :vm_ll_max => Array{<:Real, 1}, + :vm_fix => Array{<:Real, 1}, + :va_fix => Array{<:Real, 1}, +) + + +CHECKS[:bus] = function check_bus(data, bus) + i = bus["id"] + if !haskey(bus, "neutral") + @assert(!haskey(bus, "vm_ln_max"), "Bus $i does not have a neutral, and therefore vm_ln_max bounds do not make sense.") + @assert(!haskey(bus, "vm_ln_min"), "Bus $i does not have a neutral, and therefore vm_ln_min bounds do not make sense.") + @assert(!haskey(bus, "vm_ng_min"), "Bus $i does not have a neutral, and therefore a vm_ng_min bound does not make sense.") + @assert(!haskey(bus, "vm_ng_max"), "Bus $i does not have a neutral, and therefore a vm_ng_max bound does not make sense.") + end + nph = length(bus["phases"]) + for key in ["vm_ln_max", "vm_ln_min", "vm_lg_min", "vm_lg_max"] + if haskey(bus, key) + @assert(length(bus[key])==nph, "For bus $i, the length of $key should match the number of phases.") + end + end + if nph==1 + @assert(!haskey(bus, "vm_ll_min"), "Bus $i only has a single phase, so line-to-line bounds do not make sense.") + @assert(!haskey(bus, "vm_ll_max"), "Bus $i only has a single phase, so line-to-line bounds do not make sense.") + else + els = nph==2 ? 1 : nph + for key in ["vm_ll_min", "vm_ll_max"] + if haskey(bus, key) + @assert(length(bus["vm_ll_min"])==els, "For bus $i, the $key bound should have only $els elements.") + end + end + end + if haskey(bus, "voltage_zone") + zone_id = bus["voltage_zone"] + @assert(haskey(data["voltage_zone"], zone_id), "Bus $i: the voltage zone $zone_id is not defined.") + voltage_zone = data["voltage_zone"][zone_id] + for key in ["vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] + if haskey(voltage_zone, key) + @assert(!haskey(bus, key), "Bus $i: the property $key is already specified in the voltage_zone; this cannot be overwritten.") + end + end + end +end + +function create_bus(id; kwargs...) + bus = Dict{String,Any}() + bus["id"] = id + add_kwarg!(bus, kwargs, :terminals, collect(1:4)) + add_kwarg!(bus, kwargs, :phases, collect(1:3)) + add_kwarg!(bus, kwargs, :neutral, 4) + add_kwarg!(bus, kwargs, :grounded, false) + copy_kwargs_to_dict_if_present!(bus, kwargs, [:vm_nom_ln, :vm_ln_min, :vm_ln_max, :vm_ng_min, :vm_ng_max, :vm_lg_min, :vm_lg_max, :vm_ll_min, :vm_ll_max, :vm_fix, :va_fix, :rg, :xg]) + return bus +end + +function create_bus_in_zone(id, voltage_zone, data_model; kwargs...) + bus = Dict{String,Any}() + bus["id"] = id + bus["voltage_zone"] = voltage_zone + if !(id in data_model["voltage_zone"][voltage_zone]["buses"]) + push!(data_model["voltage_zone"][voltage_zone]["buses"], id) + end + add_kwarg!(bus, kwargs, :terminals, collect(1:4)) + add_kwarg!(bus, kwargs, :phases, collect(1:3)) + add_kwarg!(bus, kwargs, :neutral, 4) + add_kwarg!(bus, kwargs, :grounded, false) + copy_kwargs_to_dict_if_present!(bus, kwargs, [:vm_nom_ln, :vm_ln_min, :vm_ln_max, :vm_ng_min, :vm_ng_max, :vm_lg_min, :vm_lg_max, :vm_ll_min, :vm_ll_max, :vm_fix, :va_fix, :rg, :xg]) + return bus +end + + +# Load + +DTYPES[:load] = Dict( + :id => AbstractString, + :bus => String, + :terminals => Array{<:Int}, + :configuration => String, + :pd => Array{<:Real, 1}, + :qd => Array{<:Real, 1}, + :model => String, + :vnom => Array{<:Real, 1}, + :alpha => Array{<:Real, 1}, + :beta => Array{<:Real, 1}, +) + + +CHECKS[:load] = function check_load(data, load) + N = length(load["pd"]) + i = load["id"] + + @assert(load["configuration"] in ["wye", "delta"]) + + if load["configuration"]=="delta" + @assert(N>=3, "Load $i: delta-connected loads should have at least dimension 3, not $N.") + end + + for key in [:qd, :vm_nom, :alpha, :beta] + if haskey(load, key) + @assert(length(load[key])==N, "Load $i: $key should have the same length as pd.") + end + end +end + + +function create_load(id, bus; kwargs...) + load = Dict{String,Any}() + load["id"] = id + load["bus"] = bus + + add_kwarg!(load, kwargs, :configuration, "wye") + add_kwarg!(load, kwargs, :terminals, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) + add_kwarg!(load, kwargs, :pd, fill(0.0, 3)) + add_kwarg!(load, kwargs, :qd, fill(0.0, 3)) + add_kwarg!(load, kwargs, :model, "constant_power") + copy_kwargs_to_dict_if_present!(load, kwargs, [:vm_nom, :alpha, :beta]) + return load +end + +# generatorerator + + +DTYPES[:generator] = Dict( + :id => AbstractString, + :bus => String, + :terminals => Array{<:Int}, + :configuration => String, + :pd_set => Array{<:Real, 1}, + :qd_set => Array{<:Real, 1}, + :pd_min => Array{<:Real, 1}, + :pd_max => Array{<:Real, 1}, + :pd_min => Array{<:Real, 1}, + :pd_max => Array{<:Real, 1}, +) + + +CHECKS[:generator] = function check_generator(data, generator) + N = length(generator["pd_set"]) + i = generator["id"] + + @assert(generator["configuration"] in ["wye", "delta"]) + + if load["configuration"]=="delta" + @assert(N>=3, "generatorerator $i: delta-connected loads should have at least dimension 3, not $N.") + end +end + + +function create_generator(id, bus; kwargs...) + generator = Dict{String,Any}() + generator["id"] = id + generator["bus"] = bus + add_kwarg!(generator, kwargs, :configuration, "wye") + add_kwarg!(generator, kwargs, :terminals, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) + copy_kwargs_to_dict_if_present!(generator, kwargs, [:pd_min, :pd_max, :qd_min, :qd_max]) + return generator +end + + +# Transformer, n-windings three-phase lossy + + +DTYPES[:transformer_nw_lossy] = Dict( + :id => AbstractString, + :n_windings=>Int, + :n_phases=>Int, + :buses => Array{Int, 1}, + :terminals => Array{Array{Int, 1}, 1}, + :vnom => Array{<:Real, 1}, + :snom => Array{<:Real, 1}, + :configuration => Array{String, 1}, + :polarity => Array{Bool, 1}, + :xsc => Array{<:Real, 1}, + :rs => Array{<:Real, 1}, + :loadloss => Real, + :imag => Real, + :tm_fix => Array{Array{Bool, 1}, 1}, + :tm_set => Array{<:Array{<:Real, 1}, 1}, + :tm_min => Array{<:Array{<:Real, 1}, 1}, + :tm_max => Array{<:Array{<:Real, 1}, 1}, + :tm_step => Array{<:Array{<:Real, 1}, 1}, +) + + +CHECKS[:transformer_nw_lossy] = function check_transformer_nw_lossy(data, trans) + @assert(all([x in ["wye", "delta"] for x in trans["configuration"]])) + n_windings = trans["n_windings"] + @assert(length(trans["xsc"])==n_windings^2-n_windings) +end + + +function create_transformer_nw_lossy(id, n_windings, buses, terminals, vnom, snom; kwargs...) + trans = Dict{String,Any}() + trans["id"] = id + trans["buses"] = buses + trans["n_windings"] = n_windings + trans["terminals"] = terminals + trans["vnom"] = vnom + trans["snom"] = snom + add_kwarg!(trans, kwargs, :configuration, fill("wye", n_windings)) + add_kwarg!(trans, kwargs, :polarity, fill(true, n_windings)) + add_kwarg!(trans, kwargs, :rs, fill(0.0, n_windings)) + add_kwarg!(trans, kwargs, :xsc, fill(0.0, n_windings^2-n_windings)) + add_kwarg!(trans, kwargs, :loadloss, 0.0) + add_kwarg!(trans, kwargs, :imag, 0.0) + add_kwarg!(trans, kwargs, :tm_set, fill(fill(1.0, 3), n_windings)) + add_kwarg!(trans, kwargs, :tm_min, fill(fill(0.9, 3), n_windings)) + add_kwarg!(trans, kwargs, :tm_max, fill(fill(1.1, 3), n_windings)) + add_kwarg!(trans, kwargs, :tm_step, fill(fill(1/32, 3), n_windings)) + add_kwarg!(trans, kwargs, :tm_fix, fill(fill(true, 3), n_windings)) + copy_kwargs_to_dict_if_present!(trans, kwargs, [:tm_min, :tm_max]) + return trans +end + + +# Transformer, two-winding three-phase + +DTYPES[:transformer_2w_ideal] = Dict( + :id => AbstractString, + :f_bus => String, + :t_bus => String, + :configuration => String, + :f_terminals => Array{Int, 1}, + :t_terminals => Array{Int, 1}, + :tm_nom => Real, + :tm_set => Real, + :tm_min => Real, + :tm_max => Real, + :tm_step => Real, + :tm_fix => Real, +) + + +CHECKS[:transformer_2w_ideal] = function check_transformer_2w_ideal(data, trans) +end + + +function create_transformer_2w_ideal(id, f_bus, t_bus, tm_nom; kwargs...) + trans = Dict{String,Any}() + trans["id"] = id + trans["f_bus"] = f_bus + trans["t_bus"] = t_bus + trans["tm_nom"] = tm_nom + add_kwarg!(trans, kwargs, :configuration, "wye") + add_kwarg!(trans, kwargs, :f_terminals, trans["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) + add_kwarg!(trans, kwargs, :t_terminals, [1, 2, 3, 4]) + add_kwarg!(trans, kwargs, :tm_set, 1.0) + add_kwarg!(trans, kwargs, :tm_min, 0.9) + add_kwarg!(trans, kwargs, :tm_max, 1.1) + add_kwarg!(trans, kwargs, :tm_step, 1/32) + add_kwarg!(trans, kwargs, :tm_fix, true) + return trans +end + + +# Capacitor + +DTYPES[:capacitor] = Dict( + :id => AbstractString, + :bus => String, + :terminals => Array{Int, 1}, + :configuration => String, + :qd_ref => Array{<:Real, 1}, + :vm_nom => Real, +) + + +CHECKS[:capacitor] = function check_capacitor(data, cap) + i = cap["id"] + N = length(cap["terminals"]) + config = cap["configuration"] + if config=="wye" + @assert(length(cap["qd_ref"])==N-1, "Capacitor $i: qd_ref should have $(N-1) elements.") + else + @assert(length(cap["qd_ref"])==N, "Capacitor $i: qd_ref should have $N elements.") + end + @assert(config in ["delta", "wye", "wye-grounded", "wye-floating"]) + if config=="delta" + @assert(N>=3, "Capacitor $i: delta-connected capacitors should have at least 3 elements.") + end +end + + +function create_capacitor(id, bus, vm_nom; kwargs...) + cap = Dict{String,Any}() + cap["id"] = id + cap["bus"] = bus + cap["vm_nom"] = vm_nom + add_kwarg!(cap, kwargs, :configuration, "wye") + add_kwarg!(cap, kwargs, :terminals, collect(1:4)) + add_kwarg!(cap, kwargs, :qd_ref, fill(0.0, 3)) + return cap +end + + +# Shunt + +DTYPES[:shunt] = Dict( + :id => AbstractString, + :bus => String, + :terminals => Array{Int, 1}, + :g_sh => Array{<:Real, 2}, + :b_sh => Array{<:Real, 2}, +) + + +CHECKS[:shunt] = function check_shunt(data, shunt) +end + + +function create_shunt(id, bus, terminals; kwargs...) + shunt = Dict{String,Any}() + shunt["id"] = id + shunt["bus"] = bus + shunt["terminals"] = terminals + add_kwarg!(shunt, kwargs, :g_sh, fill(0.0, length(terminals), length(terminals))) + add_kwarg!(shunt, kwargs, :b_sh, fill(0.0, length(terminals), length(terminals))) + return shunt +end diff --git a/src/io/data_model_mapping.jl b/src/io/data_model_mapping.jl new file mode 100644 index 000000000..040720918 --- /dev/null +++ b/src/io/data_model_mapping.jl @@ -0,0 +1,341 @@ +import LinearAlgebra + + +function map_down_data_model(data_model_user) + data_model_base = deepcopy(data_model_user) + data_model_base["source_data_model"] = data_model_user + + _expand_linecode!(data_model_base) + _expand_voltage_zone!(data_model_base) + _load_to_shunt!(data_model_base) + _capacitor_to_shunt!(data_model_base) + _decompose_transformer_nw_lossy!(data_model_base) + + # add low level component types if not present yet + for comp_type in ["load", "generator", "bus", "line", "shunt", "transformer_2w_ideal"] + if !haskey(data_model_base, comp_type) + data_model_base[comp_type] = Dict{String, Any}() + end + end + return data_model_base +end + + +function _expand_voltage_zone!(data_model) + # expand line codes + for (id, bus) in data_model["bus"] + if haskey(bus, "voltage_zone") + voltage_zone = data_model["voltage_zone"][bus["voltage_zone"]] + bus["vnom"] = voltage_zone["vnom"] + for key in ["vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] + if haskey(voltage_zone, key) + bus[key] = voltage_zone[key] + end + end + delete!(bus, "voltage_zone") + end + end +end + + +function _expand_linecode!(data_model) + # expand line codes + for (id, line) in data_model["line"] + if haskey(line, "linecode") + linecode = data_model["linecode"][line["linecode"]] + for key in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + line[key] = line["length"]*linecode[key] + end + delete!(line, "linecode") + delete!(line, "length") + end + end +end + + +function _load_to_shunt!(data_model) + if haskey(data_model, "load") + for (id, load) in data_model["load"] + if load["model"]=="constant_impedance" + b = load["qd"]./load["vm_nom"].^2*1E3 + g = load["pd"]./load["vm_nom"].^2*1E3 + y = b.+im*g + N = length(b) + + if load["configuration"]=="delta" + # create delta transformation matrix Md + Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) + Md[N,1] = -1 + Y = Md'*LinearAlgebra.diagm(0=>y)*Md + + else # load["configuration"]=="wye" + Y_fr = LinearAlgebra.diagm(0=>y) + # B = [[b]; -1'*[b]]*[I -1] + Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) + end + + shunt = create_shunt(NaN, load["bus"], load["terminals"], b_sh=imag.(Y), g_sh=real.(Y)) + add_component!(data_model, "shunt", shunt) + delete_component!(data_model, "load", load) + + add_mapping!(data_model, "load_to_shunt", Dict( + "load" => data_model["source_data_model"]["load"][id], + "shunt" => shunt, + )) + end + end + end +end + + +function _capacitor_to_shunt!(data_model) + if haskey(data_model, "capacitor") + for (id, cap) in data_model["capacitor"] + b = cap["qd_ref"]./cap["vm_nom"]^2*1E3 + N = length(b) + + if cap["configuration"]=="delta" + # create delta transformation matrix Md + Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) + Md[N,1] = -1 + B = Md'*LinearAlgebra.diagm(0=>b)*Md + + elseif cap["configuration"]=="wye-grounded" + B = LinearAlgebra.diagm(0=>b) + + elseif cap["configuration"]=="wye-floating" + # this is a floating wye-segment + # B = [b]*(I-1/(b'*1)*[b';...;b']) + B = LinearAlgebra.diagm(0=>b)*(LinearAlgebra.diagm(0=>ones(N)) - 1/sum(b)*repeat(b',N,1)) + + else # cap["configuration"]=="wye" + B_fr = LinearAlgebra.diagm(0=>b) + # B = [[b]; -1'*[b]]*[I -1] + B = vcat(B_fr, -ones(N)'*B_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) + end + + shunt = create_shunt(NaN, cap["bus"], cap["terminals"], b_sh=B) + add_component!(data_model, "shunt", shunt) + delete_component!(data_model, "capacitor", cap) + + add_mapping!(data_model, "capacitor_to_shunt", Dict( + "capacitor" => data_model["source_data_model"]["capacitor"][id], + "shunt" => shunt, + )) + end + end +end + + +# test +""" + + function decompose_transformer_nw_lossy!(data_model) + +Replaces complex transformers with a composition of ideal transformers and lines +which model losses. New buses (virtual, no physical meaning) are added. +""" +function _decompose_transformer_nw_lossy!(data_model) + for (tr_id, trans) in data_model["transformer_nw_lossy"] + + vnom = trans["vnom"]*data_model["v_var_scalar"] + snom = trans["snom"]*data_model["v_var_scalar"] + + nrw = length(trans["buses"]) + + # calculate zbase in which the data is specified, and convert to SI + zbase = (vnom.^2)./snom + # x_sc is specified with respect to first winding + x_sc = trans["xsc"].*zbase[1] + # rs is specified with respect to each winding + r_s = trans["rs"].*zbase + + g_sh = (trans["noloadloss"]*snom[1]/3)/vnom[1]^2 + b_sh = (trans["imag"]*snom[1]/3)/vnom[1]^2 + + # data is measured externally, but we now refer it to the internal side + ratios = vnom/1E3 + x_sc = x_sc./ratios[1]^2 + r_s = r_s./ratios.^2 + g_sh = g_sh*ratios[1]^2 + b_sh = b_sh*ratios[1]^2 + + # convert x_sc from list of upper triangle elements to an explicit dict + y_sh = g_sh + im*b_sh + z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) + + vbuses, vlines, trans_t_bus_w = _build_loss_model!(data_model, r_s, z_sc, y_sh) + + trans_w = Array{Dict, 1}(undef, nrw) + for w in 1:nrw + # 2-WINDING TRANSFORMER + # make virtual bus and mark it for reduction + tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] + trans_w[w] = create_transformer_2w_ideal(NaN, + trans["buses"][w], trans_t_bus_w[w], tm_nom, + f_terminals = trans["terminals"][w], + t_terminals = collect(1:4), + configuration = trans["configuration"][w], + polarity = trans["polarity"][w], + #tm_set = trans["tm_set"][w], + tm_fix = trans["tm_fix"][w], + #tm_max = trans["tm_max"][w], + #tm_min = trans["tm_min"][w], + #tm_step = trans["tm_step"][w], + ) + + add_component!(data_model, "transformer_2w_ideal", trans_w[w]) + end + + delete_component!(data_model, "transformer_nw_lossy", trans) + + add_mapping!(data_model, "transformer_decomposition", Dict( + "trans"=>data_model["source_data_model"]["transformer_nw_lossy"][tr_id], + "trans_w"=>trans_w, + "vlines"=>vlines, + "vbuses"=>vbuses, + )) + end +end + + +""" +Converts a set of short-circuit tests to an equivalent reactance network. +Reference: +R. C. Dugan, “A perspective on transformer modeling for distribution system analysis,” +in 2003 IEEE Power Engineering Society General Meeting (IEEE Cat. No.03CH37491), 2003, vol. 1, pp. 114-119 Vol. 1. +""" +function _sc2br_impedance(Zsc) + N = maximum([maximum(k) for k in keys(Zsc)]) + # check whether no keys are missing + # Zsc should contain tupples for upper triangle of NxN + for i in 1:N + for j in i+1:N + if !haskey(Zsc, (i,j)) + if haskey(Zsc, (j,i)) + # Zsc is symmetric; use value of lower triangle if defined + Zsc[(i,j)] = Zsc[(j,i)] + else + Memento.error(_LOGGER, "Short-circuit impedance between winding $i and $j is missing.") + end + end + end + end + # make Zb + Zb = zeros(Complex{Float64}, N-1,N-1) + for i in 1:N-1 + Zb[i,i] = Zsc[(1,i+1)] + end + for i in 1:N-1 + for j in 1:i-1 + Zb[i,j] = (Zb[i,i]+Zb[j,j]-Zsc[(j+1,i+1)])/2 + Zb[j,i] = Zb[i,j] + end + end + # get Ybus + Y = LinearAlgebra.pinv(Zb) + Y = [-Y*ones(N-1) Y] + Y = [-ones(1,N-1)*Y; Y] + # extract elements + Zbr = Dict() + for k in keys(Zsc) + Zbr[k] = (abs(Y[k...])==0) ? Inf : -1/Y[k...] + end + return Zbr +end + + +function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) + # precompute the minimal set of buses and lines + N = length(r_s) + tr_t_bus = collect(1:N) + buses = Set(1:2*N) + edges = [[[i,i+N] for i in 1:N]..., [[i+N,j+N] for (i,j) in keys(zsc)]...] + lines = Dict(enumerate(edges)) + z = Dict(enumerate([r_s..., values(zsc)...])) + shunts = Dict(2=>ysh) + + # remove Inf lines + + for (l,edge) in lines + if real(z[l])==Inf || imag(z[l])==Inf + delete!(lines, l) + delete!(z, l) + end + end + + # merge short circuits + + stack = Set(keys(lines)) + + while !isempty(stack) + l = pop!(stack) + if z[l] == 0 + (i,j) = lines[l] + # remove line + delete!(lines, l) + # remove bus j + delete!(buses, j) + # update lines + for (k,(edge)) in lines + if edge[1]==j + edge[1] = i + end + if edge[2]==j + edge[2] = i + end + if edge[1]==edge[2] + delete!(lines, k) + delete!(stack, k) + end + end + # move shunts + if haskey(shunts, j) + if haskey(shunts, i) + shunts[i] += shunts[j] + else + shunts[i] = shunts[j] + end + end + # update transformer buses + for w in 1:N + if tr_t_bus[w]==j + tr_t_bus[w] = i + end + end + end + end + + bus_ids = Dict() + for bus in buses + bus_ids[bus] = add_component!(data_model, "bus", create_bus("")) + end + line_ids = Dict() + for (l,(i,j)) in lines + # merge the shunts into the shunts of the pi model of the line + g_fr = b_fr = g_to = b_to = 0 + if haskey(shunts, i) + g_fr = real(shunts[i]) + b_fr = imag(shunts[i]) + delete!(shunts, i) + end + if haskey(shunts, j) + g_fr = real(shunts[j]) + b_fr = imag(shunts[j]) + delete!(shunts, j) + end + line_ids[l] = add_component!(data_model, "line", create_line("", + bus_ids[i], bus_ids[j], n_phases, + f_terminals = collect(1:n_phases), + t_terminals = collect(1:n_phases), + rs = LinearAlgebra.diagm(0=>fill(real(z[l]), n_phases)), + xs = LinearAlgebra.diagm(0=>fill(imag(z[l]), n_phases)), + g_fr = LinearAlgebra.diagm(0=>fill(g_fr, n_phases)), + b_fr = LinearAlgebra.diagm(0=>fill(b_fr, n_phases)), + g_to = LinearAlgebra.diagm(0=>fill(g_to, n_phases)), + b_to = LinearAlgebra.diagm(0=>fill(b_to, n_phases)), + )) + end + + return bus_ids, line_ids, [bus_ids[bus] for bus in tr_t_bus] +end diff --git a/src/io/data_model_pu.jl b/src/io/data_model_pu.jl new file mode 100644 index 000000000..bb58aab8a --- /dev/null +++ b/src/io/data_model_pu.jl @@ -0,0 +1,278 @@ + +function _find_zones(data_model) + unused_line_ids = Set(keys(data_model["line"])) + bus_lines = Dict([(id,Set()) for id in keys(data_model["bus"])]) + for (line_id,line) in data_model["line"] + f_bus = line["f_bus"] + t_bus = line["t_bus"] + push!(bus_lines[f_bus], (line_id,t_bus)) + push!(bus_lines[t_bus], (line_id,f_bus)) + end + zones = [] + buses = Set(keys(data_model["bus"])) + while !isempty(buses) + stack = [pop!(buses)] + zone = Set() + while !isempty(stack) + bus = pop!(stack) + delete!(buses, bus) + push!(zone, bus) + for (line_id,bus_to) in bus_lines[bus] + if line_id in unused_line_ids && bus_to in buses + delete!(unused_line_ids, line_id) + push!(stack, bus_to) + end + end + end + append!(zones, [zone]) + end + zones = Dict(enumerate(zones)) + + return zones +end + + +function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) + # find zones of buses connected by lines + zones = _find_zones(data_model) + bus_to_zone = Dict([(bus,zone) for (zone, buses) in zones for bus in buses]) + + # assign specified vbase to corresponding zones + zone_vbase = Dict{Int, Union{Missing,Real}}([(zone,missing) for zone in keys(zones)]) + for (bus,vbase) in vbase_sources + if !ismissing(zone_vbase[bus_to_zone[bus]]) + Memento.warn("You supplied multiple voltage bases for the same zone; ignoring all but the last one.") + end + zone_vbase[bus_to_zone[bus]] = vbase + end + + # transformers form the edges between these zones + zone_edges = Dict([(zone,[]) for zone in keys(zones)]) + edges = Set() + for (i,(_,trans)) in enumerate(data_model["transformer_2w_ideal"]) + push!(edges,i) + f_zone = bus_to_zone[trans["f_bus"]] + t_zone = bus_to_zone[trans["t_bus"]] + tm_nom = trans["configuration"]=="delta" ? trans["tm_nom"]/sqrt(3) : trans["tm_nom"] + push!(zone_edges[f_zone], (i, t_zone, 1/tm_nom)) + push!(zone_edges[t_zone], (i, f_zone, tm_nom)) + end + + # initialize the stack with all specified zones + stack = [zone for (zone,vbase) in zone_vbase if !ismissing(vbase)] + + while !isempty(stack) + + zone = pop!(stack) + + for (edge_id, zone_to, scale) in zone_edges[zone] + delete!(edges, edge_id) + + if ismissing(zone_vbase[zone_to]) + zone_vbase[zone_to] = zone_vbase[zone]*scale + push!(stack, zone_to) + end + end + end + + bus_vbase = Dict([(bus,zone_vbase[zone]) for (bus,zone) in bus_to_zone]) + line_vbase = Dict([(id, bus_vbase[line["f_bus"]]) for (id,line) in data_model["line"]]) + return (bus_vbase, line_vbase) +end + + +function make_pu!(data_model; sbase=1, vbases=missing) + v_var_scalar = data_model["v_var_scalar"] + + sbase_old = get(data_model, "sbase", missing) + + # automatically find a good vbase + if ismissing(vbases) + buses_vnom = [(id, bus["vnom"]) for (id,bus) in data_model["bus"] if haskey(bus, "vnom")] + if !isempty(buses_vnom) + vbases = Dict([buses_vnom[1]]) + else + Memento.error("Please specify vbases manually; cannot make an educated guess for this data model.") + end + end + + bus_vbase, line_vbase = _calc_vbase(data_model, vbases) + + for (id, bus) in data_model["bus"] + _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, v_var_scalar) + end + + for (id, line) in data_model["line"] + vbase = line_vbase[id] + _rebase_pu_line!(line, line_vbase[id], sbase, sbase_old, v_var_scalar) + end + + for (id, shunt) in data_model["shunt"] + _rebase_pu_shunt!(shunt, bus_vbase[shunt["bus"]], sbase, sbase_old, v_var_scalar) + end + + for (id, load) in data_model["load"] + _rebase_pu_load!(load, bus_vbase[load["bus"]], sbase, sbase_old, v_var_scalar) + end + + for (id, gen) in data_model["generator"] + #_rebase_pu_generator!(gen, bus_vbase[gen["bus"]], sbase_old, sbase, v_var_scalar) + end + + for (id, trans) in data_model["transformer_2w_ideal"] + # voltage base across transformer does not have to be consistent with the ratio! + f_vbase = bus_vbase[trans["f_bus"]] + t_vbase = bus_vbase[trans["t_bus"]] + _rebase_pu_transformer_2w_ideal!(trans, f_vbase, t_vbase, sbase_old, sbase, v_var_scalar) + end + + data_model["sbase"] = sbase + + return data_model +end + + +function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) + + # if not in p.u., these are normalized with respect to vnom + prop_vnom = ["vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] + + if !haskey(bus, "vbase") + + if haskey(bus, "vnom") + vnom = bus["vnom"] + _scale_props!(bus, prop_vnom, vnom/vbase) + _scale_props!(bus, ["vnom"], 1/vbase) + end + + z_old = 1.0 + else + vbase_old = bus["vbase"] + _scale_props!(bus, [prop_vnom..., "vnom"], vbase_old/vbase) + + z_old = vbase_old^2*sbase_old*v_var_scalar + end + + # rebase grounding resistance + z_new = vbase^2/sbase*v_var_scalar + z_scale = z_old/z_new + _scale_props!(bus, ["rg", "xg"], z_scale) + + # save new vbase + bus["vbase"] = vbase +end + + +function _rebase_pu_line!(line, vbase, sbase, sbase_old, v_var_scalar) + + if !haskey(line, "vbase") + z_old = 1 + else + z_old = vbase_old^2/sbase_old*v_var_scalar + end + + z_new = vbase^2/sbase*v_var_scalar + z_scale = z_old/z_new + y_scale = 1/z_scale + + _scale_props!(line, ["rs", "xs"], z_scale) + _scale_props!(line, ["b_fr", "g_fr", "b_to", "g_to"], y_scale) + + # save new vbase + line["vbase"] = vbase +end + + +function _rebase_pu_shunt!(shunt, vbase, sbase, sbase_old, v_var_scalar) + + if !haskey(shunt, "vbase") + z_old = 1 + else + z_old = vbase_old^2/sbase_old*v_var_scalar + end + + # rebase grounding resistance + z_new = vbase^2/sbase*v_var_scalar + z_scale = z_old/z_new + y_scale = 1/z_scale + scale(shunt, "gs", y_scale) + scale(shunt, "bs", y_scale) + + # save new vbase + shunt["vbase"] = vbase +end + + +function _rebase_pu_load!(load, vbase, sbase, sbase_old, v_var_scalar) + + if !haskey(load, "vbase") + vbase_old = 1 + sbase_old = 1 + else + vbase_old = load["vbase"] + end + + vbase_old = get(load, "vbase", 1.0) + vbase_scale = vbase_old/vbase + scale(load, "vnom", vbase_scale) + + sbase_scale = sbase_old/sbase + scale(load, "pd", sbase_scale) + scale(load, "qd", sbase_scale) + + # save new vbase + load["vbase"] = vbase +end + + +function _rebase_pu_generator!(gen, vbase_new, sbase_old, sbase_new, v_var_scalar) + vbase_old = get(gen, "vbase", 1.0/v_var_scalar) + vbase_scale = vbase_old/vbase_new + + for key in ["pd_set", "qd_set", "pd_min", "qd_min", "pd_max", "qd_max"] + scale(gen, key, sbase_scale) + end + + # save new vbase + gen["vbase"] = vbase +end + + +function _rebase_pu_transformer_2w_ideal!(trans, f_vbase_new, t_vbase_new, sbase_old, sbase_new, v_var_scalar) + f_vbase_old = get(trans, "f_vbase", 1.0) + t_vbase_old = get(trans, "t_vbase", 1.0) + f_vbase_scale = f_vbase_old/f_vbase_new + t_vbase_scale = t_vbase_old/t_vbase_new + + scale(trans, "tm_nom", f_vbase_scale/t_vbase_scale) + + # save new vbase + trans["f_vbase"] = f_vbase_new + trans["t_vbase"] = t_vbase_new +end + + +function _scale_props!(comp::Dict{String, Any}, prop_names::Array{String, 1}, scale::Real) + for name in prop_names + if haskey(comp, name) + comp[name] *= scale + end + end +end + +#data_model_user = make_test_data_model() +#data_model_base = map_down_data_model(data_model_user) +#bus_vbase, line_vbase = get_vbase(data_model_base, Dict("1"=>230.0)) + +#make_pu!(data_model_base) + +function add_big_M!(data_model; kwargs...) + big_M = Dict{String, Any}() + + big_M["v_phase_pu_min"] = add_kwarg!(big_M, kwargs, :v_phase_pu_min, 0.5) + big_M["v_phase_pu_max"] = add_kwarg!(big_M, kwargs, :v_phase_pu_max, 2.0) + big_M["v_neutral_pu_min"] = add_kwarg!(big_M, kwargs, :v_neutral_pu_min, 0.0) + big_M["v_neutral_pu_max"] = add_kwarg!(big_M, kwargs, :v_neutral_pu_max, 0.5) + + data_model["big_M"] = big_M +end diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl new file mode 100644 index 000000000..e10e1d3ad --- /dev/null +++ b/src/io/data_model_test.jl @@ -0,0 +1,88 @@ +BASE_DIR = "/Users/sclaeys/code/PowerModelsDistribution.jl/src/io" +include("$BASE_DIR/data_model_util.jl") +include("$BASE_DIR/data_model_components.jl") +include("$BASE_DIR/data_model_mapping.jl") +#include("$BASE_DIR/data_model_pu.jl") + +function make_test_data_model() + + data_model = create_data_model() + + data_model["linecode"] = component_dict_from_list!([ + create_linecode("1", 6, rs=ones(6, 6), xs=ones(6, 6)), + create_linecode("2", 4, rs=ones(4, 4), xs=ones(4, 4)), + create_linecode("3", 3, rs=ones(3, 3), xs=ones(3, 3)), + create_linecode("4", 2, rs=ones(2, 2), xs=ones(2, 2)), + ]) + + data_model["line"] = component_dict_from_list!([ + # 3 phase + 3 neutral conductors + create_line("1", "1", "2", 6; t_terminals=[1,2,3,4,4,4]), + # 3 phase + 1 neutral conductors + create_line("2", "2", "3", 4), + # 3 phase conductors + create_line_with_linecode("3", "3", "4", data_model["linecode"]["4"], 1.3), + # 2 phase + 1 neutral conductors + create_line("4", "3", "5", 3, f_terminals=[1,3,4], t_terminals=[1,3,4]), + # 1 phase + 1 neutral conductors + create_line_with_linecode("5", "3", "6", data_model["linecode"]["4"], 1.7, f_terminals=[2,4], t_terminals=[2,4]), + # 2 phase conductors + create_line("6", "3", "7", 2, f_terminals=[1,2], t_terminals=[1,2]), + ]) + + data_model["voltage_zone"] = component_dict_from_list!([ + create_voltage_zone("1", 0.400) + ]) + + data_model["bus"] = component_dict_from_list!([ + create_bus_in_zone("1", "1", data_model, grounded=true, rg=0.1, xg=0.1, vm_fix=[fill(230, 3)..., 0], va_fix=[0, -pi*2/3, pi*2/3, 0]), + create_bus_in_zone("2", "1", data_model), + create_bus_in_zone("3", "1", data_model), + create_bus_in_zone("4", "1", data_model, terminals=[1,2,3], neutral=missing, vm_ll_min=fill(0.9, 3), vm_ll_max=fill(1.1, 3)), + create_bus_in_zone("5", "1", data_model, terminals=[1,3,4], phases=[1,3], vm_ln_min=fill(0.9, 2), vm_ln_max=fill(1.1, 2)), + create_bus_in_zone("6", "1", data_model, terminals=[2,4], phases=[2], vm_ln_min=fill(0.9, 1), vm_ln_max=fill(1.1, 1)), + create_bus_in_zone("7", "1", data_model, terminals=[1,2], phases=[1,2], neutral=missing, vm_ll_min=fill(0.9, 1), vm_ll_max=fill(1.1, 1)), + create_bus_in_zone("8", "1", data_model, vm_ln_min=fill(230*0.9, 1), vm_ln_max=fill(1.1, 1)), + create_bus_in_zone("9", "1", data_model, terminals=[1,2,3], neutral=missing, vm_ll_min=fill(0.9, 1), vm_ll_max=fill(1.1, 1)), + ]) + + data_model["load"] = component_dict_from_list!([ + create_load("1", "6", terminals=[2,4], pd=[1.0], qd=[1.0]), + create_load("2", "7", terminals=[1,2], pd=[1.0], qd=[1.0], model="constant_current", vm_nom=[230*sqrt(3)]), + create_load("3", "5", terminals=[1,4], pd=[1.0], qd=[1.0], model="constant_impedance", vm_nom=[230]), + create_load("4", "5", terminals=[3,4], pd=[1.0], qd=[1.0], model="exponential", vm_nom=[230], alpha=[1.2], beta=[1.5]), + create_load("5", "3", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3)), + create_load("6", "3", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vm_nom=fill(230, 3)), + create_load("7", "3", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vm_nom=fill(230, 3)), + create_load("8", "3", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vm_nom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]), + create_load("9", "4", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3)), + ]) + + data_model["generator"] = component_dict_from_list!([ + create_generator("1", "1", configuration="wye"), + ]) + + data_model["transformer_nw3ph_lossy"] = component_dict_from_list!([ + create_transformer_nw3ph_lossy("1", 3, ["4", "8", "9"], [[1,2,3], [1,2,3,4], [1,2,3]], + [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], + configuration=["delta", "wye", "delta"], + xsc=[0.0, 0.0, 0.0], + rs=[0.0, 0.0, 1.0], + loadloss=0.05, + imag=0.05, + ), + ]) + + data_model["capacitor"] = component_dict_from_list!([ + create_capacitor("cap_3ph", "3", 0.230*sqrt(3), qd_ref=[1, 2, 3]), + create_capacitor("cap_3ph_delta", "4", 0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", terminals=[1,2,3]), + create_capacitor("cap_2ph_yg", "6", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,2], configuration="wye-grounded"), + create_capacitor("cap_2ph_yfl", "6", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,2], configuration="wye-floating"), + create_capacitor("cap_2ph_y", "5", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,3,4]), + ]) + + return data_model +end + +#dm = make_test_data_model() +#index_data_model(dm, components=["capacitor"]) diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl new file mode 100644 index 000000000..ad14773b8 --- /dev/null +++ b/src/io/data_model_util.jl @@ -0,0 +1,76 @@ + +#import PowerModelsDistribution +#get = PowerModelsDistribution.get + +function scale(dict, key, scale) + if haskey(dict, key) + dict[key] *= scale + end +end + + +function add_component!(data_model, comp_type, comp) + if !haskey(data_model, comp_type) + data_model[comp_type] = Dict{String, Any}() + end + comp_dict = data_model[comp_type] + virtual_ids = [parse(Int, x[1]) for x in [match(r"_virtual_([1-9]{1}[0-9]*)", id) for id in keys(comp_dict)] if !isnothing(x)] + if isempty(virtual_ids) + id = "_virtual_1" + else + id = "_virtual_$(maximum(virtual_ids)+1)" + end + comp["id"] = id + comp_dict[id] = comp + return id +end + +function delete_component!(data_model, comp_type, comp) + delete!(data_model[comp_type], comp["id"]) + if isempty(data_model[comp_type]) + delete!(data_model, comp_type) + end +end + +function add_mapping!(data_model::Dict{String, Any}, mapping_type::String, mapping::Dict{String, <:Any}) + if !haskey(data_model, "mapping") + data_model["mapping"] = Dict{String, Any}() + end + + if !haskey(data_model["mapping"], mapping_type) + data_model["mapping"][mapping_type] = Array{Dict{String, Any}, 1}() + end + + push!(data_model["mapping"][mapping_type], mapping) +end + + +function data_model_index!(data_model; components=["line", "bus", "shunt", "transformer_2w_ideal", "load", "generator"]) + for comp_type in components + comp_dict = Dict{String, Any}() + for (i,(id,comp)) in enumerate(data_model[comp_type]) + @assert(!haskey(comp, "index"), "$comp_type $id: component already has an index.") + comp["index"] = i + comp["id"] = id + comp_dict["$i"] = comp + end + data_model[comp_type] = comp_dict + end + return data_model +end + + +function solution_ind2id(solution, data_model; id_prop="id") + for comp_type in keys(solution) + if isa(solution[comp_type], Dict) + comp_dict = Dict{String, Any}() + for (ind, comp) in solution[comp_type] + id = data_model[comp_type][ind][id_prop] + comp_dict[id] = comp + end + solution[comp_type] = comp_dict + end + end + + return solution +end diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index b17a12a93..e2157a288 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -396,10 +396,12 @@ Capacitor. If `bus2` is not specified, the capacitor will be treated as a shunt. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_capacitor(bus1="", name::AbstractString="", bus2=0; kwargs...) +function _create_capacitor(bus1="", name::AbstractString=""; kwargs...) kwargs = Dict{Symbol,Any}(kwargs) phases = get(kwargs, :phases, 3) + bus2 = get(kwargs, :bus2, string(split(bus1, ".")[1],".",join(fill("0", phases), "."))) + return Dict{String,Any}("name" => name, "bus1" => bus1, "bus2" => bus2, diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl new file mode 100644 index 000000000..676195365 --- /dev/null +++ b/src/io/opendss_dm.jl @@ -0,0 +1,1282 @@ +# OpenDSS parser +import LinearAlgebra: isdiag, diag, pinv + + +"Structure representing OpenDSS `dss_source_id` giving the type of the component `dss_type`, its name `dss_name`, and the active phases `active_phases`" +struct DSSSourceId + dss_type::AbstractString + dss_name::AbstractString + active_phases::Set{Int} +end + + +"Parses a component's OpenDSS source information into the `dss_source_id` struct" +function _parse_dss_source_id(component::Dict)::DSSSourceId + dss_type, dss_name = split(component["source_id"], '.') + return DSSSourceId(dss_type, dss_name, Set(component["active_phases"])) +end + + +"returns the linecode with name `id`" +function _get_linecode(dss_data::Dict, id::AbstractString) + if haskey(dss_data, "linecode") + for item in dss_data["linecode"] + if item["name"] == id + return item + end + end + end + return Dict{String,Any}() +end + + +""" + _discover_buses(dss_data) + +Discovers all of the buses (not separately defined in OpenDSS), from "lines". +""" +function _discover_buses(dss_data::Dict)::Array + bus_names = [] + buses = [] + for compType in ["line", "transformer", "reactor"] + if haskey(dss_data, compType) + compList = dss_data[compType] + for compObj in compList + if compType == "transformer" + compObj = _create_transformer(compObj["name"]; _to_sym_keys(compObj)...) + for bus in compObj["buses"] + name, nodes = _parse_busname(bus) + if !(name in bus_names) + push!(bus_names, name) + push!(buses, (name, nodes)) + end + end + elseif haskey(compObj, "bus2") + for key in ["bus1", "bus2"] + name, nodes = _parse_busname(compObj[key]) + if !(name in bus_names) + push!(bus_names, name) + push!(buses, (name, nodes)) + end + end + end + end + end + end + if length(buses) == 0 + Memento.error(_LOGGER, "dss_data has no lines!") + else + return buses + end +end + + +""" + _dss2pmd_bus!(pmd_data, dss_data) + +Adds PowerModels-style buses to `pmd_data` from `dss_data`. +""" +function _dss2pmd_bus_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1) + if !haskey(pmd_data, "bus") + pmd_data["bus"] = Dict{String, Any}() + end + + buses = _discover_buses(dss_data) + for (n, (bus, nodes)) in enumerate(buses) + busDict = Dict{String,Any}() + + busDict["id"] = bus + + @assert(!(length(bus)>=8 && bus[1:8]=="_virtual"), "Bus $bus: identifiers should not start with _virtual.") + + busDict["bus_type"] = 1 + + + pmd_data["bus"][busDict["id"]] = busDict + end + + # create virtual sourcebus + circuit = _create_vsource(get(dss_data["circuit"][1], "bus1", "sourcebus"), dss_data["circuit"][1]["name"]; _to_sym_keys(dss_data["circuit"][1])...) + + busDict = Dict{String,Any}() + + nodes = Array{Bool}([1 1 1 0]) + ph1_ang = circuit["angle"] + vm = circuit["pu"] + vmi = circuit["pu"] - circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) + vma = circuit["pu"] + circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) + + busDict["id"] = "_virtual_sourcebus" + + busDict["bus_type"] = 3 + + phases = circuit["phases"] + busDict["vm_fix"] = fill(vm, 3) + busDict["vnom"] = pmd_data["basekv"]/sqrt(3) + busDict["va_fix"] = [rad2deg(2*pi/phases*(i-1))+ph1_ang for i in 1:phases] + + pmd_data["bus"][busDict["id"]] = busDict +end + + +""" + find_component(pmd_data, name, compType) + +Returns the component of `compType` with `name` from `data` of type +Dict{String,Array}. +""" +function find_component(data::Dict, name::AbstractString, compType::AbstractString)::Dict + for comp in values(data[compType]) + if comp["name"] == name + return comp + end + end + Memento.warn(_LOGGER, "Could not find $compType \"$name\"") + return Dict{String,Any}() +end + + +""" + find_bus(busname, pmd_data) + +Finds the index number of the bus in existing data from the given `busname`. +""" +function find_bus(busname::AbstractString, pmd_data::Dict) + bus = find_component(pmd_data, busname, "bus") + if haskey(bus, "bus_i") + return bus["bus_i"] + else + Memento.error(_LOGGER, "cannot find connected bus with id \"$busname\"") + end +end + + +""" + _dss2pmd_load!(pmd_data, dss_data, import_all) + +Adds PowerModels-style loads to `pmd_data` from `dss_data`. +""" +function _dss2pmd_load_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool, ground_terminal::Int=4) + if !haskey(pmd_data, "load") + pmd_data["load"] = Dict{String, Any}() + end + + for load in get(dss_data, "load", []) + _apply_like!(load, dss_data, "load") + defaults = _apply_ordered_properties(_create_load(load["bus1"], load["name"]; _to_sym_keys(load)...), load) + + nphases = defaults["phases"] + conn = defaults["conn"] + + loadDict = Dict{String,Any}() + + loadDict["id"] = defaults["name"] + + load_name = defaults["name"] + + # connections + bus = _parse_busname(defaults["bus1"])[1] + loadDict["bus"] = bus + terminals_default = conn=="wye" ? [collect(1:nphases)..., 0] : collect(1:nphases) + terminals = _get_conductors_ordered_dm(defaults["bus1"], default=terminals_default, check_length=false) + # if wye connected and neutral not specified, append ground + if conn=="wye" && length(terminals)==nphases + terminals = [terminals..., 0] + end + # if the ground is used directly, register load + if 0 in terminals + if !haskey(pmd_data["bus"][bus], "awaiting_ground") + pmd_data["bus"][bus]["awaiting_ground"] = [] + end + push!(pmd_data["bus"][bus]["awaiting_ground"], loadDict) + end + loadDict["configuration"] = conn + loadDict["terminals"] = terminals + + kv = defaults["kv"] + if conn=="wye" && nphases in [2, 3] + kv = kv/sqrt(3) + end + loadDict["pd"] = fill(defaults["kw"]/nphases, nphases) + loadDict["qd"] = fill(defaults["kvar"]/nphases, nphases) + loadDict["vnom"] = fill(kv, nphases) + + # parse the model + model = defaults["model"] + # some info on OpenDSS load models + ################################## + # Constant can still be scaled by other settings, fixed cannot + # Note that in the current feature set, fixed therefore equals constant + # 1: Constant P and Q, default + if model == 2 + # 2: Constant Z + elseif model == 3 + # 3: Constant P and quadratic Q + Memento.warn(_LOGGER, "$load_name: load model 3 not supported. Treating as model 1.") + model = 1 + elseif model == 4 + # 4: Exponential + Memento.warn(_LOGGER, "$load_name: load model 4 not supported. Treating as model 1.") + model = 1 + elseif model == 5 + # 5: Constant I + #warn(_LOGGER, "$name: load model 5 not supported. Treating as model 1.") + #model = 1 + elseif model == 6 + # 6: Constant P and fixed Q + Memento.warn(_LOGGER, "$load_name: load model 6 identical to model 1 in current feature set. Treating as model 1.") + model = 1 + elseif model == 7 + # 7: Constant P and quadratic Q (i.e., fixed reactance) + Memento.warn(_LOGGER, "$load_name: load model 7 not supported. Treating as model 1.") + model = 1 + elseif model == 8 + # 8: ZIP + Memento.warn(_LOGGER, "$load_name: load model 8 not supported. Treating as model 1.") + model = 1 + end + # save adjusted model type to dict, human-readable + model_int2str = Dict(1=>"constant_power", 2=>"constant_impedance", 5=>"constant_current") + loadDict["model"] = model_int2str[model] + + #loadDict["status"] = convert(Int, defaults["enabled"]) + + #loadDict["source_id"] = "load.$load_name" + + used = ["phases", "bus1", "name"] + #_PMs._import_remaining!(loadDict, defaults, import_all; exclude=used) + + pmd_data["load"][loadDict["id"]] = loadDict + end +end + + +""" + _dss2pmd_shunt!(pmd_data, dss_data, import_all) + +Adds PowerModels-style shunts to `pmd_data` from `dss_data`. +""" +function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "shunt") + pmd_data["shunt"] = Dict{String, Any}() + end + + for shunt in get(dss_data, "capacitor", []) + _apply_like!(shunt, dss_data, "capacitor") + defaults = _apply_ordered_properties(_create_capacitor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) + + shuntDict = Dict{String,Any}() + + shuntDict["id"] = "capacitor.$(defaults["name"])" + shuntDict["status"] = convert(Int, defaults["enabled"]) + + nphases = defaults["phases"] + + dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") + conn = dyz_map[defaults["conn"]] + + bus_name = _parse_busname(defaults["bus1"])[1] + bus2_name = _parse_busname(defaults["bus2"])[1] + if bus_name!=bus2_name + Memento.error("Capacitor $(defaults["name"]): bus1 and bus2 should connect to the same bus.") + end + shuntDict["bus"] = bus_name + + f_terminals = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) + if conn=="wye" + t_terminals = _get_conductors_ordered_dm(defaults["bus2"], default=fill(0,nphases)) + else + # if delta connected, ignore bus2 and generate t_terminals such that + # it corresponds to a delta winding + t_terminals = [f_terminals[2:end]..., f_terminals[1]] + end + + + # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) + #TODO figure out for more than 3 phases + vnom_ln = defaults["kv"] + if defaults["phases"] in [2,3] + vnom_ln = vnom_ln/sqrt(3) + end + # 'kvar' is specified for all phases at once; we want the per-phase one, in MVar + qnom = (defaults["kvar"]/1E3)/nphases + b = fill(qnom/vnom_ln^2, nphases) + + # convert to a shunt matrix + terminals, B = calc_shunt(f_terminals, t_terminals, b) + + # if one terminal is ground (0), reduce shunt addmittance matrix + terminals, B = ground_shunt(terminals, B, 0) + + shuntDict["terminals"] = terminals + shuntDict["gs"] = fill(0.0, size(B)...) + shuntDict["bs"] = B + + + used = ["bus1", "phases", "name"] + _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) + + pmd_data["shunt"][shuntDict["id"]] = shuntDict + end + + + for shunt in get(dss_data, "reactor", []) + if !haskey(shunt, "bus2") + _apply_like!(shunt, dss_data, "reactor") + defaults = _apply_ordered_properties(_create_reactor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) + + shuntDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + Zbase = (pmd_data["basekv"] / sqrt(3.0))^2 * nconductors / pmd_data["baseMVA"] # Use single-phase base impedance for each phase + Gcap = Zbase * sum(defaults["kvar"]) / (nconductors * 1e3 * (pmd_data["basekv"] / sqrt(3.0))^2) + + shuntDict["shunt_bus"] = find_bus(name, pmd_data) + shuntDict["name"] = defaults["name"] + shuntDict["gs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) # TODO: + shuntDict["bs"] = _PMs.MultiConductorVector(_parse_array(Gcap, nodes, nconductors)) + shuntDict["status"] = convert(Int, defaults["enabled"]) + shuntDict["index"] = length(pmd_data["shunt"]) + 1 + + shuntDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + shuntDict["source_id"] = "reactor.$(defaults["name"])" + + used = ["bus1", "phases", "name"] + _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) + + push!(pmd_data["shunt"], shuntDict) + end + end +end + + +""" +Given a vector and a list of elements to find, this method will return a list +of the positions of the elements in that vector. +""" +function get_inds(vec::Array{<:Any, 1}, els::Array{<:Any, 1}) + ret = Array{Int, 1}(undef, length(els)) + for (i,f) in enumerate(els) + for (j,l) in enumerate(vec) + if f==l + ret[i] = j + end + end + end + return ret +end + + +""" +Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the +conductors 't_cnds', this method will return a list of conductors 'cnd' and a +matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. +""" +function calc_shunt(f_cnds, t_cnds, y) + cnds = unique([f_cnds..., t_cnds...]) + e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) + Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) + return (cnds, Y) +end + + + +""" +Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this +method will calculate the reduced addmittance matrix if terminal 'ground' is +grounded. +""" +function ground_shunt(cnds, Y, ground) + if ground in cnds + cndsr = setdiff(cnds, ground) + cndsr_inds = get_inds(cnds, cndsr) + Yr = Y[cndsr_inds, cndsr_inds] + return (cndsr, Yr) + else + return cnds, Y + end +end + + +function rm_floating_cnd(cnds, Y, f) + P = setdiff(cnds, f) + f_inds = get_inds(cnds, [f]) + P_inds = get_inds(cnds, P) + Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] + return (P,Yrm) +end + + +""" + _dss2pmd_gen!(pmd_data, dss_data, import_all) + +Adds PowerModels-style generators to `pmd_data` from `dss_data`. +""" +function _dss2pmd_gen!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "gen") + pmd_data["gen"] = [] + end + + # sourcebus generator (created by circuit) + circuit = dss_data["circuit"][1] + defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), "sourcegen"; _to_sym_keys(circuit)...) + + genDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + genDict["gen_bus"] = find_bus("virtual_sourcebus", pmd_data) + genDict["name"] = defaults["name"] + genDict["gen_status"] = convert(Int, defaults["enabled"]) + + # TODO: populate with VSOURCE properties + genDict["pg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) + genDict["qg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) + + genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) + genDict["qmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) + + genDict["pmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) + genDict["pmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) + + genDict["model"] = 2 + genDict["startup"] = 0.0 + genDict["shutdown"] = 0.0 + genDict["ncost"] = 3 + genDict["cost"] = [0.0, 1.0, 0.0] + + genDict["index"] = length(pmd_data["gen"]) + 1 + + genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + genDict["source_id"] = "vsource.$(defaults["name"])" + + used = ["name", "phases", "bus1"] + _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) + + push!(pmd_data["gen"], genDict) + + + for gen in get(dss_data, "generator", []) + _apply_like!(gen, dss_data, "generator") + defaults = _apply_ordered_properties(_create_generator(gen["bus1"], gen["name"]; _to_sym_keys(gen)...), gen) + + genDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + genDict["gen_bus"] = find_bus(name, pmd_data) + genDict["name"] = defaults["name"] + genDict["gen_status"] = convert(Int, defaults["enabled"]) + genDict["pg"] = _PMs.MultiConductorVector(_parse_array(defaults["kw"] / (1e3 * nconductors), nodes, nconductors)) + genDict["qg"] = _PMs.MultiConductorVector(_parse_array(defaults["kvar"] / (1e3 * nconductors), nodes, nconductors)) + genDict["vg"] = _PMs.MultiConductorVector(_parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors)) + + genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(defaults["minkvar"] / (1e3 * nconductors), nodes, nconductors)) + genDict["qmax"] = _PMs.MultiConductorVector(_parse_array(defaults["maxkvar"] / (1e3 * nconductors), nodes, nconductors)) + + genDict["apf"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + + genDict["pmax"] = genDict["pg"] # Assumes generator is at rated power + genDict["pmin"] = 0.0 * genDict["pg"] # 0% of pmax + + genDict["pc1"] = genDict["pmax"] + genDict["pc2"] = genDict["pmin"] + genDict["qc1min"] = genDict["qmin"] + genDict["qc1max"] = genDict["qmax"] + genDict["qc2min"] = genDict["qmin"] + genDict["qc2max"] = genDict["qmax"] + + # For distributed generation ramp rates are not usually an issue + # and they are not supported in OpenDSS + genDict["ramp_agc"] = genDict["pmax"] + + genDict["ramp_q"] = _PMs.MultiConductorVector(_parse_array(max.(abs.(genDict["qmin"].values), abs.(genDict["qmax"].values)), nodes, nconductors)) + genDict["ramp_10"] = genDict["pmax"] + genDict["ramp_30"] = genDict["pmax"] + + genDict["control_model"] = defaults["model"] + + # if PV generator mode convert attached bus to PV bus + if genDict["control_model"] == 3 + pmd_data["bus"][genDict["gen_bus"]]["bus_type"] = 2 + end + + genDict["model"] = 2 + genDict["startup"] = 0.0 + genDict["shutdown"] = 0.0 + genDict["ncost"] = 3 + genDict["cost"] = [0.0, 1.0, 0.0] + + genDict["index"] = length(pmd_data["gen"]) + 1 + + genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + genDict["source_id"] = "generator.$(defaults["name"])" + + used = ["name", "phases", "bus1"] + _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) + + push!(pmd_data["gen"], genDict) + end + + for pv in get(dss_data, "pvsystem", []) + Memento.warn(_LOGGER, "Converting PVSystem \"$(pv["name"])\" into generator with limits determined by OpenDSS property 'kVA'") + + _apply_like!(pv, dss_data, "pvsystem") + defaults = _apply_ordered_properties(_create_pvsystem(pv["bus1"], pv["name"]; _to_sym_keys(pv)...), pv) + + pvDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + pvDict["name"] = defaults["name"] + pvDict["gen_bus"] = find_bus(name, pmd_data) + + pvDict["pg"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + pvDict["qg"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + pvDict["vg"] = _PMs.MultiConductorVector(_parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors)) + + pvDict["pmin"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + pvDict["pmax"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + + pvDict["qmin"] = -_PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + pvDict["qmax"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + + pvDict["gen_status"] = convert(Int, defaults["enabled"]) + + pvDict["model"] = 2 + pvDict["startup"] = 0.0 + pvDict["shutdown"] = 0.0 + pvDict["ncost"] = 3 + pvDict["cost"] = [0.0, 1.0, 0.0] + + pvDict["index"] = length(pmd_data["gen"]) + 1 + + pvDict["active_phases"] = [nodes[n] > 0 ? 1 : 0 for n in 1:nconductors] + pvDict["source_id"] = "pvsystem.$(defaults["name"])" + + used = ["name", "phases", "bus1"] + _PMs._import_remaining!(pvDict, defaults, import_all; exclude=used) + + push!(pmd_data["gen"], pvDict) + end +end + + +""" + _dss2pmd_line!(pmd_data, dss_data, import_all) + +Adds PowerModels-style lines to `pmd_data` from `dss_data`. +""" +function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "branch") + pmd_data["line"] = Dict{String, Any}() + end + + #nconductors = pmd_data["conductors"] + + for line in get(dss_data, "line", []) + _apply_like!(line, dss_data, "line") + + if haskey(line, "linecode") + linecode = deepcopy(_get_linecode(dss_data, get(line, "linecode", ""))) + if haskey(linecode, "like") + linecode = merge(find_component(dss_data, linecode["like"], "linecode"), linecode) + end + + linecode["units"] = get(line, "units", "none") == "none" ? "none" : get(linecode, "units", "none") + linecode["circuit_basefreq"] = pmd_data["basefreq"] + + linecode = _create_linecode(get(linecode, "name", ""); _to_sym_keys(linecode)...) + delete!(linecode, "name") + else + linecode = Dict{String,Any}() + end + + if haskey(line, "basefreq") && line["basefreq"] != pmd_data["basefreq"] + Memento.warn(_LOGGER, "basefreq=$(line["basefreq"]) on line $(line["name"]) does not match circuit basefreq=$(pmd_data["basefreq"])") + line["circuit_basefreq"] = pmd_data["basefreq"] + end + + defaults = _apply_ordered_properties(_create_line(line["bus1"], line["bus2"], line["name"]; _to_sym_keys(line)...), line; linecode=linecode) + + lineDict = Dict{String,Any}() + + lineDict["id"] = defaults["name"] + lineDict["f_bus"] = _parse_busname(defaults["bus1"])[1] + lineDict["t_bus"] = _parse_busname(defaults["bus2"])[1] + + #lineDict["length"] = defaults["length"] + + nphases = defaults["phases"] + lineDict["n_conductors"] = nphases + + rmatrix = defaults["rmatrix"] + xmatrix = defaults["xmatrix"] + cmatrix = defaults["cmatrix"] + + lineDict["f_terminals"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) + lineDict["t_terminals"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) + + lineDict["rs"] = rmatrix * defaults["length"] + lineDict["xs"] = xmatrix * defaults["length"] + + lineDict["g_fr"] = fill(0.0, nphases, nphases) + lineDict["g_to"] = fill(0.0, nphases, nphases) + + lineDict["b_fr"] = (2.0 * pi * pmd_data["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 + lineDict["b_to"] = (2.0 * pi * pmd_data["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 + + #lineDict["c_rating_a"] = fill(defaults["normamps"], nphases) + #lineDict["c_rating_b"] = fill(defaults["emergamps"], nphases) + #lineDict["c_rating_c"] = fill(defaults["emergamps"], nphases) + + lineDict["status"] = convert(Int, defaults["enabled"]) + + #lineDict["source_id"] = "line.$(defaults["name"])" + + #used = ["name", "bus1", "bus2", "rmatrix", "xmatrix"] + #_PMs._import_remaining!(lineDict, defaults, import_all; exclude=used) + + pmd_data["line"][lineDict["id"]] = lineDict + end +end + + +""" + _dss2pmd_transformer!(pmd_data, dss_data, import_all) + +Adds ThreePhasePowerModels-style transformers to `pmd_data` from `dss_data`. +""" +function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "transformer_nw_lossy") + pmd_data["transformer_nw_lossy"] = Dict{String,Any}() + end + + for transformer in get(dss_data, "transformer", []) + _apply_like!(transformer, dss_data, "transformer") + defaults = _apply_ordered_properties(_create_transformer(transformer["name"]; _to_sym_keys(transformer)...), transformer) + + nphases = defaults["phases"] + nrw = defaults["windings"] + + dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") + confs = [dyz_map[x] for x in defaults["conns"]] + + # test if this transformer conforms with limitations + if nphases<3 && "delta" in confs + Memento.error("Transformers with delta windings should have at least 3 phases to be well-defined.") + end + if nrw>3 + # All of the code is compatible with any number of windings, + # except for the parsing of the loss model (the pair-wise reactance) + Memento.error(_LOGGER, "For now parsing of xscarray is not supported. At most 3 windings are allowed, not $nrw.") + end + + transDict = Dict{String, Any}() + transDict["id"] = defaults["name"] + transDict["buses"] = Array{String, 1}(undef, nrw) + transDict["terminals"] = Array{Array{Int, 1}, 1}(undef, nrw) + transDict["tm_fix"] = Array{Array{Real, 1}, 1}(undef, nrw) + transDict["vnom"] = [defaults["kvs"][w] for w in 1:nrw] + transDict["snom"] = [defaults["kvas"][w] for w in 1:nrw] + transDict["terminals"] = Array{Array{Int, 1}, 1}(undef, nrw) + transDict["configuration"] = Array{String, 1}(undef, nrw) + transDict["polarity"] = Array{String, 1}(undef, nrw) + + for w in 1:nrw + transDict["buses"][w] = _parse_busname(defaults["buses"][w])[1] + + conn = dyz_map[defaults["conns"][w]] + transDict["configuration"][w] = conn + + terminals_default = conn=="wye" ? [1:nphases..., 0] : collect(1:nphases) + terminals_w = _get_conductors_ordered_dm(defaults["buses"][w], default=terminals_default) + transDict["terminals"][w] = terminals_w + if 0 in terminals_w + bus = transDict["buses"][w] + if !haskey(pmd_data["bus"][bus], "awaiting_ground") + pmd_data["bus"][bus]["awaiting_ground"] = [] + end + push!(pmd_data["bus"][bus]["awaiting_ground"], transDict) + end + transDict["polarity"][w] = "forward" + transDict["tm_fix"][w] = fill(defaults["taps"][w], nphases) + end + + #transDict["source_id"] = "transformer.$(defaults["name"])" + if !isempty(defaults["bank"]) + transDict["bank"] = defaults["bank"] + end + + # loss model (converted to SI units, referred to secondary) + transDict["rs"] = [defaults["%rs"][w]/100 for w in 1:nrw] + transDict["noloadloss"] = defaults["%noloadloss"]/100 + transDict["imag"] = defaults["%imag"]/100 + if nrw==2 + transDict["xsc"] = [defaults["xhl"]]/100 + elseif nrw==3 + transDict["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 + end + + pmd_data["transformer_nw_lossy"][transDict["id"]] = transDict + end +end + + +""" + _dss2pmd_reactor!(pmd_data, dss_data, import_all) + +Adds PowerModels-style branch components based on DSS reactors to `pmd_data` from `dss_data` +""" +function _dss2pmd_reactor!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "branch") + pmd_data["branch"] = [] + end + + if haskey(dss_data, "reactor") + Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating like line") + for reactor in dss_data["reactor"] + if haskey(reactor, "bus2") + _apply_like!(reactor, dss_data, "reactor") + defaults = _apply_ordered_properties(_create_reactor(reactor["bus1"], reactor["name"], reactor["bus2"]; _to_sym_keys(reactor)...), reactor) + + reactDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + + f_bus, nodes = _parse_busname(defaults["bus1"]) + t_bus = _parse_busname(defaults["bus2"])[1] + + reactDict["name"] = defaults["name"] + reactDict["f_bus"] = find_bus(f_bus, pmd_data) + reactDict["t_bus"] = find_bus(t_bus, pmd_data) + + reactDict["br_r"] = _PMs.MultiConductorMatrix(_parse_matrix(diagm(0 => fill(0.2, nconductors)), nodes, nconductors)) + reactDict["br_x"] = _PMs.MultiConductorMatrix(_parse_matrix(zeros(nconductors, nconductors), nodes, nconductors)) + + reactDict["g_fr"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + reactDict["g_to"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + reactDict["b_fr"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + reactDict["b_to"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + + for key in ["g_fr", "g_to", "b_fr", "b_to"] + reactDict[key] = _PMs.MultiConductorMatrix(LinearAlgebra.diagm(0=>reactDict[key].values)) + end + + reactDict["c_rating_a"] = _PMs.MultiConductorVector(_parse_array(defaults["normamps"], nodes, nconductors)) + reactDict["c_rating_b"] = _PMs.MultiConductorVector(_parse_array(defaults["emergamps"], nodes, nconductors)) + reactDict["c_rating_c"] = _PMs.MultiConductorVector(_parse_array(defaults["emergamps"], nodes, nconductors)) + + reactDict["tap"] = _PMs.MultiConductorVector(_parse_array(1.0, nodes, nconductors, NaN)) + reactDict["shift"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + + reactDict["br_status"] = convert(Int, defaults["enabled"]) + + reactDict["angmin"] = _PMs.MultiConductorVector(_parse_array(-60.0, nodes, nconductors, -60.0)) + reactDict["angmax"] = _PMs.MultiConductorVector(_parse_array( 60.0, nodes, nconductors, 60.0)) + + reactDict["transformer"] = true + + reactDict["index"] = length(pmd_data["branch"]) + 1 + + nodes = .+([_parse_busname(defaults[n])[2] for n in ["bus1", "bus2"]]...) + reactDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + reactDict["source_id"] = "reactor.$(defaults["name"])" + + used = [] + _PMs._import_remaining!(reactDict, defaults, import_all; exclude=used) + + push!(pmd_data["branch"], reactDict) + end + end + end +end + + +""" + _dss2pmd_pvsystem!(pmd_data, dss_data) + +Adds PowerModels-style pvsystems to `pmd_data` from `dss_data`. +""" +function _dss2pmd_pvsystem!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "pvsystem") + pmd_data["pvsystem"] = [] + end + + for pvsystem in get(dss_data, "pvsystem", []) + _apply_like!(pvsystem, dss_data, "pvsystem") + defaults = _apply_ordered_properties(_create_pvsystem(pvsystem["bus1"], pvsystem["name"]; _to_sym_keys(pvsystem)...), pvsystem) + + pvsystemDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + pvsystemDict["name"] = defaults["name"] + pvsystemDict["pv_bus"] = find_bus(name, pmd_data) + pvsystemDict["p"] = _PMs.MultiConductorVector(_parse_array(defaults["kw"] / 1e3, nodes, nconductors)) + pvsystemDict["q"] = _PMs.MultiConductorVector(_parse_array(defaults["kvar"] / 1e3, nodes, nconductors)) + pvsystemDict["status"] = convert(Int, defaults["enabled"]) + + pvsystemDict["index"] = length(pmd_data["pvsystem"]) + 1 + + pvsystemDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + pvsystemDict["source_id"] = "pvsystem.$(defaults["name"])" + + used = ["phases", "bus1", "name"] + _PMs._import_remaining!(pvsystemDict, defaults, import_all; exclude=used) + + push!(pmd_data["pvsystem"], pvsystemDict) + end +end + + +""" + _dss2pmd_storage!(pmd_data, dss_data, import_all) + +Adds PowerModels-style storage to `pmd_data` from `dss_data` +""" +function _dss2pmd_storage!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "storage") + pmd_data["storage"] = [] + end + + for storage in get(dss_data, "storage", []) + _apply_like!(storage, dss_data, "storage") + defaults = _apply_ordered_properties(_create_storage(storage["bus1"], storage["name"]; _to_sym_keys(storage)...), storage) + + storageDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + storageDict["name"] = defaults["name"] + storageDict["storage_bus"] = find_bus(name, pmd_data) + storageDict["energy"] = defaults["kwhstored"] / 1e3 + storageDict["energy_rating"] = defaults["kwhrated"] / 1e3 + storageDict["charge_rating"] = defaults["%charge"] * defaults["kwrated"] / 1e3 / 100.0 + storageDict["discharge_rating"] = defaults["%discharge"] * defaults["kwrated"] / 1e3 / 100.0 + storageDict["charge_efficiency"] = defaults["%effcharge"] / 100.0 + storageDict["discharge_efficiency"] = defaults["%effdischarge"] / 100.0 + storageDict["thermal_rating"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / 1e3 / nconductors, nodes, nconductors)) + storageDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-defaults["kvar"] / 1e3 / nconductors, nodes, nconductors)) + storageDict["qmax"] = _PMs.MultiConductorVector(_parse_array( defaults["kvar"] / 1e3 / nconductors, nodes, nconductors)) + storageDict["r"] = _PMs.MultiConductorVector(_parse_array(defaults["%r"] / 100.0, nodes, nconductors)) + storageDict["x"] = _PMs.MultiConductorVector(_parse_array(defaults["%x"] / 100.0, nodes, nconductors)) + storageDict["p_loss"] = defaults["%idlingkw"] * defaults["kwrated"] / 1e3 + storageDict["q_loss"] = defaults["%idlingkvar"] * defaults["kvar"] / 1e3 + + storageDict["status"] = convert(Int, defaults["enabled"]) + + storageDict["ps"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + storageDict["qs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + + storageDict["index"] = length(pmd_data["storage"]) + 1 + + storageDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + storageDict["source_id"] = "storage.$(defaults["name"])" + + used = ["phases", "bus1", "name"] + _PMs._import_remaining!(storageDict, defaults, import_all; exclude=used) + + push!(pmd_data["storage"], storageDict) + end +end + + +""" + _adjust_sourcegen_bounds!(pmd_data) + +Changes the bounds for the sourcebus generator by checking the emergamps of all +of the lines attached to the sourcebus and taking the sum of non-infinite +values. Defaults to Inf if all emergamps connected to sourcebus are also Inf. +This method was updated to include connected transformers as well. It know +has to occur after the call to InfrastructureModels.arrays_to_dicts, so the code +was adjusted to accomodate that. +""" +function _adjust_sourcegen_bounds!(pmd_data) + emergamps = Array{Float64,1}([0.0]) + sourcebus_n = find_bus(pmd_data["sourcebus"], pmd_data) + for (_,line) in pmd_data["branch"] + if (line["f_bus"] == sourcebus_n || line["t_bus"] == sourcebus_n) && !startswith(line["source_id"], "virtual") + append!(emergamps, get(line, "c_rating_b", get(line, "rate_b", missing)).values) + end + end + + if haskey(pmd_data, "transformer") + for (_,trans) in pmd_data["transformer"] + if trans["f_bus"] == sourcebus_n || trans["t_bus"] == sourcebus_n + append!(emergamps, trans["rate_b"].values) + end + end + end + + bound = sum(emergamps) + + pmd_data["gen"]["1"]["pmin"] = _PMs.MultiConductorVector(fill(-bound, size(pmd_data["gen"]["1"]["pmin"]))) + pmd_data["gen"]["1"]["pmax"] = _PMs.MultiConductorVector(fill( bound, size(pmd_data["gen"]["1"]["pmin"]))) + pmd_data["gen"]["1"]["qmin"] = _PMs.MultiConductorVector(fill(-bound, size(pmd_data["gen"]["1"]["pmin"]))) + pmd_data["gen"]["1"]["qmax"] = _PMs.MultiConductorVector(fill( bound, size(pmd_data["gen"]["1"]["pmin"]))) + + # set current rating of vbranch modelling internal impedance + vbranch = [br for (id, br) in pmd_data["branch"] if br["name"]=="sourcebus_vbranch"][1] + vbranch["rate_a"] = _PMs.MultiConductorVector(fill(bound, length(vbranch["rate_a"]))) +end + + +"This function appends a component to a component dictionary of a pmd data model" +function _push_dict_ret_key!(dict::Dict{String, Any}, v::Dict{String, Any}; assume_no_gaps=false) + if isempty(dict) + k = 1 + elseif assume_no_gaps + k = length(keys(dict))+1 + else + k = maximum([parse(Int, x) for x in keys(dict)])+1 + end + + dict[string(k)] = v + v["index"] = k + return k +end + + +""" + _where_is_comp(data, comp_id) + +Finds existing component of id `comp_id` in array of `data` and returns index. +Assumes all components in `data` are unique. +""" +function _where_is_comp(data::Array, comp_id::AbstractString)::Int + for (i, e) in enumerate(data) + if e["name"] == comp_id + return i + end + end + return 0 +end + + +""" + _correct_duplicate_components!(dss_data) + +Finds duplicate components in `dss_data` and merges up, meaning that older +data (lower indices) is always overwritten by newer data (higher indices). +""" +function _correct_duplicate_components!(dss_data::Dict) + out = Dict{String,Array}() + for (k, v) in dss_data + if !(k in ["options"]) + out[k] = [] + for comp in v + if isa(comp, Dict) + idx = _where_is_comp(out[k], comp["name"]) + if idx > 0 + merge!(out[k][idx], comp) + else + push!(out[k], comp) + end + end + end + end + end + merge!(dss_data, out) +end + + +"Creates a virtual branch between the `virtual_sourcebus` and `sourcebus` with the impedance given by `circuit`" +function _create_sourcebus_vbranch_dm!(pmd_data::Dict, circuit::Dict) + #TODO convert to pu + rs = circuit["rmatrix"] + xs = circuit["xmatrix"] + + id = "_virtual_source_imp" + pmd_data["line"][id] = create_line(id, "_virtual_sourcebus", "sourcebus", size(rs)[1], + rs=rs, + xs=xs + ) + #vbranch = _create_vbranch!(pmd_data, sourcebus, vsourcebus; name="sourcebus_vbranch", br_r=br_r, br_x=br_x) +end + + +"Combines transformers with 'bank' keyword into a single transformer" +function _bank_transformers!(pmd_data::Dict) + transformer_names = Dict(trans["name"] => n for (n, trans) in get(pmd_data, "transformer_comp", Dict())) + bankable_transformers = [trans for trans in values(get(pmd_data, "transformer_comp", Dict())) if haskey(trans, "bank")] + banked_transformers = Dict() + for transformer in bankable_transformers + bank = transformer["bank"] + + if !(bank in keys(banked_transformers)) + n = length(pmd_data["transformer_comp"])+length(banked_transformers)+1 + + banked_transformers[bank] = deepcopy(transformer) + banked_transformers[bank]["name"] = deepcopy(transformer["bank"]) + banked_transformers[bank]["source_id"] = "transformer.$(transformer["bank"])" + banked_transformers[bank]["index"] = n + # set impedances / admittances to zero; only the specified phases should be non-zero + for key in ["rs", "xs", "bsh", "gsh"] + inds = key=="xs" ? keys(banked_transformers[bank][key]) : 1:length(banked_transformers[bank][key]) + for w in inds + banked_transformers[bank][key][w] *= 0 + end + end + delete!(banked_transformers[bank], "bank") + end + + banked_transformer = banked_transformers[bank] + for phase in transformer["active_phases"] + push!(banked_transformer["active_phases"], phase) + for (k, v) in banked_transformer + if isa(v, _PMs.MultiConductorVector) + banked_transformer[k][phase] = deepcopy(transformer[k][phase]) + elseif isa(v, _PMs.MultiConductorMatrix) + banked_transformer[k][phase, :] .= deepcopy(transformer[k][phase, :]) + elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorVector + # most properties are arrays (indexed over the windings) + for w in 1:length(v) + banked_transformer[k][w][phase] = deepcopy(transformer[k][w][phase]) + end + elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorMatrix + # most properties are arrays (indexed over the windings) + for w in 1:length(v) + banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) + end + elseif k=="xs" + # xs is a Dictionary indexed over pairs of windings + for w in keys(v) + banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) + end + end + end + end + end + + for transformer in bankable_transformers + delete!(pmd_data["transformer_comp"], transformer_names[transformer["name"]]) + end + + for transformer in values(banked_transformers) + pmd_data["transformer_comp"]["$(transformer["index"])"] = deepcopy(transformer) + end +end + + +""" + parse_options(options) + +Parses options defined with the `set` command in OpenDSS. +""" +function parse_options(options) + out = Dict{String,Any}() + if haskey(options, "voltagebases") + out["voltagebases"] = _parse_array(Float64, options["voltagebases"]) + end + + if !haskey(options, "defaultbasefreq") + Memento.warn(_LOGGER, "defaultbasefreq is not defined, default for circuit set to 60 Hz") + out["defaultbasefreq"] = 60.0 + else + out["defaultbasefreq"] = parse(Float64, options["defaultbasefreq"]) + end + + return out +end + + +"Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" +function parse_opendss_dm(dss_data::Dict; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict + pmd_data = Dict{String,Any}() + + _correct_duplicate_components!(dss_data) + + parse_dss_with_dtypes!(dss_data, ["line", "linecode", "load", "generator", "capacitor", + "reactor", "circuit", "transformer", "pvsystem", + "storage"]) + + if haskey(dss_data, "options") + condensed_opts = [Dict{String,Any}()] + for opt in dss_data["options"] + merge!(condensed_opts[1], opt) + end + dss_data["options"] = condensed_opts + end + + merge!(pmd_data, parse_options(get(dss_data, "options", [Dict{String,Any}()])[1])) + + #pmd_data["per_unit"] = false + #pmd_data["source_type"] = "dss" + #pmd_data["source_version"] = string(VersionNumber("0")) + + if haskey(dss_data, "circuit") + circuit = dss_data["circuit"][1] + defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), circuit["name"]; _to_sym_keys(circuit)...) + + #pmd_data["name"] = defaults["name"] + pmd_data["basekv"] = defaults["basekv"] + pmd_data["v_var_scalar"] = 1E3 + #pmd_data["baseMVA"] = defaults["basemva"] + pmd_data["basefreq"] = pop!(pmd_data, "defaultbasefreq") + #pmd_data["pu"] = defaults["pu"] + #pmd_data["conductors"] = defaults["phases"] + #pmd_data["sourcebus"] = defaults["bus1"] + else + Memento.error(_LOGGER, "Circuit not defined, not a valid circuit!") + end + + _dss2pmd_bus_dm!(pmd_data, dss_data, import_all, vmin, vmax) + _dss2pmd_line_dm!(pmd_data, dss_data, import_all) + _dss2pmd_transformer_dm!(pmd_data, dss_data, import_all) + + + _dss2pmd_load_dm!(pmd_data, dss_data, import_all) + _dss2pmd_shunt_dm!(pmd_data, dss_data, import_all) + + + _discover_terminals!(pmd_data) + #_dss2pmd_reactor!(pmd_data, dss_data, import_all) + #_dss2pmd_gen!(pmd_data, dss_data, import_all) + #_dss2pmd_pvsystem!(pmd_data, dss_data, import_all) + #_dss2pmd_storage!(pmd_data, dss_data, import_all) + + #pmd_data["dcline"] = [] + #pmd_data["switch"] = [] + + #InfrastructureModels.arrays_to_dicts!(pmd_data) + + if bank_transformers + _bank_transformers!(pmd_data) + end + + _create_sourcebus_vbranch_dm!(pmd_data, defaults) + + #_adjust_sourcegen_bounds!(pmd_data) + + #pmd_data["files"] = dss_data["filename"] + + return pmd_data +end + +function _discover_terminals!(pmd_data) + terminals = Dict{String, Set{Int}}([(bus["id"], Set{Int}()) for (_,bus) in pmd_data["bus"]]) + + for (_,line) in pmd_data["line"] + push!(terminals[line["f_bus"]], line["f_terminals"]...) + push!(terminals[line["t_bus"]], line["t_terminals"]...) + end + + if haskey(pmd_data, "transformer_nw3ph_lossy") + for (_,tr) in pmd_data["transformer_nw3ph_lossy"] + for w in tr["n_windings"] + push!(terminals[buses[w]], terminals[w]...) + end + end + end + + for (id, bus) in pmd_data["bus"] + pmd_data["bus"][id]["terminals"] = sort(collect(terminals[id])) + end + + # identify neutrals and propagate along cables + bus_neutral = _find_neutrals(pmd_data) + + for (id,bus) in pmd_data["bus"] + if haskey(bus, "awaiting_ground") || haskey(bus_neutral, id) + # this bus will need a neutral + if haskey(bus_neutral, id) + neutral = bus_neutral[id] + else + neutral = maximum(bus["terminals"])+1 + push!(bus["terminals"], neutral) + end + bus["neutral"] = neutral + if haskey(bus, "awaiting_ground") + bus["grounded"] = true + bus["rg"] = 0.0 + bus["xg"] = 0.0 + for comp in bus["awaiting_ground"] + if eltype(comp["terminals"])<:Array + for w in 1:length(comp["terminals"]) + if comp["buses"][w]==id + comp["terminals"][w] .+= (comp["terminals"][w].==0)*neutral + end + end + else + comp["terminals"] .+= (comp["terminals"].==0)*neutral + end + end + delete!(bus, "awaiting_ground") + end + end + phases = haskey(bus, "neutral") ? setdiff(bus["terminals"], bus["neutral"]) : bus["terminals"] + bus["phases"] = phases + end +end + +function _find_neutrals(pmd_data) + vertices = [(id, t) for (id, bus) in pmd_data["bus"] for t in bus["terminals"]] + neutrals = [] + edges = Set([((line["f_bus"], line["f_terminals"][c]),(line["t_bus"], line["t_terminals"][c])) for (id, line) in pmd_data["line"] for c in 1:length(line["f_terminals"])]) + + bus_neutrals = [(id,bus["neutral"]) for (id,bus) in pmd_data["bus"] if haskey(bus, "neutral")] + trans_neutrals = [] + for (_, tr) in pmd_data["transformer_nw_lossy"] + for w in 1:length(tr["terminals"]) + if tr["configuration"][w] == "wye" + push!(trans_neutrals, (tr["buses"][w], tr["terminals"][w][end])) + end + end + end + load_neutrals = [(load["bus"],load["terminals"][end]) for (_,load) in pmd_data["load"] if load["configuration"]=="wye"] + neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) + neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) + stack = deepcopy(neutrals) + while !isempty(stack) + vertex = pop!(stack) + candidates_t = [((f,t), t) for (f,t) in edges if f==vertex] + candidates_f = [((f,t), f) for (f,t) in edges if t==vertex] + for (edge,next) in [candidates_t..., candidates_f...] + delete!(edges, edge) + push!(stack, next) + push!(neutrals, next) + end + end + bus_neutral = Dict{String, Int}() + for (bus,t) in neutrals + bus_neutral[bus] = t + end + return bus_neutral +end + + +"Parses a DSS file into a PowerModels usable format" +function parse_opendss_dm(io::IOStream; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict + dss_data = parse_dss(io) + + return parse_opendss_dm(dss_data; import_all=import_all) +end + + +""" + _get_conductors_ordered(busname; neutral=true) + +Returns an ordered list of defined conductors. If ground=false, will omit any `0` +""" +function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_length=true)::Array + parts = split(busname, '.'; limit=2) + ret = [] + if length(parts)==2 + conds_str = split(parts[2], '.') + ret = [parse(Int, i) for i in conds_str] + else + return default + end + if check_length && (default)!=length(ret) + Memento.error("An incorrect number of nodes was specified; |$(parts[2])|!=$(length(default)).") + end + return ret +end From de9694fa3fdb2e3424120264d72c5a0d623dc0ca Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Wed, 12 Feb 2020 16:48:37 -0700 Subject: [PATCH 002/224] first pass --- src/io/data_model_components.jl | 494 ++++++++++++++++---------------- src/io/data_model_test.jl | 110 ++++--- 2 files changed, 294 insertions(+), 310 deletions(-) diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl index 727e55fec..b453b5ac5 100644 --- a/src/io/data_model_components.jl +++ b/src/io/data_model_components.jl @@ -27,6 +27,7 @@ function component_dict_from_list!(list) return dict end +REQUIRED_FIELDS = Dict{Symbol, Any}() DTYPES = Dict{Symbol, Any}() CHECKS = Dict{Symbol, Any}() @@ -41,10 +42,27 @@ function check_dtypes(dict, dtypes, comp_type, id) end end + +function add!(data_model, comp_type, comp_dict) + @assert(haskey(comp_dict, "id"), "The component does not have an id defined.") + id = comp_dict["id"] + if !haskey(data_model, comp_type) + data_model[comp_type] = Dict{String, Any}() + else + @assert(!haskey(data_model[comp_type], id), "There is already a $comp_type with id $id.") + end + data_model[comp_type][id] = comp_dict +end + function check_data_model(data) - for component in [:bus, :linecode, :line, :load, :generator, :transformer_nph3w_lossy, :transformer_2w_ideal, :capacitor, :shunt] + for component in [:bus, :linecode, :line, :load, :generator, :transformer_nw, :capacitor, :shunt] if haskey(data, string(component)) for (id, comp_dict) in data[string(component)] + if haskey(REQUIRED_FIELDS, component) + for field in REQUIRED_FIELDS[component] + @assert(haskey(comp_dict, string(field)), "The property \'$field\' is missing for $component $id.") + end + end if haskey(DTYPES, component) check_dtypes(comp_dict, DTYPES[component], component, id) end @@ -59,7 +77,6 @@ end function create_data_model(;kwargs...) data_model = Dict{String, Any}() - data_model["quantity_scalars"] = Dict{String, Any}() add_kwarg!(data_model, kwargs, :v_var_scalar, 1E3) return data_model end @@ -86,12 +103,66 @@ DTYPES[:boundary] = Dict() DTYPES[:meter] = Dict() +function _check_same_size(data, keys; context=missing) + size_comp = size(data[string(keys[1])]) + for key in keys[2:end] + @assert(all(size(data[string(key)]).==size_comp), "$context: the property $key should have the same size as $(keys[1]).") + end +end -# Linecode + +function _check_has_size(data, keys, size_comp; context=missing, allow_missing=true) + for key in keys + if haskey(data, key) || !allow_missing + @assert(all(size(data[string(key)]).==size_comp), "$context: the property $key should have as size $size_comp.") + end + end +end + +function _check_connectivity(data, comp_dict; context=missing) + + if haskey(comp_dict, "f_bus") + # two-port element + _check_bus_and_terminals(data, comp_dict["f_bus"], comp_dict["f_connections"], context) + _check_bus_and_terminals(data, comp_dict["t_bus"], comp_dict["t_connections"], context) + elseif haskey(comp_dict, "bus") + if isa(comp_dict, Vector) + for i in in length(comp_dict["bus"]) + _check_bus_and_terminals(data, comp_dict["bus"][i], comp_dict["connections"][i], context) + end + else + _check_bus_and_terminals(data, comp_dict["bus"], comp_dict["connections"], context) + end + end +end + + +function _check_bus_and_terminals(data, bus_id, terminals, context=missing) + @assert(haskey(data, "bus") && haskey(data["bus"], bus_id), "$context: the bus $bus_id is not defined.") + bus = data["bus"][bus_id] + for t in terminals + @assert(t in bus["terminals"], "$context: bus $(bus["id"]) does not have terminal \'$t\'.") + end +end + + +function _check_has_keys(comp_dict, keys; context=missing) + for key in keys + @assert(haskey(comp_dict, key), "$context: the property $key is missing.") + end +end + +function _check_configuration_infer_dim(comp_dict; context=missing) + conf = comp_dict["configuration"] + @assert(conf in ["delta", "wye"], "$context: the configuration should be \'delta\' or \'wye\', not \'$conf\'.") + return conf=="wye" ? length(comp_dict["connections"])-1 : length(comp_dict["connections"]) +end + + +# linecode DTYPES[:linecode] = Dict( :id => AbstractString, - :n_conductors => Int, :rs => Array{<:Real, 2}, :xs => Array{<:Real, 2}, :g_fr => Array{<:Real, 2}, @@ -100,15 +171,21 @@ DTYPES[:linecode] = Dict( :b_to => Array{<:Real, 2}, ) +REQUIRED_FIELDS[:linecode] = keys(DTYPES[:linecode]) -CHECKS[:linecode] = function check_line(data, linecode) +CHECKS[:linecode] = function check_linecode(data, linecode) + _check_same_size(linecode, [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to]) end - -function create_linecode(id, n_conductors; kwargs...) +function create_linecode(id; kwargs...) linecode = Dict{String,Any}() linecode["id"] = id - linecode["n_conductors"] = n_conductors + n_conductors = 0 + for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] + if haskey(kwargs, key) + n_conductors = size(kwargs[key])[1] + end + end add_kwarg!(linecode, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) add_kwarg!(linecode, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) add_kwarg!(linecode, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) @@ -118,34 +195,28 @@ function create_linecode(id, n_conductors; kwargs...) return linecode end - # line DTYPES[:line] = Dict( :id => AbstractString, :status => Int, - :f_bus => String, - :t_bus => String, - :n_conductors => Int, - :f_terminals => Vector{<:Int}, - :t_terminals => Vector{<:Int}, - :linecode => Int, + :f_bus => AbstractString, + :t_bus => AbstractString, + :f_connections => Vector{<:Int}, + :t_connections => Vector{<:Int}, + :linecode => AbstractString, :length => Real, - :rs =>Matrix{<:Real}, - :xs =>Matrix{<:Real}, - :g_fr => Matrix{<:Real}, - :b_fr =>Matrix{<:Real}, - :g_to =>Matrix{<:Real}, - :b_to =>Matrix{<:Real}, - :b_to =>Matrix{<:Real}, :c_rating =>Vector{<:Real}, + :s_rating =>Vector{<:Real}, ) +REQUIRED_FIELDS[:line] = [:id, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length] CHECKS[:line] = function check_line(data, line) i = line["id"] - if haskey(line, "linecode") + # for now, always require a lione code + if true || haskey(line, "linecode") # line is defined with a linecode @assert(haskey(line, "length"), "line $i: a line defined through a linecode, should have a length property.") @@ -157,9 +228,10 @@ CHECKS[:line] = function check_line(data, line) @assert(!haskey(line, key), "line $i: a line with a linecode, should not specify $key; this is already done by the linecode.") end - N = linecode["n_conductors"] - @assert(length(line["f_terminals"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") - @assert(length(line["t_terminals"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") + N = size(linecode["rs"])[1] + @show(N) + @assert(length(line["f_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") + @assert(length(line["t_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") else # normal line @assert(!haskey(line, "length"), "line $i: length only makes sense for linees defined through linecodes.") @@ -167,133 +239,55 @@ CHECKS[:line] = function check_line(data, line) @assert(haskey(line, key), "line $i: a line without linecode, should specify $key.") end end -end - -function create_line(id, f_bus, t_bus, n_conductors; kwargs...) - line = Dict{String,Any}() - line["id"] = id - line["f_bus"] = f_bus - line["t_bus"] = t_bus - line["n_conductors"] = n_conductors - add_kwarg!(line, kwargs, :f_terminals, collect(1:n_conductors)) - add_kwarg!(line, kwargs, :t_terminals, collect(1:n_conductors)) - add_kwarg!(line, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) - return line + _check_connectivity(data, line, context="line $(line["id"])") end -function create_line_with_linecode(id, f_bus, t_bus, linecode, length; kwargs...) +function create_line(id, f_bus, t_bus, linecode, length; kwargs...) line = Dict{String,Any}() line["id"] = id line["f_bus"] = f_bus line["t_bus"] = t_bus - line["linecode"] = linecode["id"] + line["linecode"] = linecode line["length"] = length - n_conductors = linecode["n_conductors"] - add_kwarg!(line, kwargs, :f_terminals, collect(1:n_conductors)) - add_kwarg!(line, kwargs, :t_terminals, collect(1:n_conductors)) + add_kwarg!(line, kwargs, :f_connections, collect(1:4)) + add_kwarg!(line, kwargs, :t_connections, collect(1:4)) return line end - -# Voltage zone - -DTYPES[:voltage_zone] = Dict( - :id => AbstractString, - :buses => Array{String, 1}, - :vnom => Real, - :vm_ln_min => Array{<:Real, 1}, - :vm_ln_max => Array{<:Real, 1}, - :vm_lg_min => Array{<:Real, 1}, - :vm_lg_max => Array{<:Real, 1}, - :vm_ng_min => Array{<:Real, 1}, - :vm_ng_max => Array{<:Real, 1}, - :vm_ll_min => Array{<:Real, 1}, - :vm_ll_max => Array{<:Real, 1}, -) - - -CHECKS[:voltage_zone] = function check_voltage_zone(data, bus) - i = bus["id"] - @assert(haskey(bus, "vnom"), "Voltage zone $i: a voltage zone should specify a vnom.") -end - - -function create_voltage_zone(id, vnom; kwargs...) - voltage_zone = Dict{String, Any}( - "id" => id, - "vnom" => vnom, - "buses" => Array{String, 1}(), - ) -end - # Bus DTYPES[:bus] = Dict( :id => AbstractString, - :bus => String, - :bus_type => Int, - :terminals => Array{<:Int}, + :terminals => Array{<:Any}, :phases => Array{<:Int}, :neutral => Union{Int, Missing}, - :grounded => Bool, - :voltage_zone => String, - :rg => Real, - :xg => Real, - :vnom => Real, - :vm_ln_min => Array{<:Real, 1}, - :vm_ln_max => Array{<:Real, 1}, - :vm_lg_min => Array{<:Real, 1}, - :vm_lg_max => Array{<:Real, 1}, - :vm_ng_min => Array{<:Real, 1}, - :vm_ng_max => Array{<:Real, 1}, - :vm_ll_min => Array{<:Real, 1}, - :vm_ll_max => Array{<:Real, 1}, + :grounded => Array{<:Any}, + :rg => Array{<:Real}, + :xg => Array{<:Real}, + :vm_pn_min => Real, + :vm_pn_max => Real, + :vm_pp_min => Real, + :vm_pp_max => Real, + :vm_min => Array{<:Real, 1}, + :vm_max => Array{<:Real, 1}, :vm_fix => Array{<:Real, 1}, :va_fix => Array{<:Real, 1}, ) +REQUIRED_FIELDS[:bus] = [:id, :terminals, :grounded, :rg, :xg] CHECKS[:bus] = function check_bus(data, bus) - i = bus["id"] - if !haskey(bus, "neutral") - @assert(!haskey(bus, "vm_ln_max"), "Bus $i does not have a neutral, and therefore vm_ln_max bounds do not make sense.") - @assert(!haskey(bus, "vm_ln_min"), "Bus $i does not have a neutral, and therefore vm_ln_min bounds do not make sense.") - @assert(!haskey(bus, "vm_ng_min"), "Bus $i does not have a neutral, and therefore a vm_ng_min bound does not make sense.") - @assert(!haskey(bus, "vm_ng_max"), "Bus $i does not have a neutral, and therefore a vm_ng_max bound does not make sense.") - end - nph = length(bus["phases"]) - for key in ["vm_ln_max", "vm_ln_min", "vm_lg_min", "vm_lg_max"] - if haskey(bus, key) - @assert(length(bus[key])==nph, "For bus $i, the length of $key should match the number of phases.") - end - end - if nph==1 - @assert(!haskey(bus, "vm_ll_min"), "Bus $i only has a single phase, so line-to-line bounds do not make sense.") - @assert(!haskey(bus, "vm_ll_max"), "Bus $i only has a single phase, so line-to-line bounds do not make sense.") - else - els = nph==2 ? 1 : nph - for key in ["vm_ll_min", "vm_ll_max"] - if haskey(bus, key) - @assert(length(bus["vm_ll_min"])==els, "For bus $i, the $key bound should have only $els elements.") - end - end - end - if haskey(bus, "voltage_zone") - zone_id = bus["voltage_zone"] - @assert(haskey(data["voltage_zone"], zone_id), "Bus $i: the voltage zone $zone_id is not defined.") - voltage_zone = data["voltage_zone"][zone_id] - for key in ["vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] - if haskey(voltage_zone, key) - @assert(!haskey(bus, key), "Bus $i: the property $key is already specified in the voltage_zone; this cannot be overwritten.") - end - end + id = bus["id"] + + _check_same_size(bus, ["grounded", "rg", "xg"], context="bus $id") + + N = length(bus["terminals"]) + _check_has_size(bus, ["vm_max", "vm_min", "vm", "va"], N, context="bus $id") + + if haskey(bus, "neutral") + assert(haskey(bus, "phases"), "bus $id: has a neutral, but no phases.") end end @@ -301,60 +295,51 @@ function create_bus(id; kwargs...) bus = Dict{String,Any}() bus["id"] = id add_kwarg!(bus, kwargs, :terminals, collect(1:4)) - add_kwarg!(bus, kwargs, :phases, collect(1:3)) - add_kwarg!(bus, kwargs, :neutral, 4) - add_kwarg!(bus, kwargs, :grounded, false) - copy_kwargs_to_dict_if_present!(bus, kwargs, [:vm_nom_ln, :vm_ln_min, :vm_ln_max, :vm_ng_min, :vm_ng_max, :vm_lg_min, :vm_lg_max, :vm_ll_min, :vm_ll_max, :vm_fix, :va_fix, :rg, :xg]) - return bus -end - -function create_bus_in_zone(id, voltage_zone, data_model; kwargs...) - bus = Dict{String,Any}() - bus["id"] = id - bus["voltage_zone"] = voltage_zone - if !(id in data_model["voltage_zone"][voltage_zone]["buses"]) - push!(data_model["voltage_zone"][voltage_zone]["buses"], id) - end - add_kwarg!(bus, kwargs, :terminals, collect(1:4)) - add_kwarg!(bus, kwargs, :phases, collect(1:3)) - add_kwarg!(bus, kwargs, :neutral, 4) - add_kwarg!(bus, kwargs, :grounded, false) - copy_kwargs_to_dict_if_present!(bus, kwargs, [:vm_nom_ln, :vm_ln_min, :vm_ln_max, :vm_ng_min, :vm_ng_max, :vm_lg_min, :vm_lg_max, :vm_ll_min, :vm_ll_max, :vm_fix, :va_fix, :rg, :xg]) + add_kwarg!(bus, kwargs, :grounded, []) + add_kwarg!(bus, kwargs, :rg, Array{Float64, 1}()) + add_kwarg!(bus, kwargs, :xg, Array{Float64, 1}()) + copy_kwargs_to_dict_if_present!(bus, kwargs, [:phases, :neutral, :vm_max, :vm_min, :vm_pp_max, :vm_pp_min, :vm_pn_max, :vm_pn_min, :vm_fix, :va_fix]) return bus end - # Load DTYPES[:load] = Dict( :id => AbstractString, :bus => String, - :terminals => Array{<:Int}, + :connections => Array{<:Int}, :configuration => String, + :model => String, :pd => Array{<:Real, 1}, :qd => Array{<:Real, 1}, - :model => String, + :pd_ref => Array{<:Real, 1}, + :qd_ref => Array{<:Real, 1}, :vnom => Array{<:Real, 1}, :alpha => Array{<:Real, 1}, :beta => Array{<:Real, 1}, ) +REQUIRED_FIELDS[:load] = [:id, :bus, :connections, :configuration] CHECKS[:load] = function check_load(data, load) - N = length(load["pd"]) - i = load["id"] - - @assert(load["configuration"] in ["wye", "delta"]) - - if load["configuration"]=="delta" - @assert(N>=3, "Load $i: delta-connected loads should have at least dimension 3, not $N.") + id = load["id"] + + N = _check_configuration_infer_dim(load; context="load $id") + + model = load["model"] + @assert(model in ["constant_power", "constant_impedance", "constant_current", "exponential"]) + if model=="constant_power" + _check_has_keys(load, ["pd", "qd"], context="load $id, $model:") + _check_has_size(load, ["pd", "qd"], N, context="load $id, $model:") + elseif model=="exponential" + _check_has_keys(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], context="load $id, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], N, context="load $id, $model:") + else + _check_has_keys(load, ["pd_ref", "qd_ref", "vnom"], context="load $id, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vnom"], N, context="load $id, $model:") end - for key in [:qd, :vm_nom, :alpha, :beta] - if haskey(load, key) - @assert(length(load[key])==N, "Load $i: $key should have the same length as pd.") - end - end + _check_connectivity(data, load; context="load $id") end @@ -364,49 +349,51 @@ function create_load(id, bus; kwargs...) load["bus"] = bus add_kwarg!(load, kwargs, :configuration, "wye") - add_kwarg!(load, kwargs, :terminals, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) - add_kwarg!(load, kwargs, :pd, fill(0.0, 3)) - add_kwarg!(load, kwargs, :qd, fill(0.0, 3)) + add_kwarg!(load, kwargs, :connections, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) add_kwarg!(load, kwargs, :model, "constant_power") - copy_kwargs_to_dict_if_present!(load, kwargs, [:vm_nom, :alpha, :beta]) + if load["model"]=="constant_power" + add_kwarg!(load, kwargs, :pd, fill(0.0, 3)) + add_kwarg!(load, kwargs, :qd, fill(0.0, 3)) + else + add_kwarg!(load, kwargs, :pd_ref, fill(0.0, 3)) + add_kwarg!(load, kwargs, :qd_ref, fill(0.0, 3)) + copy_kwargs_to_dict_if_present!(load, kwargs, [:pd_ref, :qd_ref, :vnom, :alpha, :beta]) + end return load end -# generatorerator - +# generator DTYPES[:generator] = Dict( :id => AbstractString, :bus => String, - :terminals => Array{<:Int}, + :connections => Array{<:Int}, :configuration => String, - :pd_set => Array{<:Real, 1}, - :qd_set => Array{<:Real, 1}, - :pd_min => Array{<:Real, 1}, - :pd_max => Array{<:Real, 1}, + :pd => Array{<:Real, 1}, + :qd => Array{<:Real, 1}, :pd_min => Array{<:Real, 1}, :pd_max => Array{<:Real, 1}, + :qd_min => Array{<:Real, 1}, + :qd_max => Array{<:Real, 1}, ) +REQUIRED_FIELDS[:generator] = [:id, :bus, :connections] CHECKS[:generator] = function check_generator(data, generator) - N = length(generator["pd_set"]) - i = generator["id"] + id = generator["id"] - @assert(generator["configuration"] in ["wye", "delta"]) + N = _check_configuration_infer_dim(generator; context="generator $id") + _check_has_size(generator, ["pd", "qd", "pd_min", "pd_max", "qd_min", "qd_max"], N, context="generator $id") - if load["configuration"]=="delta" - @assert(N>=3, "generatorerator $i: delta-connected loads should have at least dimension 3, not $N.") - end + _check_connectivity(data, generator; context="generator $id") end - function create_generator(id, bus; kwargs...) generator = Dict{String,Any}() generator["id"] = id generator["bus"] = bus add_kwarg!(generator, kwargs, :configuration, "wye") - add_kwarg!(generator, kwargs, :terminals, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) + add_kwarg!(generator, kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) copy_kwargs_to_dict_if_present!(generator, kwargs, [:pd_min, :pd_max, :qd_min, :qd_max]) return generator end @@ -415,50 +402,61 @@ end # Transformer, n-windings three-phase lossy -DTYPES[:transformer_nw_lossy] = Dict( +DTYPES[:transformer_nw] = Dict( :id => AbstractString, - :n_windings=>Int, - :n_phases=>Int, - :buses => Array{Int, 1}, - :terminals => Array{Array{Int, 1}, 1}, + :bus => Array{<:AbstractString, 1}, + :connections => Array{<:Array{<:Any, 1}, 1}, :vnom => Array{<:Real, 1}, :snom => Array{<:Real, 1}, :configuration => Array{String, 1}, :polarity => Array{Bool, 1}, :xsc => Array{<:Real, 1}, :rs => Array{<:Real, 1}, - :loadloss => Real, + :noloadloss => Real, :imag => Real, :tm_fix => Array{Array{Bool, 1}, 1}, - :tm_set => Array{<:Array{<:Real, 1}, 1}, + :tm => Array{<:Array{<:Real, 1}, 1}, :tm_min => Array{<:Array{<:Real, 1}, 1}, :tm_max => Array{<:Array{<:Real, 1}, 1}, :tm_step => Array{<:Array{<:Real, 1}, 1}, ) -CHECKS[:transformer_nw_lossy] = function check_transformer_nw_lossy(data, trans) - @assert(all([x in ["wye", "delta"] for x in trans["configuration"]])) - n_windings = trans["n_windings"] - @assert(length(trans["xsc"])==n_windings^2-n_windings) +CHECKS[:transformer_nw] = function check_transformer_nw(data, trans) + id = trans["id"] + nrw = length(trans["bus"]) + _check_has_size(trans, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_min", "tm_max", "tm_step"], nrw, context="trans $id") + @assert(length(trans["xsc"])==(nrw^2-nrw)/2) + + nphs = [] + for w in 1:nrw + @assert(trans["configuration"][w] in ["wye", "delta"]) + conf = trans["configuration"][w] + conns = trans["connections"][w] + nph = conf=="wye" ? length(conns)-1 : length(conns) + @assert(all(nph.==nphs), "transformer $id: winding $w has a different number of phases than the previous ones.") + push!(nphs, nph) + #TODO check length other properties + end end -function create_transformer_nw_lossy(id, n_windings, buses, terminals, vnom, snom; kwargs...) +function create_transformer_nw(id, n_windings, bus, connections, vnom, snom; kwargs...) trans = Dict{String,Any}() trans["id"] = id - trans["buses"] = buses - trans["n_windings"] = n_windings - trans["terminals"] = terminals + trans["bus"] = bus + trans["connections"] = connections trans["vnom"] = vnom trans["snom"] = snom + + n_windings = length(connections) add_kwarg!(trans, kwargs, :configuration, fill("wye", n_windings)) add_kwarg!(trans, kwargs, :polarity, fill(true, n_windings)) add_kwarg!(trans, kwargs, :rs, fill(0.0, n_windings)) add_kwarg!(trans, kwargs, :xsc, fill(0.0, n_windings^2-n_windings)) - add_kwarg!(trans, kwargs, :loadloss, 0.0) + add_kwarg!(trans, kwargs, :noloadloss, 0.0) add_kwarg!(trans, kwargs, :imag, 0.0) - add_kwarg!(trans, kwargs, :tm_set, fill(fill(1.0, 3), n_windings)) + add_kwarg!(trans, kwargs, :tm, fill(fill(1.0, 3), n_windings)) add_kwarg!(trans, kwargs, :tm_min, fill(fill(0.9, 3), n_windings)) add_kwarg!(trans, kwargs, :tm_max, fill(fill(1.1, 3), n_windings)) add_kwarg!(trans, kwargs, :tm_step, fill(fill(1/32, 3), n_windings)) @@ -467,45 +465,45 @@ function create_transformer_nw_lossy(id, n_windings, buses, terminals, vnom, sno return trans end - -# Transformer, two-winding three-phase - -DTYPES[:transformer_2w_ideal] = Dict( - :id => AbstractString, - :f_bus => String, - :t_bus => String, - :configuration => String, - :f_terminals => Array{Int, 1}, - :t_terminals => Array{Int, 1}, - :tm_nom => Real, - :tm_set => Real, - :tm_min => Real, - :tm_max => Real, - :tm_step => Real, - :tm_fix => Real, -) - - -CHECKS[:transformer_2w_ideal] = function check_transformer_2w_ideal(data, trans) -end - - -function create_transformer_2w_ideal(id, f_bus, t_bus, tm_nom; kwargs...) - trans = Dict{String,Any}() - trans["id"] = id - trans["f_bus"] = f_bus - trans["t_bus"] = t_bus - trans["tm_nom"] = tm_nom - add_kwarg!(trans, kwargs, :configuration, "wye") - add_kwarg!(trans, kwargs, :f_terminals, trans["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) - add_kwarg!(trans, kwargs, :t_terminals, [1, 2, 3, 4]) - add_kwarg!(trans, kwargs, :tm_set, 1.0) - add_kwarg!(trans, kwargs, :tm_min, 0.9) - add_kwarg!(trans, kwargs, :tm_max, 1.1) - add_kwarg!(trans, kwargs, :tm_step, 1/32) - add_kwarg!(trans, kwargs, :tm_fix, true) - return trans -end +# +# # Transformer, two-winding three-phase +# +# DTYPES[:transformer_2w_ideal] = Dict( +# :id => AbstractString, +# :f_bus => String, +# :t_bus => String, +# :configuration => String, +# :f_terminals => Array{Int, 1}, +# :t_terminals => Array{Int, 1}, +# :tm_nom => Real, +# :tm_set => Real, +# :tm_min => Real, +# :tm_max => Real, +# :tm_step => Real, +# :tm_fix => Real, +# ) +# +# +# CHECKS[:transformer_2w_ideal] = function check_transformer_2w_ideal(data, trans) +# end +# +# +# function create_transformer_2w_ideal(id, f_bus, t_bus, tm_nom; kwargs...) +# trans = Dict{String,Any}() +# trans["id"] = id +# trans["f_bus"] = f_bus +# trans["t_bus"] = t_bus +# trans["tm_nom"] = tm_nom +# add_kwarg!(trans, kwargs, :configuration, "wye") +# add_kwarg!(trans, kwargs, :f_terminals, trans["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) +# add_kwarg!(trans, kwargs, :t_terminals, [1, 2, 3, 4]) +# add_kwarg!(trans, kwargs, :tm_set, 1.0) +# add_kwarg!(trans, kwargs, :tm_min, 0.9) +# add_kwarg!(trans, kwargs, :tm_max, 1.1) +# add_kwarg!(trans, kwargs, :tm_step, 1/32) +# add_kwarg!(trans, kwargs, :tm_fix, true) +# return trans +# end # Capacitor @@ -513,36 +511,36 @@ end DTYPES[:capacitor] = Dict( :id => AbstractString, :bus => String, - :terminals => Array{Int, 1}, + :connections => Array{Int, 1}, :configuration => String, :qd_ref => Array{<:Real, 1}, - :vm_nom => Real, + :vnom => Real, ) CHECKS[:capacitor] = function check_capacitor(data, cap) - i = cap["id"] - N = length(cap["terminals"]) + id = cap["id"] + N = length(cap["connections"]) config = cap["configuration"] if config=="wye" - @assert(length(cap["qd_ref"])==N-1, "Capacitor $i: qd_ref should have $(N-1) elements.") + @assert(length(cap["qd_ref"])==N-1, "Capacitor $id: qd_ref should have $(N-1) elements.") else - @assert(length(cap["qd_ref"])==N, "Capacitor $i: qd_ref should have $N elements.") + @assert(length(cap["qd_ref"])==N, "Capacitor $id: qd_ref should have $N elements.") end @assert(config in ["delta", "wye", "wye-grounded", "wye-floating"]) if config=="delta" - @assert(N>=3, "Capacitor $i: delta-connected capacitors should have at least 3 elements.") + @assert(N>=3, "Capacitor $id: delta-connected capacitors should have at least 3 elements.") end end -function create_capacitor(id, bus, vm_nom; kwargs...) +function create_capacitor(id, bus, vnom; kwargs...) cap = Dict{String,Any}() cap["id"] = id cap["bus"] = bus - cap["vm_nom"] = vm_nom + cap["vnom"] = vnom add_kwarg!(cap, kwargs, :configuration, "wye") - add_kwarg!(cap, kwargs, :terminals, collect(1:4)) + add_kwarg!(cap, kwargs, :connections, collect(1:4)) add_kwarg!(cap, kwargs, :qd_ref, fill(0.0, 3)) return cap end diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl index e10e1d3ad..5195525ee 100644 --- a/src/io/data_model_test.jl +++ b/src/io/data_model_test.jl @@ -2,87 +2,73 @@ BASE_DIR = "/Users/sclaeys/code/PowerModelsDistribution.jl/src/io" include("$BASE_DIR/data_model_util.jl") include("$BASE_DIR/data_model_components.jl") include("$BASE_DIR/data_model_mapping.jl") -#include("$BASE_DIR/data_model_pu.jl") +include("$BASE_DIR/data_model_pu.jl") function make_test_data_model() data_model = create_data_model() - data_model["linecode"] = component_dict_from_list!([ - create_linecode("1", 6, rs=ones(6, 6), xs=ones(6, 6)), - create_linecode("2", 4, rs=ones(4, 4), xs=ones(4, 4)), - create_linecode("3", 3, rs=ones(3, 3), xs=ones(3, 3)), - create_linecode("4", 2, rs=ones(2, 2), xs=ones(2, 2)), - ]) + add!(data_model, "linecode", create_linecode("6_conds", rs=ones(6, 6), xs=ones(6, 6))) + add!(data_model, "linecode", create_linecode("4_conds", rs=ones(4, 4), xs=ones(4, 4))) + add!(data_model, "linecode", create_linecode("3_conds", rs=ones(3, 3), xs=ones(3, 3))) + add!(data_model, "linecode", create_linecode("2_conds", rs=ones(2, 2), xs=ones(2, 2))) - data_model["line"] = component_dict_from_list!([ - # 3 phase + 3 neutral conductors - create_line("1", "1", "2", 6; t_terminals=[1,2,3,4,4,4]), - # 3 phase + 1 neutral conductors - create_line("2", "2", "3", 4), - # 3 phase conductors - create_line_with_linecode("3", "3", "4", data_model["linecode"]["4"], 1.3), - # 2 phase + 1 neutral conductors - create_line("4", "3", "5", 3, f_terminals=[1,3,4], t_terminals=[1,3,4]), - # 1 phase + 1 neutral conductors - create_line_with_linecode("5", "3", "6", data_model["linecode"]["4"], 1.7, f_terminals=[2,4], t_terminals=[2,4]), - # 2 phase conductors - create_line("6", "3", "7", 2, f_terminals=[1,2], t_terminals=[1,2]), - ]) + # 3 phase + 3 neutral conductors + add!(data_model, "line", create_line("1", "1", "2", "6_conds", 1; f_connections=[1,2,3,4,4,4], t_connections=collect(1:6))) + add!(data_model, "line", create_line("2", "2", "3", "6_conds", 1; f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4])) + # 3 phase + 1 neutral conductors + add!(data_model, "line", create_line("3", "3", "4", "4_conds", 1.2)) + # 3 phase conductors + add!(data_model, "line", create_line("4", "4", "5", "3_conds", 1.3; f_connections=collect(1:3), t_connections=collect(1:3))) + # 2 phase + 1 neutral conductors + add!(data_model, "line", create_line("5", "4", "6", "3_conds", 1.3, f_connections=[1,3,4], t_connections=[1,3,4])) + # 1 phase + 1 neutral conductors + add!(data_model, "line", create_line("6", "4", "7", "2_conds", 1.7, f_connections=[2,4], t_connections=[2,4])) + # 2 phase conductors + add!(data_model, "line", create_line("7", "4", "8", "2_conds", 1.3, f_connections=[1,2], t_connections=[1,2])) - data_model["voltage_zone"] = component_dict_from_list!([ - create_voltage_zone("1", 0.400) - ]) - data_model["bus"] = component_dict_from_list!([ - create_bus_in_zone("1", "1", data_model, grounded=true, rg=0.1, xg=0.1, vm_fix=[fill(230, 3)..., 0], va_fix=[0, -pi*2/3, pi*2/3, 0]), - create_bus_in_zone("2", "1", data_model), - create_bus_in_zone("3", "1", data_model), - create_bus_in_zone("4", "1", data_model, terminals=[1,2,3], neutral=missing, vm_ll_min=fill(0.9, 3), vm_ll_max=fill(1.1, 3)), - create_bus_in_zone("5", "1", data_model, terminals=[1,3,4], phases=[1,3], vm_ln_min=fill(0.9, 2), vm_ln_max=fill(1.1, 2)), - create_bus_in_zone("6", "1", data_model, terminals=[2,4], phases=[2], vm_ln_min=fill(0.9, 1), vm_ln_max=fill(1.1, 1)), - create_bus_in_zone("7", "1", data_model, terminals=[1,2], phases=[1,2], neutral=missing, vm_ll_min=fill(0.9, 1), vm_ll_max=fill(1.1, 1)), - create_bus_in_zone("8", "1", data_model, vm_ln_min=fill(230*0.9, 1), vm_ln_max=fill(1.1, 1)), - create_bus_in_zone("9", "1", data_model, terminals=[1,2,3], neutral=missing, vm_ll_min=fill(0.9, 1), vm_ll_max=fill(1.1, 1)), - ]) + add!(data_model, "bus", create_bus("1", terminals=collect(1:4))) + add!(data_model, "bus", create_bus("2", terminals=collect(1:6))) + add!(data_model, "bus", create_bus("3", terminals=collect(1:4))) + add!(data_model, "bus", create_bus("4")) + add!(data_model, "bus", create_bus("5", terminals=collect(1:3))) + add!(data_model, "bus", create_bus("6", terminals=[1,3,4])) + add!(data_model, "bus", create_bus("7", terminals=[2,4])) + add!(data_model, "bus", create_bus("8", terminals=[1,2])) - data_model["load"] = component_dict_from_list!([ - create_load("1", "6", terminals=[2,4], pd=[1.0], qd=[1.0]), - create_load("2", "7", terminals=[1,2], pd=[1.0], qd=[1.0], model="constant_current", vm_nom=[230*sqrt(3)]), - create_load("3", "5", terminals=[1,4], pd=[1.0], qd=[1.0], model="constant_impedance", vm_nom=[230]), - create_load("4", "5", terminals=[3,4], pd=[1.0], qd=[1.0], model="exponential", vm_nom=[230], alpha=[1.2], beta=[1.5]), - create_load("5", "3", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3)), - create_load("6", "3", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vm_nom=fill(230, 3)), - create_load("7", "3", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vm_nom=fill(230, 3)), - create_load("8", "3", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vm_nom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]), - create_load("9", "4", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3)), - ]) + # + add!(data_model, "load", create_load("1", "7", connections=[2,4], pd=[1.0], qd=[1.0])) + add!(data_model, "load", create_load("2", "8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)])) + add!(data_model, "load", create_load("3", "6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230])) + add!(data_model, "load", create_load("4", "6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5])) + add!(data_model, "load", create_load("5", "4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3))) + add!(data_model, "load", create_load("6", "4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3))) + add!(data_model, "load", create_load("7", "4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3))) + add!(data_model, "load", create_load("8", "4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5])) + add!(data_model, "load", create_load("9", "5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3))) - data_model["generator"] = component_dict_from_list!([ - create_generator("1", "1", configuration="wye"), - ]) + add!(data_model, "generator", create_generator("1", "1", configuration="wye")) - data_model["transformer_nw3ph_lossy"] = component_dict_from_list!([ - create_transformer_nw3ph_lossy("1", 3, ["4", "8", "9"], [[1,2,3], [1,2,3,4], [1,2,3]], + add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["4", "8", "9"], [[1,2,3], [1,2,3,4], [1,2,3]], [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], configuration=["delta", "wye", "delta"], xsc=[0.0, 0.0, 0.0], rs=[0.0, 0.0, 1.0], loadloss=0.05, imag=0.05, - ), - ]) - - data_model["capacitor"] = component_dict_from_list!([ - create_capacitor("cap_3ph", "3", 0.230*sqrt(3), qd_ref=[1, 2, 3]), - create_capacitor("cap_3ph_delta", "4", 0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", terminals=[1,2,3]), - create_capacitor("cap_2ph_yg", "6", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,2], configuration="wye-grounded"), - create_capacitor("cap_2ph_yfl", "6", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,2], configuration="wye-floating"), - create_capacitor("cap_2ph_y", "5", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,3,4]), - ]) + )) + + add!(data_model, "capacitor", create_capacitor("cap_3ph", "3", 0.230*sqrt(3), qd_ref=[1, 2, 3])) + add!(data_model, "capacitor", create_capacitor("cap_3ph_delta", "4", 0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", terminals=[1,2,3])) + add!(data_model, "capacitor", create_capacitor("cap_2ph_yg", "6", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,2], configuration="wye-grounded")) + add!(data_model, "capacitor", create_capacitor("cap_2ph_yfl", "6", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,2], configuration="wye-floating")) + add!(data_model, "capacitor", create_capacitor("cap_2ph_y", "5", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,3,4])) return data_model end -#dm = make_test_data_model() +@time dm = make_test_data_model() +@time check_data_model(dm) +dm #index_data_model(dm, components=["capacitor"]) From c5fed31fb6d55c0bf01bd5f5b912cd383abf3577 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Thu, 13 Feb 2020 12:15:08 -0700 Subject: [PATCH 003/224] mapping updated --- src/io/data_model_components.jl | 26 +++- src/io/data_model_mapping.jl | 215 ++++++++++++++++++-------------- src/io/data_model_test.jl | 45 +++++-- src/io/data_model_util.jl | 27 ++-- 4 files changed, 193 insertions(+), 120 deletions(-) diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl index b453b5ac5..b45d1e4f3 100644 --- a/src/io/data_model_components.jl +++ b/src/io/data_model_components.jl @@ -126,8 +126,8 @@ function _check_connectivity(data, comp_dict; context=missing) _check_bus_and_terminals(data, comp_dict["f_bus"], comp_dict["f_connections"], context) _check_bus_and_terminals(data, comp_dict["t_bus"], comp_dict["t_connections"], context) elseif haskey(comp_dict, "bus") - if isa(comp_dict, Vector) - for i in in length(comp_dict["bus"]) + if isa(comp_dict["bus"], Vector) + for i in 1:length(comp_dict["bus"]) _check_bus_and_terminals(data, comp_dict["bus"][i], comp_dict["connections"][i], context) end else @@ -210,7 +210,7 @@ DTYPES[:line] = Dict( :s_rating =>Vector{<:Real}, ) -REQUIRED_FIELDS[:line] = [:id, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length] +REQUIRED_FIELDS[:line] = [:id, :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length] CHECKS[:line] = function check_line(data, line) i = line["id"] @@ -251,6 +251,8 @@ function create_line(id, f_bus, t_bus, linecode, length; kwargs...) line["t_bus"] = t_bus line["linecode"] = linecode line["length"] = length + + add_kwarg!(line, kwargs, :status, 1) add_kwarg!(line, kwargs, :f_connections, collect(1:4)) add_kwarg!(line, kwargs, :t_connections, collect(1:4)) return line @@ -260,6 +262,7 @@ end DTYPES[:bus] = Dict( :id => AbstractString, + :status => Int, :terminals => Array{<:Any}, :phases => Array{<:Int}, :neutral => Union{Int, Missing}, @@ -276,7 +279,7 @@ DTYPES[:bus] = Dict( :va_fix => Array{<:Real, 1}, ) -REQUIRED_FIELDS[:bus] = [:id, :terminals, :grounded, :rg, :xg] +REQUIRED_FIELDS[:bus] = [:id, :status, :terminals, :grounded, :rg, :xg] CHECKS[:bus] = function check_bus(data, bus) id = bus["id"] @@ -294,6 +297,8 @@ end function create_bus(id; kwargs...) bus = Dict{String,Any}() bus["id"] = id + + add_kwarg!(bus, kwargs, :status, 1) add_kwarg!(bus, kwargs, :terminals, collect(1:4)) add_kwarg!(bus, kwargs, :grounded, []) add_kwarg!(bus, kwargs, :rg, Array{Float64, 1}()) @@ -306,6 +311,7 @@ end DTYPES[:load] = Dict( :id => AbstractString, + :status => Int, :bus => String, :connections => Array{<:Int}, :configuration => String, @@ -319,7 +325,7 @@ DTYPES[:load] = Dict( :beta => Array{<:Real, 1}, ) -REQUIRED_FIELDS[:load] = [:id, :bus, :connections, :configuration] +REQUIRED_FIELDS[:load] = [:id, :status, :bus, :connections, :configuration] CHECKS[:load] = function check_load(data, load) id = load["id"] @@ -348,6 +354,7 @@ function create_load(id, bus; kwargs...) load["id"] = id load["bus"] = bus + add_kwarg!(load, kwargs, :status, 1) add_kwarg!(load, kwargs, :configuration, "wye") add_kwarg!(load, kwargs, :connections, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) add_kwarg!(load, kwargs, :model, "constant_power") @@ -366,6 +373,7 @@ end DTYPES[:generator] = Dict( :id => AbstractString, + :status => Int, :bus => String, :connections => Array{<:Int}, :configuration => String, @@ -377,7 +385,7 @@ DTYPES[:generator] = Dict( :qd_max => Array{<:Real, 1}, ) -REQUIRED_FIELDS[:generator] = [:id, :bus, :connections] +REQUIRED_FIELDS[:generator] = [:id, :status, :bus, :connections] CHECKS[:generator] = function check_generator(data, generator) id = generator["id"] @@ -392,6 +400,7 @@ function create_generator(id, bus; kwargs...) generator = Dict{String,Any}() generator["id"] = id generator["bus"] = bus + add_kwarg!(generator, kwargs, :status, 1) add_kwarg!(generator, kwargs, :configuration, "wye") add_kwarg!(generator, kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) copy_kwargs_to_dict_if_present!(generator, kwargs, [:pd_min, :pd_max, :qd_min, :qd_max]) @@ -438,6 +447,8 @@ CHECKS[:transformer_nw] = function check_transformer_nw(data, trans) push!(nphs, nph) #TODO check length other properties end + + _check_connectivity(data, trans; context="transformer_nw $id") end @@ -550,6 +561,7 @@ end DTYPES[:shunt] = Dict( :id => AbstractString, + :status => 1, :bus => String, :terminals => Array{Int, 1}, :g_sh => Array{<:Real, 2}, @@ -566,6 +578,8 @@ function create_shunt(id, bus, terminals; kwargs...) shunt["id"] = id shunt["bus"] = bus shunt["terminals"] = terminals + + add_kwarg!(shunt, kwargs, :status, 1) add_kwarg!(shunt, kwargs, :g_sh, fill(0.0, length(terminals), length(terminals))) add_kwarg!(shunt, kwargs, :b_sh, fill(0.0, length(terminals), length(terminals))) return shunt diff --git a/src/io/data_model_mapping.jl b/src/io/data_model_mapping.jl index 040720918..2c3f451cc 100644 --- a/src/io/data_model_mapping.jl +++ b/src/io/data_model_mapping.jl @@ -2,39 +2,23 @@ import LinearAlgebra function map_down_data_model(data_model_user) - data_model_base = deepcopy(data_model_user) - data_model_base["source_data_model"] = data_model_user + data_model = deepcopy(data_model_user) + data_model = data_model_user - _expand_linecode!(data_model_base) - _expand_voltage_zone!(data_model_base) - _load_to_shunt!(data_model_base) - _capacitor_to_shunt!(data_model_base) - _decompose_transformer_nw_lossy!(data_model_base) + !haskey(data_model, "mappings") - # add low level component types if not present yet - for comp_type in ["load", "generator", "bus", "line", "shunt", "transformer_2w_ideal"] - if !haskey(data_model_base, comp_type) - data_model_base[comp_type] = Dict{String, Any}() - end - end - return data_model_base -end + _expand_linecode!(data_model) + add_mappings!(data_model, "load_to_shunt", _load_to_shunt!(data_model)) + add_mappings!(data_model, "capacitor_to_shunt", _capacitor_to_shunt!(data_model)) + add_mappings!(data_model, "decompose_transformer_nw", _decompose_transformer_nw!(data_model)) - -function _expand_voltage_zone!(data_model) - # expand line codes - for (id, bus) in data_model["bus"] - if haskey(bus, "voltage_zone") - voltage_zone = data_model["voltage_zone"][bus["voltage_zone"]] - bus["vnom"] = voltage_zone["vnom"] - for key in ["vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] - if haskey(voltage_zone, key) - bus[key] = voltage_zone[key] - end - end - delete!(bus, "voltage_zone") + # add low level component types if not present yet + for comp_type in ["load", "generator", "bus", "line", "shunt", "transformer_2wa", "storage", "switch"] + if !haskey(data_model, comp_type) + data_model[comp_type] = Dict{String, Any}() end end + return data_model end @@ -50,15 +34,17 @@ function _expand_linecode!(data_model) delete!(line, "length") end end + delete!(data_model, "linecode") end function _load_to_shunt!(data_model) + mappings = [] if haskey(data_model, "load") for (id, load) in data_model["load"] if load["model"]=="constant_impedance" - b = load["qd"]./load["vm_nom"].^2*1E3 - g = load["pd"]./load["vm_nom"].^2*1E3 + b = load["qd_ref"]./load["vnom"].^2*1E3 + g = load["pd_ref"]./load["vnom"].^2*1E3 y = b.+im*g N = length(b) @@ -74,24 +60,28 @@ function _load_to_shunt!(data_model) Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) end - shunt = create_shunt(NaN, load["bus"], load["terminals"], b_sh=imag.(Y), g_sh=real.(Y)) + shunt = create_shunt(NaN, load["bus"], load["connections"], b_sh=imag.(Y), g_sh=real.(Y)) add_component!(data_model, "shunt", shunt) delete_component!(data_model, "load", load) - add_mapping!(data_model, "load_to_shunt", Dict( - "load" => data_model["source_data_model"]["load"][id], + push!(mappings, Dict( + "load" => load, "shunt" => shunt, )) end end end + + return mappings end function _capacitor_to_shunt!(data_model) + mappings = [] + if haskey(data_model, "capacitor") for (id, cap) in data_model["capacitor"] - b = cap["qd_ref"]./cap["vm_nom"]^2*1E3 + b = cap["qd_ref"]./cap["vnom"]^2*1E3 N = length(b) if cap["configuration"]=="delta" @@ -114,16 +104,18 @@ function _capacitor_to_shunt!(data_model) B = vcat(B_fr, -ones(N)'*B_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) end - shunt = create_shunt(NaN, cap["bus"], cap["terminals"], b_sh=B) + shunt = create_shunt(NaN, cap["bus"], cap["connections"], b_sh=B) add_component!(data_model, "shunt", shunt) delete_component!(data_model, "capacitor", cap) - add_mapping!(data_model, "capacitor_to_shunt", Dict( - "capacitor" => data_model["source_data_model"]["capacitor"][id], + push!(mappings, Dict( + "capacitor" => cap, "shunt" => shunt, )) end end + + return mappings end @@ -135,67 +127,73 @@ end Replaces complex transformers with a composition of ideal transformers and lines which model losses. New buses (virtual, no physical meaning) are added. """ -function _decompose_transformer_nw_lossy!(data_model) - for (tr_id, trans) in data_model["transformer_nw_lossy"] - - vnom = trans["vnom"]*data_model["v_var_scalar"] - snom = trans["snom"]*data_model["v_var_scalar"] - - nrw = length(trans["buses"]) - - # calculate zbase in which the data is specified, and convert to SI - zbase = (vnom.^2)./snom - # x_sc is specified with respect to first winding - x_sc = trans["xsc"].*zbase[1] - # rs is specified with respect to each winding - r_s = trans["rs"].*zbase - - g_sh = (trans["noloadloss"]*snom[1]/3)/vnom[1]^2 - b_sh = (trans["imag"]*snom[1]/3)/vnom[1]^2 - - # data is measured externally, but we now refer it to the internal side - ratios = vnom/1E3 - x_sc = x_sc./ratios[1]^2 - r_s = r_s./ratios.^2 - g_sh = g_sh*ratios[1]^2 - b_sh = b_sh*ratios[1]^2 - - # convert x_sc from list of upper triangle elements to an explicit dict - y_sh = g_sh + im*b_sh - z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) - - vbuses, vlines, trans_t_bus_w = _build_loss_model!(data_model, r_s, z_sc, y_sh) - - trans_w = Array{Dict, 1}(undef, nrw) - for w in 1:nrw - # 2-WINDING TRANSFORMER - # make virtual bus and mark it for reduction - tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] - trans_w[w] = create_transformer_2w_ideal(NaN, - trans["buses"][w], trans_t_bus_w[w], tm_nom, - f_terminals = trans["terminals"][w], - t_terminals = collect(1:4), - configuration = trans["configuration"][w], - polarity = trans["polarity"][w], - #tm_set = trans["tm_set"][w], - tm_fix = trans["tm_fix"][w], - #tm_max = trans["tm_max"][w], - #tm_min = trans["tm_min"][w], - #tm_step = trans["tm_step"][w], - ) - - add_component!(data_model, "transformer_2w_ideal", trans_w[w]) - end +function _decompose_transformer_nw!(data_model) + mappings = [] + + if haskey(data_model, "transformer_nw") + for (tr_id, trans) in data_model["transformer_nw"] + + vnom = trans["vnom"]*data_model["v_var_scalar"] + snom = trans["snom"]*data_model["v_var_scalar"] + + nrw = length(trans["bus"]) + + # calculate zbase in which the data is specified, and convert to SI + zbase = (vnom.^2)./snom + # x_sc is specified with respect to first winding + x_sc = trans["xsc"].*zbase[1] + # rs is specified with respect to each winding + r_s = trans["rs"].*zbase + + g_sh = (trans["noloadloss"]*snom[1]/3)/vnom[1]^2 + b_sh = (trans["imag"]*snom[1]/3)/vnom[1]^2 + + # data is measured externally, but we now refer it to the internal side + ratios = vnom/1E3 + x_sc = x_sc./ratios[1]^2 + r_s = r_s./ratios.^2 + g_sh = g_sh*ratios[1]^2 + b_sh = b_sh*ratios[1]^2 + + # convert x_sc from list of upper triangle elements to an explicit dict + y_sh = g_sh + im*b_sh + z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) + + vbuses, vlines, trans_t_bus_w = _build_loss_model!(data_model, r_s, z_sc, y_sh) + + trans_w = Array{Dict, 1}(undef, nrw) + for w in 1:nrw + # 2-WINDING TRANSFORMER + # make virtual bus and mark it for reduction + tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] + trans_w[w] = create_transformer_2w_ideal(NaN, + trans["bus"][w], trans_t_bus_w[w], tm_nom, + f_terminals = trans["connections"][w], + t_terminals = collect(1:4), + configuration = trans["configuration"][w], + polarity = trans["polarity"][w], + #tm_set = trans["tm_set"][w], + tm_fix = trans["tm_fix"][w], + #tm_max = trans["tm_max"][w], + #tm_min = trans["tm_min"][w], + #tm_step = trans["tm_step"][w], + ) + + add_component!(data_model, "transformer_2wa", trans_w[w]) + end - delete_component!(data_model, "transformer_nw_lossy", trans) + delete_component!(data_model, "transformer_nw", trans) - add_mapping!(data_model, "transformer_decomposition", Dict( - "trans"=>data_model["source_data_model"]["transformer_nw_lossy"][tr_id], - "trans_w"=>trans_w, - "vlines"=>vlines, - "vbuses"=>vbuses, - )) + push!(mappings, Dict( + "trans"=>trans, + "trans_w"=>trans_w, + "vlines"=>vlines, + "vbuses"=>vbuses, + )) + end end + + return mappings end @@ -339,3 +337,30 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) return bus_ids, line_ids, [bus_ids[bus] for bus in tr_t_bus] end + +function make_compatible_v8!(data_model) + for (_, bus) in data_model["bus"] + bus["bus_type"] = 1 + bus["status"] = 1 + end + + for (_, load) in data_model["load"] + load["load_bus"] = load["bus"] + end + + data_model["gen"] = data_model["generator"] + data_model["branch"] = data_model["line"] + + for (_, gen) in data_model["gen"] + gen["gen_status"] = gen["status"] + gen["gen_bus"] = gen["bus"] + end + + for (_, br) in data_model["branch"] + br["br_status"] = br["status"] + end + + data_model["dc_line"] = Dict() + + return data_model +end diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl index 5195525ee..4c3052acc 100644 --- a/src/io/data_model_test.jl +++ b/src/io/data_model_test.jl @@ -36,6 +36,8 @@ function make_test_data_model() add!(data_model, "bus", create_bus("6", terminals=[1,3,4])) add!(data_model, "bus", create_bus("7", terminals=[2,4])) add!(data_model, "bus", create_bus("8", terminals=[1,2])) + add!(data_model, "bus", create_bus("9", terminals=[1,2,3,4])) + add!(data_model, "bus", create_bus("10", terminals=[1,2,3])) # add!(data_model, "load", create_load("1", "7", connections=[2,4], pd=[1.0], qd=[1.0])) @@ -50,7 +52,7 @@ function make_test_data_model() add!(data_model, "generator", create_generator("1", "1", configuration="wye")) - add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["4", "8", "9"], [[1,2,3], [1,2,3,4], [1,2,3]], + add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["5", "9", "10"], [[1,2,3], [1,2,3,4], [1,2,3]], [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], configuration=["delta", "wye", "delta"], xsc=[0.0, 0.0, 0.0], @@ -58,17 +60,40 @@ function make_test_data_model() loadloss=0.05, imag=0.05, )) - + add!(data_model, "capacitor", create_capacitor("cap_3ph", "3", 0.230*sqrt(3), qd_ref=[1, 2, 3])) - add!(data_model, "capacitor", create_capacitor("cap_3ph_delta", "4", 0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", terminals=[1,2,3])) - add!(data_model, "capacitor", create_capacitor("cap_2ph_yg", "6", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,2], configuration="wye-grounded")) - add!(data_model, "capacitor", create_capacitor("cap_2ph_yfl", "6", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,2], configuration="wye-floating")) - add!(data_model, "capacitor", create_capacitor("cap_2ph_y", "5", 0.230*sqrt(3), qd_ref=[1, 2], terminals=[1,3,4])) + add!(data_model, "capacitor", create_capacitor("cap_3ph_delta", "4", 0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3])) + add!(data_model, "capacitor", create_capacitor("cap_2ph_yg", "6", 0.230*sqrt(3), qd_ref=[1, 2], connections=[1,2], configuration="wye-grounded")) + add!(data_model, "capacitor", create_capacitor("cap_2ph_yfl", "6", 0.230*sqrt(3), qd_ref=[1, 2], connections=[1,2], configuration="wye-floating")) + add!(data_model, "capacitor", create_capacitor("cap_2ph_y", "5", 0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4])) + + return data_model +end + + +function make_3wire_data_model() + + data_model = create_data_model() + + add!(data_model, "linecode", create_linecode("3_conds", rs=ones(3, 3), xs=ones(3, 3))) + + # 3 phase conductors + add!(data_model, "line", create_line("1", "1", "2", "3_conds", 1.3; f_connections=collect(1:3), t_connections=collect(1:3))) + + add!(data_model, "bus", create_bus("1", terminals=collect(1:4))) + add!(data_model, "bus", create_bus("2", terminals=collect(1:4))) + + # + add!(data_model, "load", create_load("1", "2", connections=collect(1:4), pd=[1.0, 2.0, 3.0], qd=[1.0, 2.0, 3.0])) + + add!(data_model, "generator", create_generator("1", "1", connections=collect(1:4))) return data_model end -@time dm = make_test_data_model() -@time check_data_model(dm) -dm -#index_data_model(dm, components=["capacitor"]) + +dm_hl = make_3wire_data_model() +check_data_model(dm_hl) +## +dm = map_down_data_model(dm_hl) +data_model_index!(dm) diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl index ad14773b8..bcd5a7206 100644 --- a/src/io/data_model_util.jl +++ b/src/io/data_model_util.jl @@ -32,20 +32,24 @@ function delete_component!(data_model, comp_type, comp) end end -function add_mapping!(data_model::Dict{String, Any}, mapping_type::String, mapping::Dict{String, <:Any}) - if !haskey(data_model, "mapping") - data_model["mapping"] = Dict{String, Any}() +function add_mappings!(data_model::Dict{String, Any}, mapping_type::String, mappings::Vector) + if !haskey(data_model, "mappings") + data_model["mappings"] = [] end - if !haskey(data_model["mapping"], mapping_type) - data_model["mapping"][mapping_type] = Array{Dict{String, Any}, 1}() - end - - push!(data_model["mapping"][mapping_type], mapping) + append!(data_model["mappings"], [(mapping_type, mapping) for mapping in mappings]) end -function data_model_index!(data_model; components=["line", "bus", "shunt", "transformer_2w_ideal", "load", "generator"]) +function data_model_index!(data_model; components=["line", "shunt", "generator", "load", "transformer_2wa"]) + bus_id2ind = Dict() + + for (i, id) in enumerate(keys(data_model["bus"])) + data_model["bus"][id]["index"] = i + bus_id2ind[id] = i + end + data_model["bus"] = Dict(string(bus_id2ind[id])=>bus for (id, bus) in data_model["bus"]) + for comp_type in components comp_dict = Dict{String, Any}() for (i,(id,comp)) in enumerate(data_model[comp_type]) @@ -53,6 +57,11 @@ function data_model_index!(data_model; components=["line", "bus", "shunt", "tran comp["index"] = i comp["id"] = id comp_dict["$i"] = comp + for bus_key in ["f_bus", "t_bus", "bus"] + if haskey(comp, bus_key) + comp[bus_key] = bus_id2ind[comp[bus_key]] + end + end end data_model[comp_type] = comp_dict end From fada2c55c1e6849178221377bec36ea362b283a6 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 14 Feb 2020 11:23:09 -0700 Subject: [PATCH 004/224] pmd trans update --- src/core/constraint_template.jl | 24 +++++++++--------------- src/core/data.jl | 28 ++++++++++------------------ 2 files changed, 19 insertions(+), 33 deletions(-) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 463566c52..7457527ad 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -133,30 +133,24 @@ function constraint_mc_trans(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw t_bus = trans["t_bus"] f_idx = (i, f_bus, t_bus) t_idx = (i, t_bus, f_bus) - f_type = trans["config_fr"]["type"] - t_type = trans["config_to"]["type"] - f_cnd = trans["config_fr"]["cnd"] - t_cnd = trans["config_to"]["cnd"] + config = trans["configuration"] + type = trans["configuration"] + f_cnd = trans["f_connections"][1:3] + t_cnd = trans["t_connections"][1:3] tm_set = trans["tm"] - tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : trans["fixed"] + tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : trans["tm_fix"] tm_scale = calculate_tm_scale(trans, _PMs.ref(pm, nw, :bus, f_bus), _PMs.ref(pm, nw, :bus, t_bus)) #TODO change data model # there is redundancy in specifying polarity seperately on from and to side - f_pol = trans["config_fr"]["polarity"]=='+' ? 1 : -1 - t_pol = trans["config_to"]["polarity"]=='+' ? 1 : -1 - pol = f_pol*t_pol + pol = trans["polarity"] - if f_type=="wye" && t_type=="wye" + if config=="wye" constraint_mc_trans_yy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) - elseif f_type=="delta" && t_type=="wye" + elseif config=="delta" constraint_mc_trans_dy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) - elseif f_type=="wye" && t_type=="delta" - constraint_mc_trans_dy(pm, nw, i, t_bus, f_bus, t_idx, f_idx, t_cnd, f_cnd, pol, tm_set, tm_fixed, (tm_scale)^-1) - elseif f_type=="delta" && t_type=="delta" - Memento.error(_LOGGER, "Dd transformers are not supported at the low-level data format. This can be cast as a combo of two dy transformers.") end - if f_type=="zig-zag" || t_type=="zig-zag" + if type=="zig-zag" Memento.error(_LOGGER, "Zig-zag not yet supported.") end end diff --git a/src/core/data.jl b/src/core/data.jl index dfec1dfd0..0792804a8 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -113,28 +113,20 @@ end "Calculates the tap scale factor for the non-dimensionalized equations." function calculate_tm_scale(trans::Dict{String,Any}, bus_fr::Dict{String,Any}, bus_to::Dict{String,Any}) - f_vnom = trans["config_fr"]["vm_nom"] - t_vnom = trans["config_to"]["vm_nom"] - f_vbase = bus_fr["base_kv"] - t_vbase = bus_to["base_kv"] - f_type = trans["config_fr"]["type"] - t_type = trans["config_to"]["type"] - - tm_scale = (f_vnom/t_vnom)*(t_vbase/f_vbase) - if f_type == "delta" + tm_nom = trans["tm_nom"] + f_vbase = bus_fr["vbase"] + t_vbase = bus_to["vbase"] + config = trans["configuration"] + + tm_scale = tm_nom*(t_vbase/f_vbase) + if config == "delta" + #TODO is this still needed? tm_scale *= sqrt(3) - end - if t_type == "delta" - tm_scale *= 1/sqrt(3) - end - if f_type == "zig-zag" - Memento.error(_LOGGER, "Zig-zag not yet supported.") - end - if t_type == "zig-zag" + elseif config == "zig-zag" Memento.error(_LOGGER, "Zig-zag not yet supported.") end - return tm_scale + return tm_nom end From bed7a68ccc4d57011c4973afc3729989d34f6fa1 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 14 Feb 2020 11:23:32 -0700 Subject: [PATCH 005/224] solution infrastructure --- src/PowerModelsDistribution.jl | 13 ++-- src/{io => core}/data_model_mapping.jl | 99 +++++++++++++++++++------- src/{io => core}/data_model_pu.jl | 21 +++++- src/io/data_model_components.jl | 45 ++++++------ src/io/data_model_test.jl | 80 +++++++++++++++++---- src/io/data_model_util.jl | 12 ++-- 6 files changed, 196 insertions(+), 74 deletions(-) rename src/{io => core}/data_model_mapping.jl (78%) rename src/{io => core}/data_model_pu.jl (92%) diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index b3336aa30..3b67ddae2 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -45,13 +45,12 @@ module PowerModelsDistribution include("io/dss_structs.jl") include("io/opendss.jl") - include("io/common_dm.jl") - include("io/opendss_dm.jl") - include("io/data_model_components.jl") - include("io/data_model_mapping.jl") - include("io/data_model_pu.jl") - include("io/data_model_test.jl") - include("io/data_model_util.jl") + #include("io/common_dm.jl") + #include("io/opendss_dm.jl") + # include("io/data_model_components.jl") + # include("core/data_model_mapping.jl") + # include("core/data_model_pu.jl") + # include("io/data_model_util.jl") include("prob/mld.jl") include("prob/opf.jl") diff --git a/src/io/data_model_mapping.jl b/src/core/data_model_mapping.jl similarity index 78% rename from src/io/data_model_mapping.jl rename to src/core/data_model_mapping.jl index 2c3f451cc..5cdc2ede6 100644 --- a/src/io/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -1,5 +1,6 @@ import LinearAlgebra +# MAP DATA MODEL DOWN function map_down_data_model(data_model_user) data_model = deepcopy(data_model_user) @@ -166,20 +167,22 @@ function _decompose_transformer_nw!(data_model) # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] - trans_w[w] = create_transformer_2w_ideal(NaN, - trans["bus"][w], trans_t_bus_w[w], tm_nom, - f_terminals = trans["connections"][w], - t_terminals = collect(1:4), - configuration = trans["configuration"][w], - polarity = trans["polarity"][w], - #tm_set = trans["tm_set"][w], - tm_fix = trans["tm_fix"][w], - #tm_max = trans["tm_max"][w], - #tm_min = trans["tm_min"][w], - #tm_step = trans["tm_step"][w], + trans_w[w] = Dict( + "f_bus" => trans["bus"][w], + "t_bus" => trans_t_bus_w[w], + "tm_nom" => tm_nom, + "f_connections" => trans["connections"][w], + "t_connections" => collect(1:4), + "configuration" => trans["configuration"][w], + "polarity" => trans["polarity"][w], + "tm" => trans["tm"][w], + "tm_fix" => trans["tm_fix"][w], + "tm_max" => trans["tm_max"][w], + "tm_min" => trans["tm_min"][w], + "tm_step" => trans["tm_step"][w], ) - add_component!(data_model, "transformer_2wa", trans_w[w]) + add_virtual_get_id!(data_model, "transformer_2wa", trans_w[w]) end delete_component!(data_model, "transformer_nw", trans) @@ -306,7 +309,7 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) bus_ids = Dict() for bus in buses - bus_ids[bus] = add_component!(data_model, "bus", create_bus("")) + bus_ids[bus] = add_virtual_get_id!(data_model, "bus", create_bus("")) end line_ids = Dict() for (l,(i,j)) in lines @@ -322,16 +325,17 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) b_fr = imag(shunts[j]) delete!(shunts, j) end - line_ids[l] = add_component!(data_model, "line", create_line("", - bus_ids[i], bus_ids[j], n_phases, - f_terminals = collect(1:n_phases), - t_terminals = collect(1:n_phases), - rs = LinearAlgebra.diagm(0=>fill(real(z[l]), n_phases)), - xs = LinearAlgebra.diagm(0=>fill(imag(z[l]), n_phases)), - g_fr = LinearAlgebra.diagm(0=>fill(g_fr, n_phases)), - b_fr = LinearAlgebra.diagm(0=>fill(b_fr, n_phases)), - g_to = LinearAlgebra.diagm(0=>fill(g_to, n_phases)), - b_to = LinearAlgebra.diagm(0=>fill(b_to, n_phases)), + line_ids[l] = add_virtual_get_id!(data_model, "line", Dict( + "status"=>1, + "f_bus"=>bus_ids[i], "t_bus"=>bus_ids[j], + "f_connections"=>collect(1:n_phases), + "t_connections"=>collect(1:n_phases), + "rs"=>LinearAlgebra.diagm(0=>fill(real(z[l]), n_phases)), + "xs"=>LinearAlgebra.diagm(0=>fill(imag(z[l]), n_phases)), + "g_fr"=>LinearAlgebra.diagm(0=>fill(g_fr, n_phases)), + "b_fr"=>LinearAlgebra.diagm(0=>fill(b_fr, n_phases)), + "g_to"=>LinearAlgebra.diagm(0=>fill(g_to, n_phases)), + "b_to"=>LinearAlgebra.diagm(0=>fill(b_to, n_phases)), )) end @@ -339,9 +343,13 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) end function make_compatible_v8!(data_model) + data_model["conductors"] = 3 for (_, bus) in data_model["bus"] bus["bus_type"] = 1 bus["status"] = 1 + bus["bus_i"] = bus["index"] + bus["vmin"] = fill(0.9, 3) + bus["vmax"] = fill(1.1, 3) end for (_, load) in data_model["load"] @@ -349,18 +357,57 @@ function make_compatible_v8!(data_model) end data_model["gen"] = data_model["generator"] - data_model["branch"] = data_model["line"] for (_, gen) in data_model["gen"] gen["gen_status"] = gen["status"] gen["gen_bus"] = gen["bus"] + gen["pmin"] = gen["pg_min"] + gen["pmax"] = gen["pg_max"] + gen["qmin"] = gen["qg_min"] + gen["qmax"] = gen["qg_max"] + gen["conn"] = gen["configuration"] + gen["cost"] = [1.0, 0] + gen["model"] = 2 end + data_model["branch"] = data_model["line"] for (_, br) in data_model["branch"] br["br_status"] = br["status"] + br["br_r"] = br["rs"] + br["br_x"] = br["xs"] + br["tap"] = 1.0 + br["shift"] = 0 + @show br + if !haskey(br, "angmin") + N = size(br["br_r"])[1] + br["angmin"] = fill(-pi/2, N) + br["angmax"] = fill(pi/2, N) + end end - data_model["dc_line"] = Dict() - + for (_, tr) in data_model["transformer_2wa"] + tr["rate_a"] = fill(1000.0, 3) + end + + data_model["dcline"] = Dict() + data_model["transformer"] = data_model["transformer_2wa"] + + data_model["per_unit"] = true + data_model["baseMVA"] = 1E12 + data_model["name"] = "IDC" + + return data_model end + +# MAP SOLUTION UP + +function map_solution_up(data_model::Dict, solution::Dict) + sol_hl = deepcopy(solution) + for i in length(data_model["mappings"]):-1:1 + (name, data) = data_model["mappings"][i] + if name=="decompose_transformer_nw" + @show data + end + end +end diff --git a/src/io/data_model_pu.jl b/src/core/data_model_pu.jl similarity index 92% rename from src/io/data_model_pu.jl rename to src/core/data_model_pu.jl index bb58aab8a..b4c734842 100644 --- a/src/io/data_model_pu.jl +++ b/src/core/data_model_pu.jl @@ -49,7 +49,7 @@ function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) # transformers form the edges between these zones zone_edges = Dict([(zone,[]) for zone in keys(zones)]) edges = Set() - for (i,(_,trans)) in enumerate(data_model["transformer_2w_ideal"]) + for (i,(_,trans)) in enumerate(data_model["transformer_2wa"]) push!(edges,i) f_zone = bus_to_zone[trans["f_bus"]] t_zone = bus_to_zone[trans["t_bus"]] @@ -119,7 +119,7 @@ function make_pu!(data_model; sbase=1, vbases=missing) #_rebase_pu_generator!(gen, bus_vbase[gen["bus"]], sbase_old, sbase, v_var_scalar) end - for (id, trans) in data_model["transformer_2w_ideal"] + for (id, trans) in data_model["transformer_2wa"] # voltage base across transformer does not have to be consistent with the ratio! f_vbase = bus_vbase[trans["f_bus"]] t_vbase = bus_vbase[trans["t_bus"]] @@ -276,3 +276,20 @@ function add_big_M!(data_model; kwargs...) data_model["big_M"] = big_M end + +function sol_remove_pu!(solution, data_model) + sbase = data_model["sbase"] + for (comp_type, comp_dict) in [(x,y) for (x,y) in solution if isa(y, Dict)] + for (id, comp) in comp_dict + for (prop, val) in comp + if any([occursin(x, prop) for x in ["p", "q"]]) + comp[prop] = val*sbase + elseif occursin("vm", prop) + comp[prop] = val*data_model[comp_type][id]["vbase"] + end + end + end + end + + return solution +end diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl index b45d1e4f3..c68324d35 100644 --- a/src/io/data_model_components.jl +++ b/src/io/data_model_components.jl @@ -47,7 +47,7 @@ function add!(data_model, comp_type, comp_dict) @assert(haskey(comp_dict, "id"), "The component does not have an id defined.") id = comp_dict["id"] if !haskey(data_model, comp_type) - data_model[comp_type] = Dict{String, Any}() + data_model[comp_type] = Dict{Any, Any}() else @assert(!haskey(data_model[comp_type], id), "There is already a $comp_type with id $id.") end @@ -162,7 +162,7 @@ end # linecode DTYPES[:linecode] = Dict( - :id => AbstractString, + :id => Any, :rs => Array{<:Real, 2}, :xs => Array{<:Real, 2}, :g_fr => Array{<:Real, 2}, @@ -198,7 +198,7 @@ end # line DTYPES[:line] = Dict( - :id => AbstractString, + :id => Any, :status => Int, :f_bus => AbstractString, :t_bus => AbstractString, @@ -208,6 +208,8 @@ DTYPES[:line] = Dict( :length => Real, :c_rating =>Vector{<:Real}, :s_rating =>Vector{<:Real}, + :angmin=>Vector{<:Real}, + :angmax=>Vector{<:Real} ) REQUIRED_FIELDS[:line] = [:id, :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length] @@ -229,7 +231,6 @@ CHECKS[:line] = function check_line(data, line) end N = size(linecode["rs"])[1] - @show(N) @assert(length(line["f_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") @assert(length(line["t_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") else @@ -244,24 +245,28 @@ CHECKS[:line] = function check_line(data, line) end -function create_line(id, f_bus, t_bus, linecode, length; kwargs...) +function create_line(id, f_bus, t_bus, linecode, len; kwargs...) line = Dict{String,Any}() line["id"] = id line["f_bus"] = f_bus line["t_bus"] = t_bus line["linecode"] = linecode - line["length"] = length + line["length"] = len add_kwarg!(line, kwargs, :status, 1) add_kwarg!(line, kwargs, :f_connections, collect(1:4)) add_kwarg!(line, kwargs, :t_connections, collect(1:4)) + + N = length(line["f_connections"]) + add_kwarg!(line, kwargs, :angmin, fill(-60/180*pi, N)) + add_kwarg!(line, kwargs, :angmax, fill( 60/180*pi, N)) return line end # Bus DTYPES[:bus] = Dict( - :id => AbstractString, + :id => Any, :status => Int, :terminals => Array{<:Any}, :phases => Array{<:Int}, @@ -310,7 +315,7 @@ end # Load DTYPES[:load] = Dict( - :id => AbstractString, + :id => Any, :status => Int, :bus => String, :connections => Array{<:Int}, @@ -372,17 +377,17 @@ end # generator DTYPES[:generator] = Dict( - :id => AbstractString, + :id => Any, :status => Int, :bus => String, :connections => Array{<:Int}, :configuration => String, - :pd => Array{<:Real, 1}, - :qd => Array{<:Real, 1}, - :pd_min => Array{<:Real, 1}, - :pd_max => Array{<:Real, 1}, - :qd_min => Array{<:Real, 1}, - :qd_max => Array{<:Real, 1}, + :pg => Array{<:Real, 1}, + :qg => Array{<:Real, 1}, + :pg_min => Array{<:Real, 1}, + :pg_max => Array{<:Real, 1}, + :qg_min => Array{<:Real, 1}, + :qg_max => Array{<:Real, 1}, ) REQUIRED_FIELDS[:generator] = [:id, :status, :bus, :connections] @@ -403,7 +408,7 @@ function create_generator(id, bus; kwargs...) add_kwarg!(generator, kwargs, :status, 1) add_kwarg!(generator, kwargs, :configuration, "wye") add_kwarg!(generator, kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) - copy_kwargs_to_dict_if_present!(generator, kwargs, [:pd_min, :pd_max, :qd_min, :qd_max]) + copy_kwargs_to_dict_if_present!(generator, kwargs, [:pg_min, :pg_max, :qg_min, :qg_max]) return generator end @@ -412,7 +417,7 @@ end DTYPES[:transformer_nw] = Dict( - :id => AbstractString, + :id => Any, :bus => Array{<:AbstractString, 1}, :connections => Array{<:Array{<:Any, 1}, 1}, :vnom => Array{<:Real, 1}, @@ -480,7 +485,7 @@ end # # Transformer, two-winding three-phase # # DTYPES[:transformer_2w_ideal] = Dict( -# :id => AbstractString, +# :id => Any, # :f_bus => String, # :t_bus => String, # :configuration => String, @@ -520,7 +525,7 @@ end # Capacitor DTYPES[:capacitor] = Dict( - :id => AbstractString, + :id => Any, :bus => String, :connections => Array{Int, 1}, :configuration => String, @@ -560,7 +565,7 @@ end # Shunt DTYPES[:shunt] = Dict( - :id => AbstractString, + :id => Any, :status => 1, :bus => String, :terminals => Array{Int, 1}, diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl index 4c3052acc..b2938c59d 100644 --- a/src/io/data_model_test.jl +++ b/src/io/data_model_test.jl @@ -1,8 +1,8 @@ BASE_DIR = "/Users/sclaeys/code/PowerModelsDistribution.jl/src/io" include("$BASE_DIR/data_model_util.jl") include("$BASE_DIR/data_model_components.jl") -include("$BASE_DIR/data_model_mapping.jl") -include("$BASE_DIR/data_model_pu.jl") +include("$BASE_DIR/../core/data_model_mapping.jl") +include("$BASE_DIR/../core/data_model_pu.jl") function make_test_data_model() @@ -26,7 +26,9 @@ function make_test_data_model() add!(data_model, "line", create_line("6", "4", "7", "2_conds", 1.7, f_connections=[2,4], t_connections=[2,4])) # 2 phase conductors add!(data_model, "line", create_line("7", "4", "8", "2_conds", 1.3, f_connections=[1,2], t_connections=[1,2])) - + for i in 8:1000 + add!(data_model, "line", create_line("$i", "4", "8", "2_conds", 1.3, f_connections=[1,2], t_connections=[1,2])) + end add!(data_model, "bus", create_bus("1", terminals=collect(1:4))) add!(data_model, "bus", create_bus("2", terminals=collect(1:6))) @@ -75,25 +77,77 @@ function make_3wire_data_model() data_model = create_data_model() - add!(data_model, "linecode", create_linecode("3_conds", rs=ones(3, 3), xs=ones(3, 3))) + add!(data_model, "linecode", create_linecode("3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3)))) # 3 phase conductors - add!(data_model, "line", create_line("1", "1", "2", "3_conds", 1.3; f_connections=collect(1:3), t_connections=collect(1:3))) + add!(data_model, "line", create_line(:test, "source", "tr_prim", "3_conds", 1.3; f_connections=collect(1:3), t_connections=collect(1:3))) + + add!(data_model, "bus", create_bus("source", terminals=collect(1:4))) + add!(data_model, "bus", create_bus("tr_prim", terminals=collect(1:4))) + add!(data_model, "bus", create_bus("tr_sec", terminals=collect(1:4))) + #add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) + + # add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], + # [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], + # configuration=["delta", "wye", "delta"], + # xsc=[0.0, 0.0, 0.0], + # rs=[0.0, 0.0, 0.0], + # loadloss=0.00, + # imag=0.00, + # )) + + add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["tr_prim", "tr_sec"], [[1,2,3,4], [1,2,3,4]], + [0.230, 0.230], [0.230, 0.230], + configuration=["wye", "wye"], + xsc=[0.0], + rs=[0.0, 0.0], + loadloss=0.00, + imag=0.00, + )) - add!(data_model, "bus", create_bus("1", terminals=collect(1:4))) - add!(data_model, "bus", create_bus("2", terminals=collect(1:4))) - # - add!(data_model, "load", create_load("1", "2", connections=collect(1:4), pd=[1.0, 2.0, 3.0], qd=[1.0, 2.0, 3.0])) - add!(data_model, "generator", create_generator("1", "1", connections=collect(1:4))) + # + add!(data_model, "load", create_load("1", "tr_sec", connections=collect(1:4), pd=[1.0, 2.0, 3.0]/100, qd=[1.0, 2.0, 3.0]/100)) + + add!(data_model, "generator", create_generator("1", "source", + connections=[1, 2, 3, 4], + pg_min=fill(-100, 3), + pg_max=fill( 100, 3), + qg_min=fill(-100, 3), + qg_max=fill( 100, 3), + )) return data_model end -dm_hl = make_3wire_data_model() -check_data_model(dm_hl) -## +@time dm_hl = make_3wire_data_model() + +@time check_data_model(dm_hl) +dm_hl +# dm = map_down_data_model(dm_hl) +make_pu!(dm, vbases=Dict("source"=>230.0)) data_model_index!(dm) +## +import PowerModelsDistribution +PMD = PowerModelsDistribution +import PowerModels +PMs = PowerModels +import InfrastructureModels +IM = InfrastructureModels + +import JuMP, Ipopt + +ipopt_solver = JuMP.with_optimizer(Ipopt.Optimizer) + +#dm_comp = PMD.parse_file("/Users/sclaeys/code/PowerModelsDistribution.jl/test/data/opendss/case3_balanced.dss") +dm = make_compatible_v8!(dm) +dm +## +pm = PMs.instantiate_model(dm, PMs.ACPPowerModel, PMD.build_mc_opf, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) +sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + +sol_remove_pu!(sol["solution"], dm) +solution_ind2id!(sol["solution"], dm) diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl index bcd5a7206..57826283a 100644 --- a/src/io/data_model_util.jl +++ b/src/io/data_model_util.jl @@ -9,12 +9,12 @@ function scale(dict, key, scale) end -function add_component!(data_model, comp_type, comp) +function add_virtual_get_id!(data_model, comp_type, comp) if !haskey(data_model, comp_type) - data_model[comp_type] = Dict{String, Any}() + data_model[comp_type] = Dict{Any, Any}() end comp_dict = data_model[comp_type] - virtual_ids = [parse(Int, x[1]) for x in [match(r"_virtual_([1-9]{1}[0-9]*)", id) for id in keys(comp_dict)] if !isnothing(x)] + virtual_ids = [parse(Int, x[1]) for x in [match(r"_virtual_([1-9]{1}[0-9]*)", id) for id in keys(comp_dict) if isa(id, AbstractString)] if !isnothing(x)] if isempty(virtual_ids) id = "_virtual_1" else @@ -48,7 +48,7 @@ function data_model_index!(data_model; components=["line", "shunt", "generator", data_model["bus"][id]["index"] = i bus_id2ind[id] = i end - data_model["bus"] = Dict(string(bus_id2ind[id])=>bus for (id, bus) in data_model["bus"]) + data_model["bus"] = Dict{String, Any}(string(bus_id2ind[id])=>bus for (id, bus) in data_model["bus"]) for comp_type in components comp_dict = Dict{String, Any}() @@ -69,10 +69,10 @@ function data_model_index!(data_model; components=["line", "shunt", "generator", end -function solution_ind2id(solution, data_model; id_prop="id") +function solution_ind2id!(solution, data_model; id_prop="id") for comp_type in keys(solution) if isa(solution[comp_type], Dict) - comp_dict = Dict{String, Any}() + comp_dict = Dict{Any, Any}() for (ind, comp) in solution[comp_type] id = data_model[comp_type][ind][id_prop] comp_dict[id] = comp From 7fe38f60438f9770c597fbf04137ca640521745a Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 14 Feb 2020 11:49:10 -0700 Subject: [PATCH 006/224] first prototype --- src/core/data_model_mapping.jl | 42 +++++++++++++++++++++++----------- src/core/data_model_pu.jl | 4 ++-- src/io/data_model_test.jl | 28 ++++++++++------------- src/io/data_model_util.jl | 16 ++++++++++++- 4 files changed, 58 insertions(+), 32 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index 5cdc2ede6..fcb463aab 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -2,9 +2,7 @@ import LinearAlgebra # MAP DATA MODEL DOWN -function map_down_data_model(data_model_user) - data_model = deepcopy(data_model_user) - data_model = data_model_user +function data_model_map!(data_model) !haskey(data_model, "mappings") @@ -162,12 +160,12 @@ function _decompose_transformer_nw!(data_model) vbuses, vlines, trans_t_bus_w = _build_loss_model!(data_model, r_s, z_sc, y_sh) - trans_w = Array{Dict, 1}(undef, nrw) + trans_w = Array{String, 1}(undef, nrw) for w in 1:nrw # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] - trans_w[w] = Dict( + trans_w[w] = add_virtual_get_id!(data_model, "transformer_2wa", Dict( "f_bus" => trans["bus"][w], "t_bus" => trans_t_bus_w[w], "tm_nom" => tm_nom, @@ -180,16 +178,14 @@ function _decompose_transformer_nw!(data_model) "tm_max" => trans["tm_max"][w], "tm_min" => trans["tm_min"][w], "tm_step" => trans["tm_step"][w], - ) - - add_virtual_get_id!(data_model, "transformer_2wa", trans_w[w]) + )) end delete_component!(data_model, "transformer_nw", trans) push!(mappings, Dict( "trans"=>trans, - "trans_w"=>trans_w, + "trans_2wa"=>trans_w, "vlines"=>vlines, "vbuses"=>vbuses, )) @@ -342,7 +338,7 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) return bus_ids, line_ids, [bus_ids[bus] for bus in tr_t_bus] end -function make_compatible_v8!(data_model) +function data_model_make_compatible_v8!(data_model) data_model["conductors"] = 3 for (_, bus) in data_model["bus"] bus["bus_type"] = 1 @@ -402,12 +398,32 @@ end # MAP SOLUTION UP -function map_solution_up(data_model::Dict, solution::Dict) - sol_hl = deepcopy(solution) +function solution_unmap!(solution::Dict, data_model::Dict) for i in length(data_model["mappings"]):-1:1 (name, data) = data_model["mappings"][i] if name=="decompose_transformer_nw" - @show data + for bus_id in values(data["vbuses"]) + delete!(solution["bus"], bus_id) + end + + for line_id in values(data["vlines"]) + delete!(solution["branch"], line_id) + end + + pt = [solution["transformer"][tr_id]["pf"] for tr_id in data["trans_2wa"]] + qt = [solution["transformer"][tr_id]["qf"] for tr_id in data["trans_2wa"]] + for tr_id in data["trans_2wa"] + delete!(solution["transformer"], tr_id) + end + + add_solution!(solution, "transformer_nw", data["trans"]["id"], Dict("pt"=>pt, "qt"=>qt)) + end + end + + # remove component dicts if empty + for (comp_type, comp_dict) in solution + if isa(comp_dict, Dict) && isempty(comp_dict) + delete!(solution, comp_type) end end end diff --git a/src/core/data_model_pu.jl b/src/core/data_model_pu.jl index b4c734842..045381c22 100644 --- a/src/core/data_model_pu.jl +++ b/src/core/data_model_pu.jl @@ -81,7 +81,7 @@ function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) end -function make_pu!(data_model; sbase=1, vbases=missing) +function data_model_make_pu!(data_model; sbase=1, vbases=missing) v_var_scalar = data_model["v_var_scalar"] sbase_old = get(data_model, "sbase", missing) @@ -277,7 +277,7 @@ function add_big_M!(data_model; kwargs...) data_model["big_M"] = big_M end -function sol_remove_pu!(solution, data_model) +function solution_unmake_pu!(solution, data_model) sbase = data_model["sbase"] for (comp_type, comp_dict) in [(x,y) for (x,y) in solution if isa(y, Dict)] for (id, comp) in comp_dict diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl index b2938c59d..fa795d70f 100644 --- a/src/io/data_model_test.jl +++ b/src/io/data_model_test.jl @@ -122,14 +122,13 @@ function make_3wire_data_model() end -@time dm_hl = make_3wire_data_model() - -@time check_data_model(dm_hl) -dm_hl -# -dm = map_down_data_model(dm_hl) -make_pu!(dm, vbases=Dict("source"=>230.0)) -data_model_index!(dm) +data_model = make_3wire_data_model() +check_data_model(data_model) + +data_model_map!(data_model) +data_model_make_pu!(data_model, vbases=Dict("source"=>230.0)) +data_model_index!(data_model) +data_model_make_compatible_v8!(data_model) ## import PowerModelsDistribution PMD = PowerModelsDistribution @@ -141,13 +140,10 @@ IM = InfrastructureModels import JuMP, Ipopt ipopt_solver = JuMP.with_optimizer(Ipopt.Optimizer) - -#dm_comp = PMD.parse_file("/Users/sclaeys/code/PowerModelsDistribution.jl/test/data/opendss/case3_balanced.dss") -dm = make_compatible_v8!(dm) -dm -## -pm = PMs.instantiate_model(dm, PMs.ACPPowerModel, PMD.build_mc_opf, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) +pm = PMs.instantiate_model(data_model, PMs.ACPPowerModel, PMD.build_mc_opf, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) -sol_remove_pu!(sol["solution"], dm) -solution_ind2id!(sol["solution"], dm) +solution_unmake_pu!(sol["solution"], data_model) +solution_identify!(sol["solution"], data_model) +solution_unmap!(sol["solution"], data_model) +sol["solution"] diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl index 57826283a..86ddd7838 100644 --- a/src/io/data_model_util.jl +++ b/src/io/data_model_util.jl @@ -69,7 +69,7 @@ function data_model_index!(data_model; components=["line", "shunt", "generator", end -function solution_ind2id!(solution, data_model; id_prop="id") +function solution_identify!(solution, data_model; id_prop="id") for comp_type in keys(solution) if isa(solution[comp_type], Dict) comp_dict = Dict{Any, Any}() @@ -83,3 +83,17 @@ function solution_ind2id!(solution, data_model; id_prop="id") return solution end + +function add_solution!(solution, comp_type, id, data) + if !haskey(solution, comp_type) + solution[comp_type] = Dict() + end + + if !haskey(solution[comp_type], id) + solution[comp_type][id] = Dict{String, Any}() + end + + for (key, prop) in data + solution[comp_type][id][key] = prop + end +end From 2fcbede16e8ef5b310b3cb8a5d33bbe7981be740 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 14 Feb 2020 21:52:49 -0700 Subject: [PATCH 007/224] bounds absent update --- src/core/constraint_template.jl | 18 +-- src/core/data.jl | 90 +++++++---- src/core/variable.jl | 254 ++++++++++++-------------------- src/form/acp.jl | 26 ++-- src/form/acr.jl | 8 +- src/form/ivr.jl | 18 ++- 6 files changed, 196 insertions(+), 218 deletions(-) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 7457527ad..99b653a02 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -302,10 +302,11 @@ function constraint_mc_generation(pm::_PMs.AbstractPowerModel, id::Int; nw::Int= generator = _PMs.ref(pm, nw, :gen, id) bus = _PMs.ref(pm, nw,:bus, generator["gen_bus"]) - pmin = generator["pmin"] - pmax = generator["pmax"] - qmin = generator["qmin"] - qmax = generator["qmax"] + N = 3 + pmin = get(generator, "pmin", fill(-Inf, N)) + pmax = get(generator, "pmax", fill( Inf, N)) + qmin = get(generator, "qmin", fill(-Inf, N)) + qmax = get(generator, "qmax", fill( Inf, N)) if generator["conn"]=="wye" constraint_mc_generation_wye(pm, nw, id, bus["index"], pmin, pmax, qmin, qmax; report=report, bounded=bounded) @@ -365,11 +366,8 @@ function constraint_mc_voltage_angle_difference(pm::_PMs.AbstractPowerModel, i:: t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) pair = (f_bus, t_bus) - buspair = _PMs.ref(pm, nw, :buspairs, pair) - if buspair["branch"] == i - constraint_mc_voltage_angle_difference(pm, nw, f_idx, buspair["angmin"], buspair["angmax"]) - end + constraint_mc_voltage_angle_difference(pm, nw, f_idx, branch["angmin"], branch["angmax"]) end @@ -431,7 +429,9 @@ Notable examples include IVRPowerModel and ACRPowerModel """ function constraint_mc_voltage_magnitude_bounds(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) bus = _PMs.ref(pm, nw, :bus, i) - constraint_mc_voltage_magnitude_bounds(pm, nw, i, bus["vmin"], bus["vmax"]) + vmin = get(bus, "vmin", fill(0.0, 3)) #TODO update for four-wire + vmax = get(bus, "vmax", fill(Inf, 3)) #TODO update for four-wire + constraint_mc_voltage_magnitude_bounds(pm, nw, i, vmin, vmax) end diff --git a/src/core/data.jl b/src/core/data.jl index 0792804a8..5017d2520 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -272,13 +272,37 @@ end Returns a current magnitude bound for the generators. """ function _calc_gen_current_max(gen::Dict, bus::Dict) - pabsmax = max.(abs.(gen["pmin"]), abs.(gen["pmax"])) - qabsmax = max.(abs.(gen["qmax"]), abs.(gen["qmax"])) - smax = sqrt.(pabsmax.^2 + qabsmax.^2) + if all([haskey(gen, "pmax") for prop in ["pmax", "pmin", "qmax", "qmin"]]) && haskey(bus, "vmin") + pabsmax = max.(abs.(gen["pmin"]), abs.(gen["pmax"])) + qabsmax = max.(abs.(gen["qmin"]), abs.(gen["qmax"])) + smax = sqrt.(pabsmax.^2 + qabsmax.^2) - vmin = bus["vmin"] + vmin = bus["vmin"] - return smax./vmin + return smax./vmin + else + return missing + end +end + + +""" +Returns a total (shunt+series) current magnitude bound for the from and to side +of a branch. The total power rating also implies a current bound through the +lower bound on the voltage magnitude of the connected buses. +""" +function _calc_branch_current_max(branch::Dict, bus::Dict) + bounds = [] + if haskey(branch, "c_rating_a") + push!(bounds, branch["c_rating_a"]) + end + if haskey(branch, "rate_a") && haskey(bus, "vmin") + push!(bounds, branch["rate_a"]./bus["vmin"]) + end + if length(bounds)==0 + return missing + end + return min.(bounds...) end @@ -332,16 +356,16 @@ the voltage magnitude of the connected buses. function _calc_transformer_current_max_frto(trans::Dict, bus_fr::Dict, bus_to::Dict) bounds_fr = [] bounds_to = [] - if haskey(trans, "c_rating_a") - push!(bounds_fr, trans["c_rating_a"]) - push!(bounds_to, trans["c_rating_a"]) - end - if haskey(branch, "rate_a") - push!(bounds_fr, trans["rate_a"]./bus_fr["vmin"]) - push!(bounds_to, trans["rate_a"]./bus_to["vmin"]) - end - @assert(length(bounds_fr)>=0, "no (implied/valid) current bounds defined") - return min.(bounds_fr...), min.(bounds_to...) + # if haskey(trans, "c_rating_a") + # push!(bounds_fr, trans["c_rating_a"]) + # push!(bounds_to, trans["c_rating_a"]) + # end + # if haskey(trans, "rate_a") + # push!(bounds_fr, trans["rate_a"]./bus_fr["vmin"]) + # push!(bounds_to, trans["rate_a"]./bus_to["vmin"]) + # end + # return min.(bounds_fr...), min.(bounds_to...) + return missing, missing end @@ -350,37 +374,41 @@ Returns a total (shunt+series) power magnitude bound for the from and to side of a branch. The total current rating also implies a current bound through the upper bound on the voltage magnitude of the connected buses. """ -function _calc_branch_power_ub_frto(branch::Dict, bus_fr::Dict, bus_to::Dict) - bounds_fr = [] - bounds_to = [] - if haskey(branch, "c_rating_a") - push!(bounds_fr, branch["c_rating_a"].*bus_fr["vmax"]) - push!(bounds_to, branch["c_rating_a"].*bus_to["vmax"]) +function _calc_branch_power_max(branch::Dict, bus::Dict) + bounds = [] + if haskey(branch, "c_rating_a") && haskey(bus, "vmax") + push!(bounds, branch["c_rating_a"].*bus["vmax"]) end if haskey(branch, "rate_a") - push!(bounds_fr, branch["rate_a"]) - push!(bounds_to, branch["rate_a"]) + push!(bounds, branch["rate_a"]) end - @assert(length(bounds_fr)>=0, "no (implied/valid) current bounds defined") - return min.(bounds_fr...), min.(bounds_to...) + if length(bounds)==0 + return missing + end + return min.(bounds...) end """ Returns a valid series current magnitude bound for a branch. """ -function _calc_branch_series_current_ub(branch::Dict, bus_fr::Dict, bus_to::Dict) - vmin_fr = bus_fr["vmin"] - vmin_to = bus_to["vmin"] +function _calc_branch_series_current_max(branch::Dict, bus_fr::Dict, bus_to::Dict) + ncnds = 3 #TODO update for four-wire + vmin_fr = get(bus_fr, "vmin", fill(0.0, ncnds)) + vmin_to = get(bus_to, "vmin", fill(0.0, ncnds)) - vmax_fr = bus_fr["vmax"] - vmax_to = bus_to["vmax"] + vmax_fr = get(bus_fr, "vmax", fill(Inf, ncnds)) + vmax_to = get(bus_to, "vmax", fill(Inf, ncnds)) # assumed to be matrices already # temportary fix by shunts_diag2mat! # get valid bounds on total current - c_max_fr_tot, c_max_to_tot = _calc_branch_current_max_frto(branch, bus_fr, bus_to) + c_max_fr_tot = _calc_branch_current_max(branch, bus_fr) + c_max_to_tot = _calc_branch_current_max(branch, bus_to) + if ismissing(c_max_fr_tot) || ismissing(c_max_to_tot) + return missing + end # get valid bounds on shunt current y_fr = branch["g_fr"] + im* branch["b_fr"] diff --git a/src/core/variable.jl b/src/core/variable.jl index 99a80d1b9..469b31da1 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -39,9 +39,14 @@ function variable_mc_voltage_magnitude(pm::_PMs.AbstractPowerModel; nw::Int=pm.c ) if bounded - for (i,bus) in _PMs.ref(pm, nw, :bus), c in cnds - JuMP.set_lower_bound(vm[i][c], bus["vmin"][c]) - JuMP.set_upper_bound(vm[i][c], bus["vmax"][c]) + for (i,bus) in _PMs.ref(pm, nw, :bus) + if haskey(bus, "vmin") + @show vm, bus["vmin"] + JuMP.set_lower_bound.(vm[i], bus["vmin"]) + end + if haskey(bus, "vmax") + JuMP.set_upper_bound.(vm[i], bus["vmax"]) + end end end @@ -60,9 +65,11 @@ function variable_mc_voltage_real(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, b ) if bounded - for (i,bus) in _PMs.ref(pm, nw, :bus), c in cnds - JuMP.set_lower_bound(vr[i][c], -bus["vmax"][c]) - JuMP.set_upper_bound(vr[i][c], bus["vmax"][c]) + for (i,bus) in _PMs.ref(pm, nw, :bus) + if haskey(bus, "vmax") + JuMP.set_lower_bound.(vr[i], -bus["vmax"]) + JuMP.set_upper_bound.(vr[i], bus["vmax"]) + end end end @@ -81,9 +88,11 @@ function variable_mc_voltage_imaginary(pm::_PMs.AbstractPowerModel; nw::Int=pm.c ) if bounded - for (i,bus) in _PMs.ref(pm, nw, :bus), c in cnds - JuMP.set_lower_bound(vi[i][c], -bus["vmax"][c]) - JuMP.set_upper_bound(vi[i][c], bus["vmax"][c]) + for (i,bus) in _PMs.ref(pm, nw, :bus) + if haskey(bus, "vmax") + JuMP.set_lower_bound.(vi[i], -bus["vmax"]) + JuMP.set_upper_bound.(vi[i], bus["vmax"]) + end end end @@ -109,17 +118,11 @@ function variable_mc_branch_flow_active(pm::_PMs.AbstractPowerModel; nw::Int=pm. ) if bounded - for c in cnds - flow_lb, flow_ub = _PMs.ref_calc_branch_flow_bounds(_PMs.ref(pm, nw, :branch), _PMs.ref(pm, nw, :bus), c) - - for arc in _PMs.ref(pm, nw, :arcs) - l,i,j = arc - if !isinf(flow_lb[l]) - JuMP.set_lower_bound(p[arc][c], flow_lb[l]) - end - if !isinf(flow_ub[l]) - JuMP.set_upper_bound(p[arc][c], flow_ub[l]) - end + for (l,i,j) in _PMs.ref(pm, nw, :arcs) + smax = _calc_branch_power_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + if !ismissing(smax) + JuMP.set_upper_bound.(p[(l,i,j)], smax) + JuMP.set_lower_bound.(p[(l,i,j)], -smax) end end end @@ -150,17 +153,11 @@ function variable_mc_branch_flow_reactive(pm::_PMs.AbstractPowerModel; nw::Int=p ) if bounded - for c in cnds - flow_lb, flow_ub = _PMs.ref_calc_branch_flow_bounds(_PMs.ref(pm, nw, :branch), _PMs.ref(pm, nw, :bus), c) - - for arc in _PMs.ref(pm, nw, :arcs) - l,i,j = arc - if !isinf(flow_lb[l]) - JuMP.set_lower_bound(q[arc][c], flow_lb[l]) - end - if !isinf(flow_ub[l]) - JuMP.set_upper_bound(q[arc][c], flow_ub[l]) - end + for (l,i,j) in _PMs.ref(pm, nw, :arcs) + smax = _calc_branch_power_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + if !ismissing(smax) + JuMP.set_upper_bound.(q[(l,i,j)], smax) + JuMP.set_lower_bound.(q[(l,i,j)], -smax) end end end @@ -194,26 +191,11 @@ function variable_mc_branch_current_real(pm::_PMs.AbstractPowerModel; nw::Int=pm ) if bounded - ub = Dict() - for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) - b = branch[l] - # ub[l] = Inf - if haskey(b, "rate_a") - rate_fr = b["rate_a"].*b["tap"] - rate_to = b["rate_a"] - ub[l] = max.(rate_fr./bus[i]["vmin"], rate_to./bus[j]["vmin"]) - end - if haskey(b, "c_rating_a") - ub[l] = b["c_rating_a"] - end - end - for (l,i,j) in _PMs.ref(pm, nw, :arcs) - for c in _PMs.conductor_ids(pm; nw=nw) - if !isinf(ub[l][c]) - JuMP.set_lower_bound(cr[(l,i,j)][c], -ub[l][c]) - JuMP.set_upper_bound(cr[(l,i,j)][c], ub[l][c]) - end + cmax = _calc_branch_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + if !ismissing(cmax) + JuMP.set_upper_bound.(cr[(l,i,j)], cmax) + JuMP.set_lower_bound.(cr[(l,i,j)], -cmax) end end end @@ -236,26 +218,11 @@ function variable_mc_branch_current_imaginary(pm::_PMs.AbstractPowerModel; nw::I ) if bounded - ub = Dict() - for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) - b = branch[l] - # ub[l] = Inf - if haskey(b, "rate_a") - rate_fr = b["rate_a"].*b["tap"] - rate_to = b["rate_a"] - ub[l] = max.(rate_fr./bus[i]["vmin"], rate_to./bus[j]["vmin"]) - end - if haskey(b, "c_rating_a") - ub[l] = b["c_rating_a"] - end - end - for (l,i,j) in _PMs.ref(pm, nw, :arcs) - for c in _PMs.conductor_ids(pm; nw=nw) - if !isinf(ub[l][c]) - JuMP.set_lower_bound(ci[(l,i,j)][c], -ub[l][c]) - JuMP.set_upper_bound(ci[(l,i,j)][c], ub[l][c]) - end + cmax = _calc_branch_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + if !ismissing(cmax) + JuMP.set_upper_bound.(ci[(l,i,j)], cmax) + JuMP.set_lower_bound.(ci[(l,i,j)], -cmax) end end end @@ -278,36 +245,16 @@ function variable_mc_branch_series_current_real(pm::_PMs.AbstractPowerModel; nw: ) if bounded - ub = Dict() for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) - b = branch[l] - ub[l] = Inf - if haskey(b, "rate_a") - rate = b["rate_a"].*b["tap"] - y_fr = abs.(b["g_fr"] + im*b["b_fr"]) - y_to = abs.(b["g_to"] + im*b["b_to"]) - shuntcurrent = max.(y_fr*bus[i]["vmax"].^2, y_to*bus[j]["vmax"].^2) - seriescurrent = max.(rate./bus[i]["vmin"], rate./bus[j]["vmin"]) - ub[l] = seriescurrent + shuntcurrent - end - if haskey(b, "c_rating_a") - totalcurrent = b["c_rating_a"] - y_fr = abs.(b["g_fr"] + im*b["b_fr"]) - y_to = abs.(b["g_to"] + im*b["b_to"]) - shuntcurrent = max.(y_fr*bus[i]["vmax"].^2, y_to*bus[j]["vmax"].^2) - ub[l] = totalcurrent + shuntcurrent - end - end - - for l in _PMs.ids(pm, nw, :branch) - for c in _PMs.conductor_ids(pm; nw=nw) - if !isinf(ub[l][c]) - JuMP.set_lower_bound(csr[l][c], -ub[l][c]) - JuMP.set_upper_bound(csr[l][c], ub[l][c]) - end + cmax = _calc_branch_series_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + if !ismissing(cmax) + @show cmax + JuMP.set_upper_bound.(csr[l], cmax) + JuMP.set_lower_bound.(csr[l], -cmax) end end end + report && _PMs.sol_component_value(pm, nw, :branch, :csr_fr, _PMs.ids(pm, nw, :branch), csr) end @@ -326,36 +273,15 @@ function variable_mc_branch_series_current_imaginary(pm::_PMs.AbstractPowerModel ) if bounded - ub = Dict() for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) - b = branch[l] - ub[l] = Inf - if haskey(b, "rate_a") - rate = b["rate_a"].*b["tap"] - y_fr = abs.(b["g_fr"] + im*b["b_fr"]) - y_to = abs.(b["g_to"] + im*b["b_to"]) - shuntcurrent = max.(y_fr*bus[i]["vmax"].^2, y_to*bus[j]["vmax"].^2) - seriescurrent = max.(rate./bus[i]["vmin"], rate./bus[j]["vmin"]) - ub[l] = seriescurrent + shuntcurrent - end - if haskey(b, "c_rating_a") - totalcurrent = b["c_rating_a"] - y_fr = abs.(b["g_fr"] + im*b["b_fr"]) - y_to = abs.(b["g_to"] + im*b["b_to"]) - shuntcurrent = max.(y_fr*bus[i]["vmax"].^2, y_to*bus[j]["vmax"].^2) - ub[l] = totalcurrent + shuntcurrent - end - end - - for l in _PMs.ids(pm, nw, :branch) - for c in _PMs.conductor_ids(pm; nw=nw) - if !isinf(ub[l][c]) - JuMP.set_lower_bound(csi[l][c], -ub[l][c]) - JuMP.set_upper_bound(csi[l][c], ub[l][c]) - end + cmax = _calc_branch_series_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + if !ismissing(cmax) + JuMP.set_upper_bound.(csi[l], cmax) + JuMP.set_lower_bound.(csi[l], -cmax) end end end + report && _PMs.sol_component_value(pm, nw, :branch, :csi_fr, _PMs.ids(pm, nw, :branch), csi) end @@ -375,14 +301,18 @@ function variable_mc_transformer_current_real(pm::_PMs.AbstractPowerModel; nw::I if bounded for (l,i,j) in _PMs.ref(pm, nw, :arcs_from_trans) - trans = _PMs.ref(pm, nw, :trans, l) + trans = _PMs.ref(pm, nw, :transformer, l) f_bus = _PMs.ref(pm, nw, :bus, i) t_bus = _PMs.ref(pm, nw, :bus, j) cmax_fr, cmax_to = _calc_transformer_current_max_frto(trans, f_bus, t_bus) - JuMP.set_lower_bound(ci[(l,i,j)], -cmax_fr) - JuMP.set_lower_bound(ci[(l,j,i)], -cmax_to) - JuMP.set_upper_bound(ci[(l,i,j)], cmax_fr) - JuMP.set_upper_bound(ci[(l,j,i)], cmax_to) + if !ismissing(cmax_fr) + JuMP.set_lower_bound(cr[(l,i,j)], -cmax_fr) + JuMP.set_upper_bound(cr[(l,i,j)], cmax_fr) + end + if !ismissing(cmax_to) + JuMP.set_lower_bound(cr[(l,j,i)], -cmax_to) + JuMP.set_upper_bound(cr[(l,j,i)], cmax_to) + end end end @@ -405,14 +335,18 @@ function variable_mc_transformer_current_imaginary(pm::_PMs.AbstractPowerModel; if bounded for (l,i,j) in _PMs.ref(pm, nw, :arcs_from_trans) - trans = _PMs.ref(pm, nw, :trans, l) + trans = _PMs.ref(pm, nw, :transformer, l) f_bus = _PMs.ref(pm, nw, :bus, i) t_bus = _PMs.ref(pm, nw, :bus, j) cmax_fr, cmax_to = _calc_transformer_current_max_frto(trans, f_bus, t_bus) - JuMP.set_lower_bound(ci[(l,i,j)], -cmax_fr) - JuMP.set_lower_bound(ci[(l,j,i)], -cmax_to) - JuMP.set_upper_bound(ci[(l,i,j)], cmax_fr) - JuMP.set_upper_bound(ci[(l,j,i)], cmax_to) + if !ismissing(cmax_fr) + JuMP.set_lower_bound(ci[(l,i,j)], -cmax_fr) + JuMP.set_upper_bound(ci[(l,i,j)], cmax_fr) + end + if !ismissing(cmax_to) + JuMP.set_lower_bound(ci[(l,j,i)], -cmax_to) + JuMP.set_upper_bound(ci[(l,j,i)], cmax_to) + end end end @@ -441,9 +375,13 @@ function variable_mc_voltage_magnitude_sqr(pm::_PMs.AbstractPowerModel; nw::Int= ) if bounded - for (i,bus) in _PMs.ref(pm, nw, :bus), c in cnds - JuMP.set_lower_bound(w[i][c], bus["vmin"][c]^2) - JuMP.set_upper_bound(w[i][c], bus["vmax"][c]^2) + for (i,bus) in _PMs.ref(pm, nw, :bus) + if haskey(bus, "vmin") + JuMP.set_lower_bound.(w[i], bus["vmin"].^2) + end + if haskey(bus, "vmax") + JuMP.set_upper_bound.(w[i], bus["vmax"].^2) + end end end @@ -888,9 +826,13 @@ function variable_mc_generation_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.c ) if bounded - for (i,gen) in _PMs.ref(pm, nw, :gen), c in cnds - JuMP.set_lower_bound(pg[i][c], gen["pmin"][c]) - JuMP.set_upper_bound(pg[i][c], gen["pmax"][c]) + for (i,gen) in _PMs.ref(pm, nw, :gen) + if haskey(gen, "pmin") + JuMP.set_lower_bound.(pg[i], gen["pmin"]) + end + if haskey(gen, "pmax") + JuMP.set_upper_bound.(pg[i], gen["pmax"]) + end end end @@ -911,9 +853,13 @@ function variable_mc_generation_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm ) if bounded - for (i,gen) in _PMs.ref(pm, nw, :gen), c in cnds - JuMP.set_lower_bound(qg[i][c], gen["qmin"][c]) - JuMP.set_upper_bound(qg[i][c], gen["qmax"][c]) + for (i,gen) in _PMs.ref(pm, nw, :gen) + if haskey(gen, "qmin") + JuMP.set_lower_bound.(pg[i], gen["qmin"]) + end + if haskey(gen, "qmax") + JuMP.set_upper_bound.(pg[i], gen["qmax"]) + end end end @@ -936,17 +882,11 @@ function variable_mc_generation_current_real(pm::_PMs.AbstractPowerModel; nw::In ) for i in _PMs.ids(pm, nw, :gen) ) if bounded - ub = Dict() - for (i, g) in gen - vmin = bus[g["gen_bus"]]["vmin"] - s = abs.(max.(abs.(g["pmax"]),abs.(g["pmin"])) + im.*max.(abs.(g["qmax"]), abs.(g["qmin"]))) - ub[i] = s./vmin - end - for (i, g) in gen - for c in cnds - JuMP.set_lower_bound(crg[i][c], -ub[i][c]) - JuMP.set_upper_bound(crg[i][c], ub[i][c]) + cmax = _calc_gen_current_max(g, _PMs.ref(pm, nw, :bus, g["gen_bus"])) + if !ismissing(cmax) + JuMP.set_lower_bound.(crg[i], -cmax) + JuMP.set_upper_bound.(crg[i], cmax) end end end @@ -967,17 +907,11 @@ function variable_mc_generation_current_imaginary(pm::_PMs.AbstractPowerModel; n ) for i in _PMs.ids(pm, nw, :gen) ) if bounded - ub = Dict() for (i, g) in gen - vmin = bus[g["gen_bus"]]["vmin"] - s = abs.(max.(abs.(g["pmax"]),abs.(g["pmin"])) + im.*max.(abs.(g["qmax"]), abs.(g["qmin"]))) - ub[i] = s./vmin - end - - for (i, g) in gen - for c in cnds - JuMP.set_lower_bound(cig[i][c], -ub[i][c]) - JuMP.set_upper_bound(cig[i][c], ub[i][c]) + cmax = _calc_gen_current_max(g, _PMs.ref(pm, nw, :bus, g["gen_bus"])) + if !ismissing(cmax) + JuMP.set_lower_bound.(cig[i], -cmax) + JuMP.set_upper_bound.(cig[i], cmax) end end end diff --git a/src/form/acp.jl b/src/form/acp.jl index 12b0ed672..1a056e116 100644 --- a/src/form/acp.jl +++ b/src/form/acp.jl @@ -281,6 +281,8 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACPModel, nw::Int, i: cstr_p = [] cstr_q = [] + @show(bus_loads) + for c in _PMs.conductor_ids(pm; nw=nw) cp = JuMP.@NLconstraint(pm.model, sum(p[a][c] for a in bus_arcs) @@ -290,12 +292,12 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACPModel, nw::Int, i: sum(pg[g][c] for g in bus_gens) - sum(ps[s][c] for s in bus_storage) - sum(pd[l][c] for l in bus_loads) - - ( # shunt - Gt[c,c] * vm[c]^2 - +sum( Gt[c,d] * vm[c]*vm[d] * cos(va[c]-va[d]) - +Bt[c,d] * vm[c]*vm[d] * sin(va[c]-va[d]) - for d in cnds if d != c) - ) + # - ( # shunt + # Gt[c,c] * vm[c]^2 + # +sum( Gt[c,d] * vm[c]*vm[d] * cos(va[c]-va[d]) + # +Bt[c,d] * vm[c]*vm[d] * sin(va[c]-va[d]) + # for d in cnds if d != c) + # ) ) push!(cstr_p, cp) @@ -307,12 +309,12 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACPModel, nw::Int, i: sum(qg[g][c] for g in bus_gens) - sum(qs[s][c] for s in bus_storage) - sum(qd[l][c] for l in bus_loads) - - ( # shunt - -Bt[c,c] * vm[c]^2 - -sum( Bt[c,d] * vm[c]*vm[d] * cos(va[c]-va[d]) - -Gt[c,d] * vm[c]*vm[d] * sin(va[c]-va[d]) - for d in cnds if d != c) - ) + # - ( # shunt + # -Bt[c,c] * vm[c]^2 + # -sum( Bt[c,d] * vm[c]*vm[d] * cos(va[c]-va[d]) + # -Gt[c,d] * vm[c]*vm[d] * sin(va[c]-va[d]) + # for d in cnds if d != c) + # ) ) push!(cstr_q, cq) end diff --git a/src/form/acr.jl b/src/form/acr.jl index d0942bb8c..58b933dd9 100644 --- a/src/form/acr.jl +++ b/src/form/acr.jl @@ -39,8 +39,12 @@ function constraint_mc_voltage_magnitude_bounds(pm::_PMs.AbstractACRModel, n::In vr = _PMs.var(pm, n, :vr, i) vi = _PMs.var(pm, n, :vi, i) - JuMP.@constraint(pm.model, vmin.^2 .<= vr.^2 + vi.^2) - JuMP.@constraint(pm.model, vmax.^2 .>= vr.^2 + vi.^2) + for t in 1:length(vr) + JuMP.@constraint(pm.model, vmin[t]^2 .<= vr[t]^2 + vi[t]^2) + if vmax[t] < Inf + JuMP.@constraint(pm.model, vmax[t]^2 .>= vr[t]^2 + vi[t]^2) + end + end end diff --git a/src/form/ivr.jl b/src/form/ivr.jl index 204dfddcb..d92adcf72 100644 --- a/src/form/ivr.jl +++ b/src/form/ivr.jl @@ -572,10 +572,20 @@ function constraint_mc_generation_wye(pm::_PMs.IVRPowerModel, nw::Int, id::Int, qg = JuMP.@NLexpression(pm.model, [i in 1:nph], -vr[i]*cig[i]+vi[i]*crg[i]) if bounded - JuMP.@constraint(pm.model, pmin .<= vr.*crg + vi.*cig) - JuMP.@constraint(pm.model, pmax .>= vr.*crg + vi.*cig) - JuMP.@constraint(pm.model, qmin .<= vi.*crg - vr.*cig) - JuMP.@constraint(pm.model, qmax .>= vi.*crg - vr.*cig) + for c in 1:nph + if pmin[c]>-Inf + JuMP.@constraint(pm.model, pmin[c] .<= vr[c]*crg[c] + vi[c]*cig[c]) + end + if pmax[c]< Inf + JuMP.@constraint(pm.model, pmax[c] .>= vr[c]*crg[c] + vi[c]*cig[c]) + end + if qmin[c]>-Inf + JuMP.@constraint(pm.model, qmin[c] .<= vi[c]*crg[c] - vr[c]*cig[c]) + end + if qmax[c]< Inf + JuMP.@constraint(pm.model, qmax[c] .>= vi[c]*crg[c] - vr[c]*cig[c]) + end + end end _PMs.var(pm, nw, :crg_bus)[id] = crg From 26111af7d4028e8025263ea721e174ee03a7af7e Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 14 Feb 2020 21:52:59 -0700 Subject: [PATCH 008/224] looking good --- src/core/data_model_mapping.jl | 77 +++++++++++++--- src/core/data_model_pu.jl | 12 +-- src/io/data_model_components.jl | 130 +++++++++++++++++++-------- src/io/data_model_test.jl | 152 +++++++++++++++++--------------- src/io/data_model_util.jl | 31 ++++++- 5 files changed, 275 insertions(+), 127 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index fcb463aab..d374e4714 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -7,6 +7,7 @@ function data_model_map!(data_model) !haskey(data_model, "mappings") _expand_linecode!(data_model) + add_mappings!(data_model, "decompose_voltage_source", _decompose_voltage_source!(data_model)) add_mappings!(data_model, "load_to_shunt", _load_to_shunt!(data_model)) add_mappings!(data_model, "capacitor_to_shunt", _capacitor_to_shunt!(data_model)) add_mappings!(data_model, "decompose_transformer_nw", _decompose_transformer_nw!(data_model)) @@ -65,7 +66,7 @@ function _load_to_shunt!(data_model) push!(mappings, Dict( "load" => load, - "shunt" => shunt, + "shunt_id" => shunt["id"], )) end end @@ -104,12 +105,12 @@ function _capacitor_to_shunt!(data_model) end shunt = create_shunt(NaN, cap["bus"], cap["connections"], b_sh=B) - add_component!(data_model, "shunt", shunt) + add_virtual!(data_model, "shunt", shunt) delete_component!(data_model, "capacitor", cap) push!(mappings, Dict( "capacitor" => cap, - "shunt" => shunt, + "shunt_id" => shunt["id"], )) end end @@ -118,7 +119,36 @@ function _capacitor_to_shunt!(data_model) end -# test +function _decompose_voltage_source!(data_model) + mappings = [] + for (id, vs) in data_model["voltage_source"] + gen = create_generator("", vs["bus"], connections=vs["connections"]) + @show vs + for prop in ["pg_max", "pg_min", "qg_max", "qg_min"] + if haskey(vs, prop) + gen[prop] = vs[prop] + end + end + gen_id = add_virtual_get_id!(data_model, "generator", gen) + + bus = data_model["bus"][vs["bus"]] + conns = vs["connections"] + terminals = bus["terminals"] + + @assert(Set(conns)==Set(terminals), "A voltage source should connect to all terminals of its associated bus!") + tmp = Dict(enumerate(conns)) + bus["vm"] = bus["vmax"] = bus["vmin"] = [vs["vm"][tmp[t]] for t in terminals] + bus["va"] = [vs["va"][tmp[t]] for t in terminals] + bus["bus_type"] = 3 + + delete_component!(data_model, "voltage_source", vs["id"]) + push!(mappings, Dict("voltage_source"=>vs, "gen_id"=>gen_id)) + end + + return mappings +end + + """ function decompose_transformer_nw_lossy!(data_model) @@ -338,14 +368,19 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) return bus_ids, line_ids, [bus_ids[bus] for bus in tr_t_bus] end +function _alias!(dict, fr, to) + if haskey(dict, fr) + dict[to] = dict[fr] + end +end + function data_model_make_compatible_v8!(data_model) data_model["conductors"] = 3 + data_model["buspairs"] = nothing for (_, bus) in data_model["bus"] bus["bus_type"] = 1 bus["status"] = 1 bus["bus_i"] = bus["index"] - bus["vmin"] = fill(0.9, 3) - bus["vmax"] = fill(1.1, 3) end for (_, load) in data_model["load"] @@ -357,10 +392,10 @@ function data_model_make_compatible_v8!(data_model) for (_, gen) in data_model["gen"] gen["gen_status"] = gen["status"] gen["gen_bus"] = gen["bus"] - gen["pmin"] = gen["pg_min"] - gen["pmax"] = gen["pg_max"] - gen["qmin"] = gen["qg_min"] - gen["qmax"] = gen["qg_max"] + _alias!(gen, "pg_min", "pmin") + _alias!(gen, "qg_min", "qmin") + _alias!(gen, "pg_max", "pmax") + _alias!(gen, "qg_max", "qmax") gen["conn"] = gen["configuration"] gen["cost"] = [1.0, 0] gen["model"] = 2 @@ -373,7 +408,7 @@ function data_model_make_compatible_v8!(data_model) br["br_x"] = br["xs"] br["tap"] = 1.0 br["shift"] = 0 - @show br + if !haskey(br, "angmin") N = size(br["br_r"])[1] br["angmin"] = fill(-pi/2, N) @@ -385,6 +420,12 @@ function data_model_make_compatible_v8!(data_model) tr["rate_a"] = fill(1000.0, 3) end + for (_, shunt) in data_model["shunt"] + shunt["shunt_bus"] = shunt["bus"] + shunt["gs"] = shunt["g_sh"] + shunt["bs"] = shunt["b_sh"] + end + data_model["dcline"] = Dict() data_model["transformer"] = data_model["transformer_2wa"] @@ -401,6 +442,7 @@ end function solution_unmap!(solution::Dict, data_model::Dict) for i in length(data_model["mappings"]):-1:1 (name, data) = data_model["mappings"][i] + if name=="decompose_transformer_nw" for bus_id in values(data["vbuses"]) delete!(solution["bus"], bus_id) @@ -417,6 +459,19 @@ function solution_unmap!(solution::Dict, data_model::Dict) end add_solution!(solution, "transformer_nw", data["trans"]["id"], Dict("pt"=>pt, "qt"=>qt)) + elseif name=="capacitor_to_shunt" + # shunt has no solutions defined + delete_solution!(solution, "shunt", data["shunt_id"]) + add_solution!(solution, "capacitor", data["capacitor"]["id"], Dict()) + elseif name=="load_to_shunt" + # shunt has no solutions, but a load should have! + delete!(solution, "shunt", data["shunt_id"]) + add_solution!(solution, "load", data["load"]["id"], Dict()) + elseif name=="decompose_voltage_source" + gen = solution["gen"][data["gen_id"]] + delete_solution!(solution, "gen", data["gen_id"]) + add_solution!(solution, "voltage_source", data["voltage_source"]["id"], Dict("pg"=>gen["pg"], "qg"=>gen["qg"])) + end end diff --git a/src/core/data_model_pu.jl b/src/core/data_model_pu.jl index 045381c22..59f1d6fb3 100644 --- a/src/core/data_model_pu.jl +++ b/src/core/data_model_pu.jl @@ -135,15 +135,15 @@ end function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) # if not in p.u., these are normalized with respect to vnom - prop_vnom = ["vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] + prop_vnom = ["vmax", "vmin", "vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] if !haskey(bus, "vbase") - if haskey(bus, "vnom") - vnom = bus["vnom"] - _scale_props!(bus, prop_vnom, vnom/vbase) - _scale_props!(bus, ["vnom"], 1/vbase) - end + # if haskey(bus, "vnom") + # vnom = bus["vnom"] + # _scale_props!(bus, ["vnom"], 1/vbase) + # end + _scale_props!(bus, prop_vnom, 1/vbase) z_old = 1.0 else diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl index c68324d35..0b2bdcaf4 100644 --- a/src/io/data_model_components.jl +++ b/src/io/data_model_components.jl @@ -54,8 +54,16 @@ function add!(data_model, comp_type, comp_dict) data_model[comp_type][id] = comp_dict end +function _add_unused_kwargs!(comp_dict, kwargs) + for (prop, val) in kwargs + if !haskey(comp_dict, "$prop") + comp_dict["$prop"] = val + end + end +end + function check_data_model(data) - for component in [:bus, :linecode, :line, :load, :generator, :transformer_nw, :capacitor, :shunt] + for component in keys(DTYPES) if haskey(data, string(component)) for (id, comp_dict) in data[string(component)] if haskey(REQUIRED_FIELDS, component) @@ -177,9 +185,9 @@ CHECKS[:linecode] = function check_linecode(data, linecode) _check_same_size(linecode, [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to]) end -function create_linecode(id; kwargs...) +function create_linecode(; kwargs...) linecode = Dict{String,Any}() - linecode["id"] = id + n_conductors = 0 for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] if haskey(kwargs, key) @@ -192,6 +200,8 @@ function create_linecode(id; kwargs...) add_kwarg!(linecode, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) add_kwarg!(linecode, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) add_kwarg!(linecode, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) + + _add_unused_kwargs!(linecode, kwargs) return linecode end @@ -245,13 +255,8 @@ CHECKS[:line] = function check_line(data, line) end -function create_line(id, f_bus, t_bus, linecode, len; kwargs...) +function create_line(; kwargs...) line = Dict{String,Any}() - line["id"] = id - line["f_bus"] = f_bus - line["t_bus"] = t_bus - line["linecode"] = linecode - line["length"] = len add_kwarg!(line, kwargs, :status, 1) add_kwarg!(line, kwargs, :f_connections, collect(1:4)) @@ -260,6 +265,8 @@ function create_line(id, f_bus, t_bus, linecode, len; kwargs...) N = length(line["f_connections"]) add_kwarg!(line, kwargs, :angmin, fill(-60/180*pi, N)) add_kwarg!(line, kwargs, :angmax, fill( 60/180*pi, N)) + + _add_unused_kwargs!(line, kwargs) return line end @@ -299,16 +306,16 @@ CHECKS[:bus] = function check_bus(data, bus) end end -function create_bus(id; kwargs...) +function create_bus(; kwargs...) bus = Dict{String,Any}() - bus["id"] = id add_kwarg!(bus, kwargs, :status, 1) add_kwarg!(bus, kwargs, :terminals, collect(1:4)) add_kwarg!(bus, kwargs, :grounded, []) add_kwarg!(bus, kwargs, :rg, Array{Float64, 1}()) add_kwarg!(bus, kwargs, :xg, Array{Float64, 1}()) - copy_kwargs_to_dict_if_present!(bus, kwargs, [:phases, :neutral, :vm_max, :vm_min, :vm_pp_max, :vm_pp_min, :vm_pn_max, :vm_pn_min, :vm_fix, :va_fix]) + + _add_unused_kwargs!(bus, kwargs) return bus end @@ -354,10 +361,8 @@ CHECKS[:load] = function check_load(data, load) end -function create_load(id, bus; kwargs...) +function create_load(; kwargs...) load = Dict{String,Any}() - load["id"] = id - load["bus"] = bus add_kwarg!(load, kwargs, :status, 1) add_kwarg!(load, kwargs, :configuration, "wye") @@ -369,8 +374,9 @@ function create_load(id, bus; kwargs...) else add_kwarg!(load, kwargs, :pd_ref, fill(0.0, 3)) add_kwarg!(load, kwargs, :qd_ref, fill(0.0, 3)) - copy_kwargs_to_dict_if_present!(load, kwargs, [:pd_ref, :qd_ref, :vnom, :alpha, :beta]) end + + _add_unused_kwargs!(load, kwargs) return load end @@ -401,14 +407,14 @@ CHECKS[:generator] = function check_generator(data, generator) _check_connectivity(data, generator; context="generator $id") end -function create_generator(id, bus; kwargs...) +function create_generator(; kwargs...) generator = Dict{String,Any}() - generator["id"] = id - generator["bus"] = bus + add_kwarg!(generator, kwargs, :status, 1) add_kwarg!(generator, kwargs, :configuration, "wye") add_kwarg!(generator, kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) - copy_kwargs_to_dict_if_present!(generator, kwargs, [:pg_min, :pg_max, :qg_min, :qg_max]) + + _add_unused_kwargs!(generator, kwargs) return generator end @@ -418,6 +424,7 @@ end DTYPES[:transformer_nw] = Dict( :id => Any, + :status => Int, :bus => Array{<:AbstractString, 1}, :connections => Array{<:Array{<:Any, 1}, 1}, :vnom => Array{<:Real, 1}, @@ -435,6 +442,8 @@ DTYPES[:transformer_nw] = Dict( :tm_step => Array{<:Array{<:Real, 1}, 1}, ) +REQUIRED_FIELDS[:transformer_nw] = keys(DTYPES[:transformer_nw]) + CHECKS[:transformer_nw] = function check_transformer_nw(data, trans) id = trans["id"] @@ -457,15 +466,12 @@ CHECKS[:transformer_nw] = function check_transformer_nw(data, trans) end -function create_transformer_nw(id, n_windings, bus, connections, vnom, snom; kwargs...) +function create_transformer_nw(; kwargs...) trans = Dict{String,Any}() - trans["id"] = id - trans["bus"] = bus - trans["connections"] = connections - trans["vnom"] = vnom - trans["snom"] = snom - n_windings = length(connections) + @assert(haskey(kwargs, :bus), "You have to specify at least the buses.") + n_windings = length(kwargs[:bus]) + add_kwarg!(trans, kwargs, :status, 1) add_kwarg!(trans, kwargs, :configuration, fill("wye", n_windings)) add_kwarg!(trans, kwargs, :polarity, fill(true, n_windings)) add_kwarg!(trans, kwargs, :rs, fill(0.0, n_windings)) @@ -477,7 +483,8 @@ function create_transformer_nw(id, n_windings, bus, connections, vnom, snom; kwa add_kwarg!(trans, kwargs, :tm_max, fill(fill(1.1, 3), n_windings)) add_kwarg!(trans, kwargs, :tm_step, fill(fill(1/32, 3), n_windings)) add_kwarg!(trans, kwargs, :tm_fix, fill(fill(true, 3), n_windings)) - copy_kwargs_to_dict_if_present!(trans, kwargs, [:tm_min, :tm_max]) + + _add_unused_kwargs!(trans, kwargs) return trans end @@ -526,6 +533,7 @@ end DTYPES[:capacitor] = Dict( :id => Any, + :status => Int, :bus => String, :connections => Array{Int, 1}, :configuration => String, @@ -533,31 +541,35 @@ DTYPES[:capacitor] = Dict( :vnom => Real, ) +REQUIRED_FIELDS[:capacitor] = keys(DTYPES[:capacitor]) + CHECKS[:capacitor] = function check_capacitor(data, cap) id = cap["id"] N = length(cap["connections"]) config = cap["configuration"] if config=="wye" - @assert(length(cap["qd_ref"])==N-1, "Capacitor $id: qd_ref should have $(N-1) elements.") + @assert(length(cap["qd_ref"])==N-1, "capacitor $id: qd_ref should have $(N-1) elements.") else - @assert(length(cap["qd_ref"])==N, "Capacitor $id: qd_ref should have $N elements.") + @assert(length(cap["qd_ref"])==N, "capacitor $id: qd_ref should have $N elements.") end @assert(config in ["delta", "wye", "wye-grounded", "wye-floating"]) if config=="delta" @assert(N>=3, "Capacitor $id: delta-connected capacitors should have at least 3 elements.") end + + _check_connectivity(data, cap; context="capacitor $id") end -function create_capacitor(id, bus, vnom; kwargs...) +function create_capacitor(; kwargs...) cap = Dict{String,Any}() - cap["id"] = id - cap["bus"] = bus - cap["vnom"] = vnom + add_kwarg!(cap, kwargs, :configuration, "wye") add_kwarg!(cap, kwargs, :connections, collect(1:4)) add_kwarg!(cap, kwargs, :qd_ref, fill(0.0, 3)) + + _add_unused_kwargs!(cap, kwargs) return cap end @@ -566,26 +578,66 @@ end DTYPES[:shunt] = Dict( :id => Any, - :status => 1, + :status => Int, :bus => String, :terminals => Array{Int, 1}, :g_sh => Array{<:Real, 2}, :b_sh => Array{<:Real, 2}, ) +REQUIRED_FIELDS[:capacitor] = keys(DTYPES[:shunt]) + CHECKS[:shunt] = function check_shunt(data, shunt) + _check_connectivity(data, shunt; context="shunt $id") + end -function create_shunt(id, bus, terminals; kwargs...) +function create_shunt(; kwargs...) shunt = Dict{String,Any}() - shunt["id"] = id - shunt["bus"] = bus - shunt["terminals"] = terminals add_kwarg!(shunt, kwargs, :status, 1) add_kwarg!(shunt, kwargs, :g_sh, fill(0.0, length(terminals), length(terminals))) add_kwarg!(shunt, kwargs, :b_sh, fill(0.0, length(terminals), length(terminals))) + + _add_unused_kwargs!(shunt, kwargs) return shunt end + + +# voltage source + +DTYPES[:voltage_source] = Dict( + :id => Any, + :status => Int, + :bus => String, + :connections => Array{Int, 1}, + :vm =>Array{<:Real}, + :va =>Array{<:Real}, + :pg_max =>Array{<:Real}, + :pg_min =>Array{<:Real}, + :qg_max =>Array{<:Real}, + :qg_min =>Array{<:Real}, +) + +REQUIRED_FIELDS[:voltage_source] = [:id, :status, :bus, :connections, :vm, :va] + +CHECKS[:voltage_source] = function check_voltage_source(data, vs) + id = vs["id"] + _check_connectivity(data, vs; context="voltage source $id") + N = length(vs["connections"]) + _check_has_size(vs, ["vm", "va", "pg_max", "pg_min", "qg_max", "qg_min"], N, context="voltage source $id") + +end + + +function create_voltage_source(; kwargs...) + vs = Dict{String,Any}() + + add_kwarg!(vs, kwargs, :status, 1) + add_kwarg!(vs, kwargs, :connections, collect(1:3)) + + _add_unused_kwargs!(vs, kwargs) + return vs +end diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl index fa795d70f..0bf7cd7bb 100644 --- a/src/io/data_model_test.jl +++ b/src/io/data_model_test.jl @@ -8,66 +8,66 @@ function make_test_data_model() data_model = create_data_model() - add!(data_model, "linecode", create_linecode("6_conds", rs=ones(6, 6), xs=ones(6, 6))) - add!(data_model, "linecode", create_linecode("4_conds", rs=ones(4, 4), xs=ones(4, 4))) - add!(data_model, "linecode", create_linecode("3_conds", rs=ones(3, 3), xs=ones(3, 3))) - add!(data_model, "linecode", create_linecode("2_conds", rs=ones(2, 2), xs=ones(2, 2))) + add!(data_model, "linecode", create_linecode(id="6_conds", rs=ones(6, 6), xs=ones(6, 6))) + add!(data_model, "linecode", create_linecode(id="4_conds", rs=ones(4, 4), xs=ones(4, 4))) + add!(data_model, "linecode", create_linecode(id="3_conds", rs=ones(3, 3), xs=ones(3, 3))) + add!(data_model, "linecode", create_linecode(id="2_conds", rs=ones(2, 2), xs=ones(2, 2))) # 3 phase + 3 neutral conductors - add!(data_model, "line", create_line("1", "1", "2", "6_conds", 1; f_connections=[1,2,3,4,4,4], t_connections=collect(1:6))) - add!(data_model, "line", create_line("2", "2", "3", "6_conds", 1; f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4])) + add!(data_model, "line", create_line(id="1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6))) + add!(data_model, "line", create_line(id="2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4])) # 3 phase + 1 neutral conductors - add!(data_model, "line", create_line("3", "3", "4", "4_conds", 1.2)) + add!(data_model, "line", create_line(id="3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2)) # 3 phase conductors - add!(data_model, "line", create_line("4", "4", "5", "3_conds", 1.3; f_connections=collect(1:3), t_connections=collect(1:3))) + add!(data_model, "line", create_line(id="4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3))) # 2 phase + 1 neutral conductors - add!(data_model, "line", create_line("5", "4", "6", "3_conds", 1.3, f_connections=[1,3,4], t_connections=[1,3,4])) + add!(data_model, "line", create_line(id="5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4])) # 1 phase + 1 neutral conductors - add!(data_model, "line", create_line("6", "4", "7", "2_conds", 1.7, f_connections=[2,4], t_connections=[2,4])) + add!(data_model, "line", create_line(id="6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4])) # 2 phase conductors - add!(data_model, "line", create_line("7", "4", "8", "2_conds", 1.3, f_connections=[1,2], t_connections=[1,2])) + add!(data_model, "line", create_line(id="7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2])) for i in 8:1000 - add!(data_model, "line", create_line("$i", "4", "8", "2_conds", 1.3, f_connections=[1,2], t_connections=[1,2])) + add!(data_model, "line", create_line(id="$i", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2])) end - add!(data_model, "bus", create_bus("1", terminals=collect(1:4))) - add!(data_model, "bus", create_bus("2", terminals=collect(1:6))) - add!(data_model, "bus", create_bus("3", terminals=collect(1:4))) - add!(data_model, "bus", create_bus("4")) - add!(data_model, "bus", create_bus("5", terminals=collect(1:3))) - add!(data_model, "bus", create_bus("6", terminals=[1,3,4])) - add!(data_model, "bus", create_bus("7", terminals=[2,4])) - add!(data_model, "bus", create_bus("8", terminals=[1,2])) - add!(data_model, "bus", create_bus("9", terminals=[1,2,3,4])) - add!(data_model, "bus", create_bus("10", terminals=[1,2,3])) + add!(data_model, "bus", create_bus(id="1", terminals=collect(1:4))) + add!(data_model, "bus", create_bus(id="2", terminals=collect(1:6))) + add!(data_model, "bus", create_bus(id="3", terminals=collect(1:4))) + add!(data_model, "bus", create_bus(id="4")) + add!(data_model, "bus", create_bus(id="5", terminals=collect(1:3))) + add!(data_model, "bus", create_bus(id="6", terminals=[1,3,4])) + add!(data_model, "bus", create_bus(id="7", terminals=[2,4])) + add!(data_model, "bus", create_bus(id="8", terminals=[1,2])) + add!(data_model, "bus", create_bus(id="9", terminals=[1,2,3,4])) + add!(data_model, "bus", create_bus(id="10", terminals=[1,2,3])) # - add!(data_model, "load", create_load("1", "7", connections=[2,4], pd=[1.0], qd=[1.0])) - add!(data_model, "load", create_load("2", "8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)])) - add!(data_model, "load", create_load("3", "6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230])) - add!(data_model, "load", create_load("4", "6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5])) - add!(data_model, "load", create_load("5", "4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3))) - add!(data_model, "load", create_load("6", "4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3))) - add!(data_model, "load", create_load("7", "4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3))) - add!(data_model, "load", create_load("8", "4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5])) - add!(data_model, "load", create_load("9", "5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3))) - - add!(data_model, "generator", create_generator("1", "1", configuration="wye")) - - add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["5", "9", "10"], [[1,2,3], [1,2,3,4], [1,2,3]], - [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], - configuration=["delta", "wye", "delta"], - xsc=[0.0, 0.0, 0.0], - rs=[0.0, 0.0, 1.0], - loadloss=0.05, - imag=0.05, + add!(data_model, "load", create_load(id="1", bus="7", connections=[2,4], pd=[1.0], qd=[1.0])) + add!(data_model, "load", create_load(id="2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)])) + add!(data_model, "load", create_load(id="3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230])) + add!(data_model, "load", create_load(id="4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5])) + add!(data_model, "load", create_load(id="5", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3))) + add!(data_model, "load", create_load(id="6", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3))) + add!(data_model, "load", create_load(id="7", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3))) + add!(data_model, "load", create_load(id="8", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5])) + add!(data_model, "load", create_load(id="9", bus="5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3))) + + add!(data_model, "generator", create_generator(id="1", bus="1", configuration="wye")) + + add!(data_model, "transformer_nw", create_transformer_nw(id="1", bus=["5", "9", "10"], connections=[[1,2,3], [1,2,3,4], [1,2,3]], + vnom=[0.230, 0.230, 0.230], snom=[0.230, 0.230, 0.230], + configuration=["delta", "wye", "delta"], + xsc=[0.0, 0.0, 0.0], + rs=[0.0, 0.0, 1.0], + loadloss=0.05, + imag=0.05, )) - add!(data_model, "capacitor", create_capacitor("cap_3ph", "3", 0.230*sqrt(3), qd_ref=[1, 2, 3])) - add!(data_model, "capacitor", create_capacitor("cap_3ph_delta", "4", 0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3])) - add!(data_model, "capacitor", create_capacitor("cap_2ph_yg", "6", 0.230*sqrt(3), qd_ref=[1, 2], connections=[1,2], configuration="wye-grounded")) - add!(data_model, "capacitor", create_capacitor("cap_2ph_yfl", "6", 0.230*sqrt(3), qd_ref=[1, 2], connections=[1,2], configuration="wye-floating")) - add!(data_model, "capacitor", create_capacitor("cap_2ph_y", "5", 0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4])) + add!(data_model, "capacitor", create_capacitor(id="cap_3ph", bus="3", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3])) + add!(data_model, "capacitor", create_capacitor(id="cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3])) + add!(data_model, "capacitor", create_capacitor(id="cap_2ph_yg", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,2], configuration="wye-grounded")) + add!(data_model, "capacitor", create_capacitor(id="cap_2ph_yfl", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,2], configuration="wye-floating")) + add!(data_model, "capacitor", create_capacitor(id="cap_2ph_y", bus="5", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4])) return data_model end @@ -77,14 +77,18 @@ function make_3wire_data_model() data_model = create_data_model() - add!(data_model, "linecode", create_linecode("3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3)))) + add!(data_model, "linecode", create_linecode(id="3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3)))) + + add!(data_model, "voltage_source", create_voltage_source(id="source", bus="sourcebus", vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], + #pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), + )) # 3 phase conductors - add!(data_model, "line", create_line(:test, "source", "tr_prim", "3_conds", 1.3; f_connections=collect(1:3), t_connections=collect(1:3))) + add!(data_model, "line", create_line(id=:test, f_bus="sourcebus", t_bus="tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3))) - add!(data_model, "bus", create_bus("source", terminals=collect(1:4))) - add!(data_model, "bus", create_bus("tr_prim", terminals=collect(1:4))) - add!(data_model, "bus", create_bus("tr_sec", terminals=collect(1:4))) + add!(data_model, "bus", create_bus(id="sourcebus", terminals=collect(1:3))) + add!(data_model, "bus", create_bus(id="tr_prim", terminals=collect(1:4))) + add!(data_model, "bus", create_bus(id="tr_sec", terminals=collect(1:4))) #add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) # add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], @@ -96,27 +100,29 @@ function make_3wire_data_model() # imag=0.00, # )) - add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["tr_prim", "tr_sec"], [[1,2,3,4], [1,2,3,4]], - [0.230, 0.230], [0.230, 0.230], - configuration=["wye", "wye"], - xsc=[0.0], - rs=[0.0, 0.0], - loadloss=0.00, - imag=0.00, + add!(data_model, "transformer_nw", create_transformer_nw(id="1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], + vnom=[0.230, 0.230], snom=[0.230, 0.230], + configuration=["wye", "wye"], + xsc=[0.0], + rs=[0.0, 0.0], + noloadloss=0.00, + imag=0.00, )) # - add!(data_model, "load", create_load("1", "tr_sec", connections=collect(1:4), pd=[1.0, 2.0, 3.0]/100, qd=[1.0, 2.0, 3.0]/100)) - - add!(data_model, "generator", create_generator("1", "source", - connections=[1, 2, 3, 4], - pg_min=fill(-100, 3), - pg_max=fill( 100, 3), - qg_min=fill(-100, 3), - qg_max=fill( 100, 3), - )) + add!(data_model, "load", create_load(id="1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0])) + + # add!(data_model, "generator", create_generator("1", "source", + # connections=[1, 2, 3, 4], + # pg_min=fill(-100, 3), + # pg_max=fill( 100, 3), + # qg_min=fill(-100, 3), + # qg_max=fill( 100, 3), + # )) + + #add!(data_model, "capacitor", create_capacitor(1, "tr_sec", 0.230*sqrt(3), qd_ref=[1.0, 1.0, 1.0]*1E-3, connections=[1,2,3], configuration="delta")) return data_model end @@ -126,10 +132,13 @@ data_model = make_3wire_data_model() check_data_model(data_model) data_model_map!(data_model) -data_model_make_pu!(data_model, vbases=Dict("source"=>230.0)) +#bsh = data_model["shunt"]["_virtual_1"]["b_sh"] +# +data_model_make_pu!(data_model, vbases=Dict("sourcebus"=>0.230)) +# data_model_index!(data_model) data_model_make_compatible_v8!(data_model) -## +# import PowerModelsDistribution PMD = PowerModelsDistribution import PowerModels @@ -140,10 +149,13 @@ IM = InfrastructureModels import JuMP, Ipopt ipopt_solver = JuMP.with_optimizer(Ipopt.Optimizer) -pm = PMs.instantiate_model(data_model, PMs.ACPPowerModel, PMD.build_mc_opf, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) +pm = PMs.instantiate_model(data_model, PMs.IVRPowerModel, PMD.build_mc_opf_iv, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) solution_unmake_pu!(sol["solution"], data_model) solution_identify!(sol["solution"], data_model) solution_unmap!(sol["solution"], data_model) +#vm = sol["solution"]["bus"]["tr_sec"]["vm"] +#va = sol["solution"]["bus"]["tr_sec"]["va"] +#v = vm.*exp.(im*va/180*pi) sol["solution"] diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl index 86ddd7838..c4bd442d0 100644 --- a/src/io/data_model_util.jl +++ b/src/io/data_model_util.jl @@ -25,13 +25,22 @@ function add_virtual_get_id!(data_model, comp_type, comp) return id end -function delete_component!(data_model, comp_type, comp) +add_virtual! = add_virtual_get_id! + +function delete_component!(data_model, comp_type, comp::Dict) delete!(data_model[comp_type], comp["id"]) if isempty(data_model[comp_type]) delete!(data_model, comp_type) end end +function delete_component!(data_model, comp_type, id::Any) + delete!(data_model[comp_type], id) + if isempty(data_model[comp_type]) + delete!(data_model, comp_type) + end +end + function add_mappings!(data_model::Dict{String, Any}, mapping_type::String, mappings::Vector) if !haskey(data_model, "mappings") data_model["mappings"] = [] @@ -97,3 +106,23 @@ function add_solution!(solution, comp_type, id, data) solution[comp_type][id][key] = prop end end + + +function delete_solution!(solution, comp_type, id, props) + if haskey(solution, comp_type) + if haskey(solution[comp_type], id) + for prop in props + delete!(solution[comp_type][id], prop) + end + end + end +end + + +function delete_solution!(solution, comp_type, id) + if haskey(solution, comp_type) + if haskey(solution[comp_type], id) + delete!(solution[comp_type], id) + end + end +end From c48b05d71cad91d6860338a7d75f14d42611e9f9 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Sat, 15 Feb 2020 12:25:08 -0700 Subject: [PATCH 009/224] create/add functions --- src/PowerModelsDistribution.jl | 8 +-- src/core/data_model_mapping.jl | 41 ++++++------ src/core/variable.jl | 2 - src/form/acp.jl | 2 - src/io/data_model_components.jl | 22 ++++++- src/io/data_model_test.jl | 112 +++++++++++++++----------------- 6 files changed, 99 insertions(+), 88 deletions(-) diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 3b67ddae2..87521fa65 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -47,10 +47,10 @@ module PowerModelsDistribution #include("io/common_dm.jl") #include("io/opendss_dm.jl") - # include("io/data_model_components.jl") - # include("core/data_model_mapping.jl") - # include("core/data_model_pu.jl") - # include("io/data_model_util.jl") + include("io/data_model_components.jl") + include("core/data_model_mapping.jl") + include("core/data_model_pu.jl") + include("io/data_model_util.jl") include("prob/mld.jl") include("prob/opf.jl") diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index d374e4714..35a8ea147 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -121,28 +121,31 @@ end function _decompose_voltage_source!(data_model) mappings = [] - for (id, vs) in data_model["voltage_source"] - gen = create_generator("", vs["bus"], connections=vs["connections"]) - @show vs - for prop in ["pg_max", "pg_min", "qg_max", "qg_min"] - if haskey(vs, prop) - gen[prop] = vs[prop] + + if haskey(data_model, "voltage_source") + for (id, vs) in data_model["voltage_source"] + gen = create_generator(bus=vs["bus"], connections=vs["connections"]) + + for prop in ["pg_max", "pg_min", "qg_max", "qg_min"] + if haskey(vs, prop) + gen[prop] = vs[prop] + end end - end - gen_id = add_virtual_get_id!(data_model, "generator", gen) + gen_id = add_virtual_get_id!(data_model, "generator", gen) - bus = data_model["bus"][vs["bus"]] - conns = vs["connections"] - terminals = bus["terminals"] + bus = data_model["bus"][vs["bus"]] + conns = vs["connections"] + terminals = bus["terminals"] - @assert(Set(conns)==Set(terminals), "A voltage source should connect to all terminals of its associated bus!") - tmp = Dict(enumerate(conns)) - bus["vm"] = bus["vmax"] = bus["vmin"] = [vs["vm"][tmp[t]] for t in terminals] - bus["va"] = [vs["va"][tmp[t]] for t in terminals] - bus["bus_type"] = 3 + @assert(Set(conns)==Set(terminals), "A voltage source should connect to all terminals of its associated bus!") + tmp = Dict(enumerate(conns)) + bus["vm"] = bus["vmax"] = bus["vmin"] = [vs["vm"][tmp[t]] for t in terminals] + bus["va"] = [vs["va"][tmp[t]] for t in terminals] + bus["bus_type"] = 3 - delete_component!(data_model, "voltage_source", vs["id"]) - push!(mappings, Dict("voltage_source"=>vs, "gen_id"=>gen_id)) + delete_component!(data_model, "voltage_source", vs["id"]) + push!(mappings, Dict("voltage_source"=>vs, "gen_id"=>gen_id)) + end end return mappings @@ -335,7 +338,7 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) bus_ids = Dict() for bus in buses - bus_ids[bus] = add_virtual_get_id!(data_model, "bus", create_bus("")) + bus_ids[bus] = add_virtual_get_id!(data_model, "bus", create_bus(id="")) end line_ids = Dict() for (l,(i,j)) in lines diff --git a/src/core/variable.jl b/src/core/variable.jl index 469b31da1..1ad65dbcb 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -41,7 +41,6 @@ function variable_mc_voltage_magnitude(pm::_PMs.AbstractPowerModel; nw::Int=pm.c if bounded for (i,bus) in _PMs.ref(pm, nw, :bus) if haskey(bus, "vmin") - @show vm, bus["vmin"] JuMP.set_lower_bound.(vm[i], bus["vmin"]) end if haskey(bus, "vmax") @@ -248,7 +247,6 @@ function variable_mc_branch_series_current_real(pm::_PMs.AbstractPowerModel; nw: for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) cmax = _calc_branch_series_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) if !ismissing(cmax) - @show cmax JuMP.set_upper_bound.(csr[l], cmax) JuMP.set_lower_bound.(csr[l], -cmax) end diff --git a/src/form/acp.jl b/src/form/acp.jl index 1a056e116..11749330a 100644 --- a/src/form/acp.jl +++ b/src/form/acp.jl @@ -281,8 +281,6 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACPModel, nw::Int, i: cstr_p = [] cstr_q = [] - @show(bus_loads) - for c in _PMs.conductor_ids(pm; nw=nw) cp = JuMP.@NLconstraint(pm.model, sum(p[a][c] for a in bus_arcs) diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl index 0b2bdcaf4..621c4d528 100644 --- a/src/io/data_model_components.jl +++ b/src/io/data_model_components.jl @@ -202,6 +202,7 @@ function create_linecode(; kwargs...) add_kwarg!(linecode, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) _add_unused_kwargs!(linecode, kwargs) + return linecode end @@ -267,6 +268,7 @@ function create_line(; kwargs...) add_kwarg!(line, kwargs, :angmax, fill( 60/180*pi, N)) _add_unused_kwargs!(line, kwargs) + return line end @@ -316,6 +318,7 @@ function create_bus(; kwargs...) add_kwarg!(bus, kwargs, :xg, Array{Float64, 1}()) _add_unused_kwargs!(bus, kwargs) + return bus end @@ -377,6 +380,7 @@ function create_load(; kwargs...) end _add_unused_kwargs!(load, kwargs) + return load end @@ -415,6 +419,7 @@ function create_generator(; kwargs...) add_kwarg!(generator, kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) _add_unused_kwargs!(generator, kwargs) + return generator end @@ -485,6 +490,7 @@ function create_transformer_nw(; kwargs...) add_kwarg!(trans, kwargs, :tm_fix, fill(fill(true, 3), n_windings)) _add_unused_kwargs!(trans, kwargs) + return trans end @@ -565,11 +571,13 @@ end function create_capacitor(; kwargs...) cap = Dict{String,Any}() + add_kwarg!(cap, kwargs, :status, 1) add_kwarg!(cap, kwargs, :configuration, "wye") add_kwarg!(cap, kwargs, :connections, collect(1:4)) add_kwarg!(cap, kwargs, :qd_ref, fill(0.0, 3)) _add_unused_kwargs!(cap, kwargs) + return cap end @@ -580,12 +588,12 @@ DTYPES[:shunt] = Dict( :id => Any, :status => Int, :bus => String, - :terminals => Array{Int, 1}, + :connections => Array{Int, 1}, :g_sh => Array{<:Real, 2}, :b_sh => Array{<:Real, 2}, ) -REQUIRED_FIELDS[:capacitor] = keys(DTYPES[:shunt]) +REQUIRED_FIELDS[:shunt] = keys(DTYPES[:shunt]) CHECKS[:shunt] = function check_shunt(data, shunt) @@ -594,7 +602,7 @@ CHECKS[:shunt] = function check_shunt(data, shunt) end -function create_shunt(; kwargs...) +function add_shunt!(; kwargs...) shunt = Dict{String,Any}() add_kwarg!(shunt, kwargs, :status, 1) @@ -602,6 +610,7 @@ function create_shunt(; kwargs...) add_kwarg!(shunt, kwargs, :b_sh, fill(0.0, length(terminals), length(terminals))) _add_unused_kwargs!(shunt, kwargs) + return shunt end @@ -639,5 +648,12 @@ function create_voltage_source(; kwargs...) add_kwarg!(vs, kwargs, :connections, collect(1:3)) _add_unused_kwargs!(vs, kwargs) + return vs end + + +# create add_comp! methods +for comp in keys(DTYPES) + eval(Meta.parse("add_$(comp)!(data_model; kwargs...) = add!(data_model, \"$comp\", create_$comp(; kwargs...))")) +end diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl index 0bf7cd7bb..f9fa7576c 100644 --- a/src/io/data_model_test.jl +++ b/src/io/data_model_test.jl @@ -1,73 +1,69 @@ -BASE_DIR = "/Users/sclaeys/code/PowerModelsDistribution.jl/src/io" -include("$BASE_DIR/data_model_util.jl") -include("$BASE_DIR/data_model_components.jl") -include("$BASE_DIR/../core/data_model_mapping.jl") -include("$BASE_DIR/../core/data_model_pu.jl") +using PowerModelsDistribution function make_test_data_model() data_model = create_data_model() - add!(data_model, "linecode", create_linecode(id="6_conds", rs=ones(6, 6), xs=ones(6, 6))) - add!(data_model, "linecode", create_linecode(id="4_conds", rs=ones(4, 4), xs=ones(4, 4))) - add!(data_model, "linecode", create_linecode(id="3_conds", rs=ones(3, 3), xs=ones(3, 3))) - add!(data_model, "linecode", create_linecode(id="2_conds", rs=ones(2, 2), xs=ones(2, 2))) + add_linecode!(data_model, id="6_conds", rs=ones(6, 6), xs=ones(6, 6)) + add_linecode!(data_model, id="4_conds", rs=ones(4, 4), xs=ones(4, 4)) + add_linecode!(data_model, id="3_conds", rs=ones(3, 3), xs=ones(3, 3)) + add_linecode!(data_model, id="2_conds", rs=ones(2, 2), xs=ones(2, 2)) # 3 phase + 3 neutral conductors - add!(data_model, "line", create_line(id="1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6))) - add!(data_model, "line", create_line(id="2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4])) + add_line!(data_model, id="1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6)) + add_line!(data_model, id="2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4]) # 3 phase + 1 neutral conductors - add!(data_model, "line", create_line(id="3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2)) + add_line!(data_model, id="3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2) # 3 phase conductors - add!(data_model, "line", create_line(id="4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3))) + add_line!(data_model, id="4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) # 2 phase + 1 neutral conductors - add!(data_model, "line", create_line(id="5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4])) + add_line!(data_model, id="5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4]) # 1 phase + 1 neutral conductors - add!(data_model, "line", create_line(id="6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4])) + add_line!(data_model, id="6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4]) # 2 phase conductors - add!(data_model, "line", create_line(id="7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2])) + add_line!(data_model, id="7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) for i in 8:1000 - add!(data_model, "line", create_line(id="$i", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2])) + add_line!(data_model, id="$i", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) end - add!(data_model, "bus", create_bus(id="1", terminals=collect(1:4))) - add!(data_model, "bus", create_bus(id="2", terminals=collect(1:6))) - add!(data_model, "bus", create_bus(id="3", terminals=collect(1:4))) - add!(data_model, "bus", create_bus(id="4")) - add!(data_model, "bus", create_bus(id="5", terminals=collect(1:3))) - add!(data_model, "bus", create_bus(id="6", terminals=[1,3,4])) - add!(data_model, "bus", create_bus(id="7", terminals=[2,4])) - add!(data_model, "bus", create_bus(id="8", terminals=[1,2])) - add!(data_model, "bus", create_bus(id="9", terminals=[1,2,3,4])) - add!(data_model, "bus", create_bus(id="10", terminals=[1,2,3])) + add_bus!(data_model, id="1", terminals=collect(1:4)) + add_bus!(data_model, id="2", terminals=collect(1:6)) + add_bus!(data_model, id="3", terminals=collect(1:4)) + add_bus!(data_model, id="4") + add_bus!(data_model, id="5", terminals=collect(1:4)) + add_bus!(data_model, id="6", terminals=[1,3,4]) + add_bus!(data_model, id="7", terminals=[2,4]) + add_bus!(data_model, id="8", terminals=[1,2]) + add_bus!(data_model, id="9", terminals=[1,2,3,4]) + add_bus!(data_model, id="10", terminals=[1,2,3]) # - add!(data_model, "load", create_load(id="1", bus="7", connections=[2,4], pd=[1.0], qd=[1.0])) - add!(data_model, "load", create_load(id="2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)])) - add!(data_model, "load", create_load(id="3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230])) - add!(data_model, "load", create_load(id="4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5])) - add!(data_model, "load", create_load(id="5", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3))) - add!(data_model, "load", create_load(id="6", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3))) - add!(data_model, "load", create_load(id="7", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3))) - add!(data_model, "load", create_load(id="8", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5])) - add!(data_model, "load", create_load(id="9", bus="5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3))) - - add!(data_model, "generator", create_generator(id="1", bus="1", configuration="wye")) - - add!(data_model, "transformer_nw", create_transformer_nw(id="1", bus=["5", "9", "10"], connections=[[1,2,3], [1,2,3,4], [1,2,3]], + add_load!(data_model, id="1", bus="7", connections=[2,4], pd=[1.0], qd=[1.0]) + add_load!(data_model, id="2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)]) + add_load!(data_model, id="3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230]) + add_load!(data_model, id="4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5]) + add_load!(data_model, id="5", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3)) + add_load!(data_model, id="6", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3)) + add_load!(data_model, id="7", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3)) + add_load!(data_model, id="8", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]) + add_load!(data_model, id="9", bus="5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3)) + + add_generator!(data_model, id="1", bus="1", configuration="wye") + + add_transformer_nw!(data_model, id="1", bus=["5", "9", "10"], connections=[[1,2,3], [1,2,3,4], [1,2,3]], vnom=[0.230, 0.230, 0.230], snom=[0.230, 0.230, 0.230], configuration=["delta", "wye", "delta"], xsc=[0.0, 0.0, 0.0], rs=[0.0, 0.0, 1.0], - loadloss=0.05, + noloadloss=0.05, imag=0.05, - )) + ) - add!(data_model, "capacitor", create_capacitor(id="cap_3ph", bus="3", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3])) - add!(data_model, "capacitor", create_capacitor(id="cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3])) - add!(data_model, "capacitor", create_capacitor(id="cap_2ph_yg", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,2], configuration="wye-grounded")) - add!(data_model, "capacitor", create_capacitor(id="cap_2ph_yfl", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,2], configuration="wye-floating")) - add!(data_model, "capacitor", create_capacitor(id="cap_2ph_y", bus="5", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4])) + add_capacitor!(data_model, id="cap_3ph", bus="3", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3]) + add_capacitor!(data_model, id="cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3]) + add_capacitor!(data_model, id="cap_2ph_yg", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-grounded") + add_capacitor!(data_model, id="cap_2ph_yfl", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-floating") + add_capacitor!(data_model, id="cap_2ph_y", bus="5", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4]) return data_model end @@ -77,18 +73,18 @@ function make_3wire_data_model() data_model = create_data_model() - add!(data_model, "linecode", create_linecode(id="3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3)))) + add_linecode!(data_model, id="3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3))) - add!(data_model, "voltage_source", create_voltage_source(id="source", bus="sourcebus", vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], + add_voltage_source!(data_model, id="source", bus="sourcebus", vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], #pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), - )) + ) # 3 phase conductors - add!(data_model, "line", create_line(id=:test, f_bus="sourcebus", t_bus="tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3))) + add_line!(data_model, id=:test, f_bus="sourcebus", t_bus="tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) - add!(data_model, "bus", create_bus(id="sourcebus", terminals=collect(1:3))) - add!(data_model, "bus", create_bus(id="tr_prim", terminals=collect(1:4))) - add!(data_model, "bus", create_bus(id="tr_sec", terminals=collect(1:4))) + add_bus!(data_model, id="sourcebus", terminals=collect(1:3)) + add_bus!(data_model, id="tr_prim", terminals=collect(1:4)) + add_bus!(data_model, id="tr_sec", terminals=collect(1:4)) #add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) # add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], @@ -100,19 +96,19 @@ function make_3wire_data_model() # imag=0.00, # )) - add!(data_model, "transformer_nw", create_transformer_nw(id="1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], + add_transformer_nw!(data_model, id="1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], vnom=[0.230, 0.230], snom=[0.230, 0.230], configuration=["wye", "wye"], xsc=[0.0], rs=[0.0, 0.0], noloadloss=0.00, imag=0.00, - )) + ) # - add!(data_model, "load", create_load(id="1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0])) + add_load!(data_model, id="1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0]) # add!(data_model, "generator", create_generator("1", "source", # connections=[1, 2, 3, 4], @@ -130,7 +126,7 @@ end data_model = make_3wire_data_model() check_data_model(data_model) - +## data_model_map!(data_model) #bsh = data_model["shunt"]["_virtual_1"]["b_sh"] # From cc65208f388723074bda090d40950cd40a6b67fc Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Thu, 20 Feb 2020 19:31:40 +0100 Subject: [PATCH 010/224] stash changes --- src/PowerModelsDistribution.jl | 3 + src/core/constraint.jl | 8 +- src/core/constraint_template.jl | 11 +- src/core/data.jl | 84 ++-- src/core/data_model_mapping.jl | 160 +++++-- src/core/data_model_pu.jl | 50 ++- src/core/variable.jl | 42 +- src/form/acp.jl | 24 +- src/form/apo.jl | 68 +-- src/form/dcp.jl | 16 +- src/form/ivr.jl | 12 +- src/io/common_dm.jl | 4 + src/io/data_model.md | 188 ++++++++ src/io/data_model_components.jl | 76 +++- src/io/data_model_test.jl | 14 +- src/io/data_model_util.jl | 44 +- src/io/opendss.jl | 7 + src/io/opendss_dm.jl | 419 ++++++++---------- test/data/opendss/case2_diag.dss | 2 +- test/data/opendss/case3_balanced.dss | 4 +- test/data/opendss/case3_balanced_basefreq.dss | 4 +- test/data/opendss/case3_balanced_battery.dss | 4 +- test/data/opendss/case3_balanced_cap.dss | 4 +- test/data/opendss/case3_balanced_isc.dss | 4 +- .../opendss/case3_balanced_prop-order.dss | 4 +- test/data/opendss/case3_balanced_pv.dss | 4 +- test/data/opendss/case3_balanced_switch.dss | 4 +- test/data/opendss/case3_delta_gens.dss | 4 +- test/data/opendss/case3_lm_1230.dss | 4 +- test/data/opendss/case3_lm_models.dss | 4 +- test/data/opendss/case3_unbalanced.dss | 4 +- .../opendss/case3_unbalanced_1phase-pv.dss | 4 +- .../opendss/case3_unbalanced_assym_swap.dss | 6 +- test/data/opendss/case5_phase_drop.dss | 2 +- test/data/opendss/case_mxshunt.dss | 2 +- test/data/opendss/case_mxshunt_2.dss | 2 +- test/data/opendss/loadparser_error.dss | 2 +- test/data/opendss/loadparser_warn_model.dss | 2 +- test/data/opendss/test2_master.dss | 2 +- test/data/opendss/virtual_sourcebus.dss | 2 +- 40 files changed, 826 insertions(+), 478 deletions(-) create mode 100644 src/io/data_model.md diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 87521fa65..2b1af1fe1 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -39,6 +39,9 @@ module PowerModelsDistribution include("core/constraint_template.jl") include("core/relaxation_scheme.jl") + include("io/common_dm.jl") + include("io/opendss_dm.jl") + include("io/common.jl") include("io/json.jl") include("io/dss_parse.jl") diff --git a/src/core/constraint.jl b/src/core/constraint.jl index bb68b72b5..9a2f54cad 100644 --- a/src/core/constraint.jl +++ b/src/core/constraint.jl @@ -4,11 +4,11 @@ end # Generic thermal limit constraint "" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, n::Int, f_idx, rate_a) +function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, n::Int, f_idx, s_rating) p_fr = _PMs.var(pm, n, :p, f_idx) q_fr = _PMs.var(pm, n, :q, f_idx) - mu_sm_fr = JuMP.@constraint(pm.model, p_fr.^2 + q_fr.^2 .<= rate_a.^2) + mu_sm_fr = JuMP.@constraint(pm.model, p_fr.^2 + q_fr.^2 .<= s_rating.^2) if _PMs.report_duals(pm) _PMs.sol(pm, n, :branch, f_idx[1])[:mu_sm_fr] = mu_sm_fr @@ -16,11 +16,11 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, n::Int, f end "" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractPowerModel, n::Int, t_idx, rate_a) +function constraint_mc_thermal_limit_to(pm::_PMs.AbstractPowerModel, n::Int, t_idx, s_rating) p_to = _PMs.var(pm, n, :p, t_idx) q_to = _PMs.var(pm, n, :q, t_idx) - mu_sm_to = JuMP.@constraint(pm.model, p_to.^2 + q_to.^2 .<= rate_a.^2) + mu_sm_to = JuMP.@constraint(pm.model, p_to.^2 + q_to.^2 .<= s_rating.^2) if _PMs.report_duals(pm) _PMs.sol(pm, n, :branch, t_idx[1])[:mu_sm_to] = mu_sm_to diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 99b653a02..9a452a3ac 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -143,7 +143,8 @@ function constraint_mc_trans(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw #TODO change data model # there is redundancy in specifying polarity seperately on from and to side - pol = trans["polarity"] + #TODO change this once migrated to new data model + pol = haskey(trans, "poalrity") ? trans["polarity"] : trans["config_fr"]["polarity"] if config=="wye" constraint_mc_trans_yy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) @@ -388,8 +389,8 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, i::Int; n t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) - if haskey(branch, "rate_a") - constraint_mc_thermal_limit_from(pm, nw, f_idx, branch["rate_a"]) + if haskey(branch, "s_rating") + constraint_mc_thermal_limit_from(pm, nw, f_idx, branch["s_rating"]) end end @@ -401,8 +402,8 @@ function constraint_mc_thermal_limit_to(pm::_PMs.AbstractPowerModel, i::Int; nw: t_bus = branch["t_bus"] t_idx = (i, t_bus, f_bus) - if haskey(branch, "rate_a") - constraint_mc_thermal_limit_to(pm, nw, t_idx, branch["rate_a"]) + if haskey(branch, "s_rating") + constraint_mc_thermal_limit_to(pm, nw, t_idx, branch["s_rating"]) end end diff --git a/src/core/data.jl b/src/core/data.jl index 5017d2520..60c7b6024 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -114,8 +114,9 @@ end "Calculates the tap scale factor for the non-dimensionalized equations." function calculate_tm_scale(trans::Dict{String,Any}, bus_fr::Dict{String,Any}, bus_to::Dict{String,Any}) tm_nom = trans["tm_nom"] - f_vbase = bus_fr["vbase"] - t_vbase = bus_to["vbase"] + @show bus_fr + f_vbase = haskey(bus_fr, "vbase") ? bus_fr["vbase"] : bus_fr["base_kv"] + t_vbase = haskey(bus_to, "vbase") ? bus_to["vbase"] : bus_to["base_kv"] config = trans["configuration"] tm_scale = tm_nom*(t_vbase/f_vbase) @@ -293,11 +294,11 @@ lower bound on the voltage magnitude of the connected buses. """ function _calc_branch_current_max(branch::Dict, bus::Dict) bounds = [] - if haskey(branch, "c_rating_a") - push!(bounds, branch["c_rating_a"]) + if haskey(branch, "c_rating") + push!(bounds, branch["c_rating"]) end - if haskey(branch, "rate_a") && haskey(bus, "vmin") - push!(bounds, branch["rate_a"]./bus["vmin"]) + if haskey(branch, "s_rating") && haskey(bus, "vmin") + push!(bounds, branch["s_rating"]./bus["vmin"]) end if length(bounds)==0 return missing @@ -314,13 +315,13 @@ lower bound on the voltage magnitude of the connected buses. function _calc_branch_current_max_frto(branch::Dict, bus_fr::Dict, bus_to::Dict) bounds_fr = [] bounds_to = [] - if haskey(branch, "c_rating_a") - push!(bounds_fr, branch["c_rating_a"]) - push!(bounds_to, branch["c_rating_a"]) + if haskey(branch, "c_rating") + push!(bounds_fr, branch["c_rating"]) + push!(bounds_to, branch["c_rating"]) end - if haskey(branch, "rate_a") - push!(bounds_fr, branch["rate_a"]./bus_fr["vmin"]) - push!(bounds_to, branch["rate_a"]./bus_to["vmin"]) + if haskey(branch, "s_rating") + push!(bounds_fr, branch["s_rating"]./bus_fr["vmin"]) + push!(bounds_to, branch["s_rating"]./bus_to["vmin"]) end @assert(length(bounds_fr)>=0, "no (implied/valid) current bounds defined") return min.(bounds_fr...), min.(bounds_to...) @@ -335,16 +336,18 @@ upper bound on the voltage magnitude of the connected buses. function _calc_transformer_power_ub_frto(trans::Dict, bus_fr::Dict, bus_to::Dict) bounds_fr = [] bounds_to = [] - if haskey(trans, "c_rating_a") - push!(bounds_fr, trans["c_rating_a"].*bus_fr["vmax"]) - push!(bounds_to, trans["c_rating_a"].*bus_to["vmax"]) + if haskey(trans, "c_rating") + push!(bounds_fr, trans["c_rating"].*bus_fr["vmax"]) + push!(bounds_to, trans["c_rating"].*bus_to["vmax"]) end - if haskey(trans, "rate_a") - push!(bounds_fr, trans["rate_a"]) - push!(bounds_to, trans["rate_a"]) + if haskey(trans, "s_rating") + push!(bounds_fr, trans["s_rating"]) + push!(bounds_to, trans["s_rating"]) end - @assert(length(bounds_fr)>=0, "no (implied/valid) current bounds defined") - return min.(bounds_fr...), min.(bounds_to...) + + bounds_fr = isempty(bounds_fr) ? missing : min.(bounds_fr...) + bounds_to = isempty(bounds_to) ? missing : min.(bounds_to...) + return bounds_fr, bounds_to end @@ -356,16 +359,18 @@ the voltage magnitude of the connected buses. function _calc_transformer_current_max_frto(trans::Dict, bus_fr::Dict, bus_to::Dict) bounds_fr = [] bounds_to = [] - # if haskey(trans, "c_rating_a") - # push!(bounds_fr, trans["c_rating_a"]) - # push!(bounds_to, trans["c_rating_a"]) + # if haskey(trans, "c_rating") + # push!(bounds_fr, trans["c_rating"]) + # push!(bounds_to, trans["c_rating"]) # end - # if haskey(trans, "rate_a") - # push!(bounds_fr, trans["rate_a"]./bus_fr["vmin"]) - # push!(bounds_to, trans["rate_a"]./bus_to["vmin"]) + # if haskey(trans, "s_rating") + # push!(bounds_fr, trans["s_rating"]./bus_fr["vmin"]) + # push!(bounds_to, trans["s_rating"]./bus_to["vmin"]) # end - # return min.(bounds_fr...), min.(bounds_to...) - return missing, missing + + bounds_fr = isempty(bounds_fr) ? missing : min.(bounds_fr...) + bounds_to = isempty(bounds_to) ? missing : min.(bounds_to...) + return bounds_fr, bounds_to end @@ -376,16 +381,15 @@ upper bound on the voltage magnitude of the connected buses. """ function _calc_branch_power_max(branch::Dict, bus::Dict) bounds = [] - if haskey(branch, "c_rating_a") && haskey(bus, "vmax") - push!(bounds, branch["c_rating_a"].*bus["vmax"]) - end - if haskey(branch, "rate_a") - push!(bounds, branch["rate_a"]) + if haskey(branch, "c_rating") && haskey(bus, "vmax") + push!(bounds, branch["c_rating"].*bus["vmax"]) end - if length(bounds)==0 - return missing + if haskey(branch, "s_rating") + push!(bounds, branch["s_rating"]) end - return min.(bounds...) + + bounds = isempty(bounds) ? missing : min.(bounds...) + return bounds end @@ -486,6 +490,14 @@ function _make_multiconductor!(data::Dict{String,<:Any}, conductors::Real) for (_, load) in data["gen"] load["conn"] = "wye" end + + #rename bounds from PMs to PMD + for (_, branch) in data["branch"] + if haskey(branch, "rate_a") + branch["s_rating"] = branch["rate_a"] + delete!(branch, "rate_a") + end + end end diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index 35a8ea147..e7e9415ef 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -6,9 +6,11 @@ function data_model_map!(data_model) !haskey(data_model, "mappings") - _expand_linecode!(data_model) + # needs to happen before _expand_linecode, as it might contain a linecode for the internal impedance add_mappings!(data_model, "decompose_voltage_source", _decompose_voltage_source!(data_model)) - add_mappings!(data_model, "load_to_shunt", _load_to_shunt!(data_model)) + _expand_linecode!(data_model) + # creates shunt of 4x4; disabled for now (incompatible 3-wire kron-reduced) + #add_mappings!(data_model, "load_to_shunt", _load_to_shunt!(data_model)) add_mappings!(data_model, "capacitor_to_shunt", _capacitor_to_shunt!(data_model)) add_mappings!(data_model, "decompose_transformer_nw", _decompose_transformer_nw!(data_model)) @@ -60,8 +62,8 @@ function _load_to_shunt!(data_model) Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) end - shunt = create_shunt(NaN, load["bus"], load["connections"], b_sh=imag.(Y), g_sh=real.(Y)) - add_component!(data_model, "shunt", shunt) + shunt = add_virtual!(data_model, "shunt", create_shunt(bus=load["bus"], connections=load["connections"], b_sh=imag.(Y), g_sh=real.(Y))) + delete_component!(data_model, "load", load) push!(mappings, Dict( @@ -124,27 +126,48 @@ function _decompose_voltage_source!(data_model) if haskey(data_model, "voltage_source") for (id, vs) in data_model["voltage_source"] - gen = create_generator(bus=vs["bus"], connections=vs["connections"]) + + bus = data_model["bus"][vs["bus"]] + + line_kwargs = Dict(Symbol(prop)=>vs[prop] for prop in ["rs", "xs", "g_fr", "b_fr", "g_to", "b_to", "linecode", "length"] if haskey(vs, prop)) + lossy = !isempty(line_kwargs) + + # if any loss parameters (or linecode) were supplied, then create a line and internal bus + if lossy + sourcebus = add_virtual!(data_model, "bus", create_bus(terminals=deepcopy(vs["connections"]))) + + line = add_virtual!(data_model, "line", create_line(; + f_bus=sourcebus["id"], f_connections=vs["connections"], t_bus=bus["id"], t_connections=vs["connections"], + line_kwargs... + )) + else + sourcebus = bus + end + + ground = _get_ground!(sourcebus) + gen = create_generator(bus=sourcebus["id"], connections=[vs["connections"]..., ground]) for prop in ["pg_max", "pg_min", "qg_max", "qg_min"] if haskey(vs, prop) gen[prop] = vs[prop] end end - gen_id = add_virtual_get_id!(data_model, "generator", gen) - bus = data_model["bus"][vs["bus"]] + add_virtual!(data_model, "generator", gen) + conns = vs["connections"] terminals = bus["terminals"] - @assert(Set(conns)==Set(terminals), "A voltage source should connect to all terminals of its associated bus!") tmp = Dict(enumerate(conns)) - bus["vm"] = bus["vmax"] = bus["vmin"] = [vs["vm"][tmp[t]] for t in terminals] - bus["va"] = [vs["va"][tmp[t]] for t in terminals] - bus["bus_type"] = 3 + sourcebus["vm"] = sourcebus["vmax"] = sourcebus["vmin"] = [haskey(tmp, t) ? vs["vm"][tmp[t]] : NaN for t in terminals] + sourcebus["va"] = [haskey(tmp, t) ? vs["va"][tmp[t]] : NaN for t in terminals] + sourcebus["bus_type"] = 3 delete_component!(data_model, "voltage_source", vs["id"]) - push!(mappings, Dict("voltage_source"=>vs, "gen_id"=>gen_id)) + push!(mappings, Dict("voltage_source"=>vs, "gen_id"=>gen["id"], + "vbus_id" => lossy ? sourcebus["id"] : nothing, + "vline_id" => lossy ? line["id"] : nothing, + )) end end @@ -165,8 +188,9 @@ function _decompose_transformer_nw!(data_model) if haskey(data_model, "transformer_nw") for (tr_id, trans) in data_model["transformer_nw"] - vnom = trans["vnom"]*data_model["v_var_scalar"] - snom = trans["snom"]*data_model["v_var_scalar"] + @show trans + vnom = trans["vnom"]*data_model["settings"]["v_var_scalar"] + snom = trans["snom"]*data_model["settings"]["v_var_scalar"] nrw = length(trans["bus"]) @@ -193,12 +217,13 @@ function _decompose_transformer_nw!(data_model) vbuses, vlines, trans_t_bus_w = _build_loss_model!(data_model, r_s, z_sc, y_sh) - trans_w = Array{String, 1}(undef, nrw) + trans_ids_w = Array{String, 1}(undef, nrw) for w in 1:nrw # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] - trans_w[w] = add_virtual_get_id!(data_model, "transformer_2wa", Dict( + @show trans + trans_2wa = add_virtual!(data_model, "transformer_2wa", Dict( "f_bus" => trans["bus"][w], "t_bus" => trans_t_bus_w[w], "tm_nom" => tm_nom, @@ -207,18 +232,23 @@ function _decompose_transformer_nw!(data_model) "configuration" => trans["configuration"][w], "polarity" => trans["polarity"][w], "tm" => trans["tm"][w], - "tm_fix" => trans["tm_fix"][w], - "tm_max" => trans["tm_max"][w], - "tm_min" => trans["tm_min"][w], - "tm_step" => trans["tm_step"][w], + "fixed" => trans["fixed"][w], )) + + for prop in ["tm_min", "tm_max", "tm_step"] + if haskey(trans, prop) + trans_2wa[prop] = trans[prop][w] + end + end + + trans_ids_w[w] = trans_2wa["id"] end delete_component!(data_model, "transformer_nw", trans) push!(mappings, Dict( "trans"=>trans, - "trans_2wa"=>trans_w, + "trans_2wa"=>trans_ids_w, "vlines"=>vlines, "vbuses"=>vbuses, )) @@ -377,38 +407,92 @@ function _alias!(dict, fr, to) end end -function data_model_make_compatible_v8!(data_model) +function _pad_props!(comp, keys, phases_comp, phases_all) + pos = Dict((x,i) for (i,x) in enumerate(phases_all)) + inds = [pos[x] for x in phases_comp] + for prop in keys + if haskey(comp, prop) + if isa(comp[prop], Vector) + tmp = zeros(length(phases_all)) + tmp[inds] = comp[prop] + comp[prop] = tmp + elseif isa(comp[prop], Matrix) + tmp = zeros(length(phases_all), length(phases_all)) + tmp[inds, inds] = comp[prop] + comp[prop] = tmp + else + error("Property is not a vector or matrix!") + end + end + end +end + +function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) data_model["conductors"] = 3 data_model["buspairs"] = nothing for (_, bus) in data_model["bus"] - bus["bus_type"] = 1 - bus["status"] = 1 bus["bus_i"] = bus["index"] + terminals = bus["terminals"] + @assert(all(t in phases for t in terminals)||all(terminals.==[phases..., neutral])) + for prop in ["vm", "va", "vmin", "vmax"] + if haskey(bus, prop) + if length(bus[prop])==4 + val = bus[prop] + bus[prop] = val[terminals.!=neutral] + end + end + end end for (_, load) in data_model["load"] + # remove neutral + if load["configuration"]=="wye" + @assert(load["connections"][end]==4) + load["connections"] = load["connections"][1:end-1] + _pad_props!(load, ["pd", "qd"], load["connections"], phases) + else + # three-phase loads can only be delta-connected + #@assert(all(load["connections"].==phases)) + end + load["load_bus"] = load["bus"] end data_model["gen"] = data_model["generator"] + # has to be three-phase for (_, gen) in data_model["gen"] - gen["gen_status"] = gen["status"] - gen["gen_bus"] = gen["bus"] + if gen["configuration"]=="wye" + @show gen["connections"] + @assert(all(gen["connections"].==[phases..., neutral])) + else + @assert(all(gen["connections"].==phases)) + end + + _alias!(gen, "status", "gen_status") + _alias!(gen, "bus", "gen_bus") _alias!(gen, "pg_min", "pmin") _alias!(gen, "qg_min", "qmin") _alias!(gen, "pg_max", "pmax") _alias!(gen, "qg_max", "qmax") - gen["conn"] = gen["configuration"] - gen["cost"] = [1.0, 0] + _alias!(gen, "configuration", "conn") + gen["model"] = 2 end data_model["branch"] = data_model["line"] for (_, br) in data_model["branch"] - br["br_status"] = br["status"] - br["br_r"] = br["rs"] - br["br_x"] = br["xs"] + @show br + @assert(all(x in phases for x in br["f_connections"])) + @assert(all(x in phases for x in br["t_connections"])) + @assert(all(br["f_connections"].==br["t_connections"])) + _pad_props!(br, ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to", "s_rating", "c_rating"], br["f_connections"], phases) + + # rename + _alias!(br, "status", "br_status") + _alias!(br, "rs", "br_r") + _alias!(br, "xs", "br_x") + br["tap"] = 1.0 br["shift"] = 0 @@ -419,21 +503,19 @@ function data_model_make_compatible_v8!(data_model) end end - for (_, tr) in data_model["transformer_2wa"] - tr["rate_a"] = fill(1000.0, 3) - end - for (_, shunt) in data_model["shunt"] - shunt["shunt_bus"] = shunt["bus"] - shunt["gs"] = shunt["g_sh"] - shunt["bs"] = shunt["b_sh"] + @assert(all(x in phases for x in shunt["connections"])) + _pad_props!(shunt, ["g_sh", "b_sh"], shunt["connections"], phases) + _alias!(shunt, "bus", "shunt_bus") + _alias!(shunt, "g_sh", "gs") + _alias!(shunt, "b_sh", "bs") end data_model["dcline"] = Dict() data_model["transformer"] = data_model["transformer_2wa"] data_model["per_unit"] = true - data_model["baseMVA"] = 1E12 + data_model["baseMVA"] = data_model["settings"]["sbase"]*data_model["settings"]["v_var_scalar"]/1E6 data_model["name"] = "IDC" diff --git a/src/core/data_model_pu.jl b/src/core/data_model_pu.jl index 59f1d6fb3..7ca491847 100644 --- a/src/core/data_model_pu.jl +++ b/src/core/data_model_pu.jl @@ -81,23 +81,41 @@ function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) end -function data_model_make_pu!(data_model; sbase=1, vbases=missing) - v_var_scalar = data_model["v_var_scalar"] +function data_model_make_pu!(data_model; sbase=missing, vbases=missing) + v_var_scalar = data_model["settings"]["v_var_scalar"] - sbase_old = get(data_model, "sbase", missing) + if haskey(data_model["settings"], "sbase") + sbase_old = data_model["settings"]["sbase"] + else + sbase_old = 1 + end + + if ismissing(sbase) + if haskey(data_model["settings"], "set_sbase_val") + sbase = data_model["settings"]["set_sbase_val"] + else + sbase = 1 + end + end # automatically find a good vbase if ismissing(vbases) - buses_vnom = [(id, bus["vnom"]) for (id,bus) in data_model["bus"] if haskey(bus, "vnom")] - if !isempty(buses_vnom) - vbases = Dict([buses_vnom[1]]) + if haskey(data_model["settings"], "set_vbase_val") && haskey(data_model["settings"], "set_vbase_bus") + vbases = Dict(data_model["settings"]["set_vbase_bus"]=>data_model["settings"]["set_vbase_val"]) else - Memento.error("Please specify vbases manually; cannot make an educated guess for this data model.") + buses_type_3 = [(id, sum(bus["vm"])/length(bus["vm"])) for (id,bus) in data_model["bus"] if haskey(bus, "bus_type") && bus["bus_type"]==3] + if !isempty(buses_type_3) + vbases = Dict([buses_type_3[1]]) + else + Memento.error("Please specify vbases manually; cannot make an educated guess for this data model.") + end end end bus_vbase, line_vbase = _calc_vbase(data_model, vbases) + @show bus_vbase + for (id, bus) in data_model["bus"] _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, v_var_scalar) end @@ -116,7 +134,7 @@ function data_model_make_pu!(data_model; sbase=1, vbases=missing) end for (id, gen) in data_model["generator"] - #_rebase_pu_generator!(gen, bus_vbase[gen["bus"]], sbase_old, sbase, v_var_scalar) + _rebase_pu_generator!(gen, bus_vbase[gen["bus"]], sbase, sbase_old, v_var_scalar) end for (id, trans) in data_model["transformer_2wa"] @@ -126,7 +144,7 @@ function data_model_make_pu!(data_model; sbase=1, vbases=missing) _rebase_pu_transformer_2w_ideal!(trans, f_vbase, t_vbase, sbase_old, sbase, v_var_scalar) end - data_model["sbase"] = sbase + data_model["settings"]["sbase"] = sbase return data_model end @@ -135,7 +153,7 @@ end function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) # if not in p.u., these are normalized with respect to vnom - prop_vnom = ["vmax", "vmin", "vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] + prop_vnom = ["vm", "vmax", "vmin", "vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] if !haskey(bus, "vbase") @@ -143,6 +161,7 @@ function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) # vnom = bus["vnom"] # _scale_props!(bus, ["vnom"], 1/vbase) # end + @show vbase _scale_props!(bus, prop_vnom, 1/vbase) z_old = 1.0 @@ -225,14 +244,19 @@ function _rebase_pu_load!(load, vbase, sbase, sbase_old, v_var_scalar) end -function _rebase_pu_generator!(gen, vbase_new, sbase_old, sbase_new, v_var_scalar) +function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar) vbase_old = get(gen, "vbase", 1.0/v_var_scalar) - vbase_scale = vbase_old/vbase_new + vbase_scale = vbase_old/vbase + sbase_scale = sbase_old/sbase for key in ["pd_set", "qd_set", "pd_min", "qd_min", "pd_max", "qd_max"] scale(gen, key, sbase_scale) end + @show gen["cost"] + scale(gen, "cost", 1/sbase_scale) + @show gen["cost"] + # save new vbase gen["vbase"] = vbase end @@ -284,7 +308,7 @@ function solution_unmake_pu!(solution, data_model) for (prop, val) in comp if any([occursin(x, prop) for x in ["p", "q"]]) comp[prop] = val*sbase - elseif occursin("vm", prop) + elseif any([occursin(x, prop) for x in ["vm", "vr", "vi"]]) comp[prop] = val*data_model[comp_type][id]["vbase"] end end diff --git a/src/core/variable.jl b/src/core/variable.jl index 1ad65dbcb..9f6580785 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -510,13 +510,18 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractPowerModel; nw::In ) if bounded - for arc in _PMs.ref(pm, nw, :arcs_trans) - tr_id = arc[1] - flow_lb = -_PMs.ref(pm, nw, :transformer, tr_id, "rate_a") - flow_ub = _PMs.ref(pm, nw, :transformer, tr_id, "rate_a") - for c in cnds - JuMP.set_lower_bound(pt[arc][c], flow_lb[c]) - JuMP.set_upper_bound(pt[arc][c], flow_ub[c]) + for arc in _PMs.ref(pm, nw, :arcs_from_trans) + (t,i,j) = arc + s_rating_fr, s_rating_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + + if !ismissing(s_rating_fr) + JuMP.set_lower_bound.(pt[(t,i,j)], -s_rating_fr) + JuMP.set_upper_bound.(pt[(t,i,j)], s_rating_fr) + end + + if !ismissing(s_rating_to) + JuMP.set_lower_bound.(pt[(t,j,i)], -s_rating_fr) + JuMP.set_upper_bound.(pt[(t,j,i)], s_rating_fr) end end end @@ -548,13 +553,18 @@ function variable_mc_transformer_flow_reactive(pm::_PMs.AbstractPowerModel; nw:: ) if bounded - for arc in _PMs.ref(pm, nw, :arcs_trans) - tr_id = arc[1] - flow_lb = -_PMs.ref(pm, nw, :transformer, tr_id, "rate_a") - flow_ub = _PMs.ref(pm, nw, :transformer, tr_id, "rate_a") - for c in cnds - JuMP.set_lower_bound(qt[arc][c], flow_lb[c]) - JuMP.set_upper_bound(qt[arc][c], flow_ub[c]) + for arc in _PMs.ref(pm, nw, :arcs_from_trans) + (t,i,j) = arc + s_rating_fr, s_rating_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + + if !ismissing(s_rating_fr) + JuMP.set_lower_bound.(qt[(t,i,j)], -s_rating_fr) + JuMP.set_upper_bound.(qt[(t,i,j)], s_rating_fr) + end + + if !ismissing(s_rating_to) + JuMP.set_lower_bound.(qt[(t,j,i)], -s_rating_fr) + JuMP.set_upper_bound.(qt[(t,j,i)], s_rating_fr) end end end @@ -853,10 +863,10 @@ function variable_mc_generation_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm if bounded for (i,gen) in _PMs.ref(pm, nw, :gen) if haskey(gen, "qmin") - JuMP.set_lower_bound.(pg[i], gen["qmin"]) + JuMP.set_lower_bound.(qg[i], gen["qmin"]) end if haskey(gen, "qmax") - JuMP.set_upper_bound.(pg[i], gen["qmax"]) + JuMP.set_upper_bound.(qg[i], gen["qmax"]) end end end diff --git a/src/form/acp.jl b/src/form/acp.jl index 11749330a..12b0ed672 100644 --- a/src/form/acp.jl +++ b/src/form/acp.jl @@ -290,12 +290,12 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACPModel, nw::Int, i: sum(pg[g][c] for g in bus_gens) - sum(ps[s][c] for s in bus_storage) - sum(pd[l][c] for l in bus_loads) - # - ( # shunt - # Gt[c,c] * vm[c]^2 - # +sum( Gt[c,d] * vm[c]*vm[d] * cos(va[c]-va[d]) - # +Bt[c,d] * vm[c]*vm[d] * sin(va[c]-va[d]) - # for d in cnds if d != c) - # ) + - ( # shunt + Gt[c,c] * vm[c]^2 + +sum( Gt[c,d] * vm[c]*vm[d] * cos(va[c]-va[d]) + +Bt[c,d] * vm[c]*vm[d] * sin(va[c]-va[d]) + for d in cnds if d != c) + ) ) push!(cstr_p, cp) @@ -307,12 +307,12 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACPModel, nw::Int, i: sum(qg[g][c] for g in bus_gens) - sum(qs[s][c] for s in bus_storage) - sum(qd[l][c] for l in bus_loads) - # - ( # shunt - # -Bt[c,c] * vm[c]^2 - # -sum( Bt[c,d] * vm[c]*vm[d] * cos(va[c]-va[d]) - # -Gt[c,d] * vm[c]*vm[d] * sin(va[c]-va[d]) - # for d in cnds if d != c) - # ) + - ( # shunt + -Bt[c,c] * vm[c]^2 + -sum( Bt[c,d] * vm[c]*vm[d] * cos(va[c]-va[d]) + -Gt[c,d] * vm[c]*vm[d] * sin(va[c]-va[d]) + for d in cnds if d != c) + ) ) push!(cstr_q, cq) end diff --git a/src/form/apo.jl b/src/form/apo.jl index 9071253f7..ea3ef4efa 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -102,17 +102,19 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractAPLossLessModels; ) for (l,i,j) in _PMs.ref(pm, nw, :arcs_from_trans) ) - for cnd in _PMs.conductor_ids(pm) - - if bounded - for arc in _PMs.ref(pm, nw, :arcs_from_trans) - tr_id = arc[1] - flow_lb = -_PMs.ref(pm, nw, :transformer, tr_id, "rate_a")[cnd] - flow_ub = _PMs.ref(pm, nw, :transformer, tr_id, "rate_a")[cnd] - JuMP.set_lower_bound(pt[arc][cnd], flow_lb) - JuMP.set_upper_bound(pt[arc][cnd], flow_ub) + if bounded + for arc in _PMs.ref(pm, nw, :arcs_from_trans) + (t,i,j) = arc + s_rating_fr, s_rating_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + + if !(ismissing(s_rating_fr) || ismissing(s_rating_to)) + JuMP.set_lower_bound.(pt[(t,i,j)], -min.(s_rating_fr, s_rating_to)) + JuMP.set_upper_bound.(pt[(t,i,j)], min.(s_rating_fr, s_rating_to)) end end + end + + for cnd in _PMs.conductor_ids(pm) #TODO what does this dfo, and p does not seem to be defined! for (l,branch) in _PMs.ref(pm, nw, :branch) @@ -157,8 +159,8 @@ end ## From PowerModels -"`-rate_a <= p[f_idx] <= rate_a`" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, rate_a) +"`-s_rating <= p[f_idx] <= s_rating`" +function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, s_rating) cnds = _PMs.conductor_ids(pm, n) ncnds = length(cnds) mu_sm_fr = [] @@ -167,12 +169,12 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n:: p_fr =_PMs.var(pm, n, :p, f_idx)[c] if isa(p_fr, JuMP.VariableRef) && JuMP.has_lower_bound(p_fr) push!(mu_sm_fr,JuMP.LowerBoundRef(p_fr)) - JuMP.lower_bound(p_fr) < -rate_a[c] && JuMP.set_lower_bound(p_fr, -rate_a[c]) + JuMP.lower_bound(p_fr) < -s_rating[c] && JuMP.set_lower_bound(p_fr, -s_rating[c]) if JuMP.has_upper_bound(p_fr) - JuMP.upper_bound(p_fr) > rate_a[c] && JuMP.set_upper_bound(p_fr, rate_a[c]) + JuMP.upper_bound(p_fr) > s_rating[c] && JuMP.set_upper_bound(p_fr, s_rating[c]) end else - push!(mu_sm_fr, JuMP.@constraint(pm.model, p_fr <= rate_a[c])) + push!(mu_sm_fr, JuMP.@constraint(pm.model, p_fr <= s_rating[c])) end end @@ -182,7 +184,7 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n:: end "" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::Int, t_idx, rate_a) +function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::Int, t_idx, s_rating) cnds = _PMs.conductor_ids(pm, n) ncnds = length(cnds) mu_sm_to = [] @@ -191,12 +193,12 @@ function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::In p_to =_PMs.var(pm, n, :p, t_idx)[c] if isa(p_to, JuMP.VariableRef) && JuMP.has_lower_bound(p_to) push!(mu_sm_to, JuMP.LowerBoundRef(p_to)) - JuMP.lower_bound(p_to) < -rate_a[c] && JuMP.set_lower_bound(p_to, -rate_a[c]) + JuMP.lower_bound(p_to) < -s_rating[c] && JuMP.set_lower_bound(p_to, -s_rating[c]) if JuMP.has_upper_bound(p_to) - JuMP.upper_bound(p_to) > rate_a[c] && JuMP.set_upper_bound(p_to, rate_a[c]) + JuMP.upper_bound(p_to) > s_rating[c] && JuMP.set_upper_bound(p_to, s_rating[c]) end else - push!(mu_sm_to, JuMP.@constraint(pm.model, p_to <= rate_a[c])) + push!(mu_sm_to, JuMP.@constraint(pm.model, p_to <= s_rating[c])) end end @@ -206,53 +208,53 @@ function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::In end "" -function constraint_mc_current_limit(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, c_rating_a) +function constraint_mc_current_limit(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, c_rating) p_fr =_PMs.var(pm, n, :p, f_idx) cnds = _PMs.conductor_ids(pm, n) ncnds = length(cnds) for c in 1:ncnds - JuMP.lower_bound(p_fr[c]) < -c_rating_a[c] && JuMP.set_lower_bound(p_fr[c], -c_rating_a[c]) - JuMP.upper_bound(p_fr[c]) > c_rating_a[c] && JuMP.set_upper_bound(p_fr[c], c_rating_a[c]) + JuMP.lower_bound(p_fr[c]) < -c_rating[c] && JuMP.set_lower_bound(p_fr[c], -c_rating[c]) + JuMP.upper_bound(p_fr[c]) > c_rating[c] && JuMP.set_upper_bound(p_fr[c], c_rating[c]) end end "" -function constraint_mc_thermal_limit_from_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, rate_a) +function constraint_mc_thermal_limit_from_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, s_rating) p_fr =_PMs.var(pm, n, :p, f_idx) z =_PMs.var(pm, n, :z_branch, i) - JuMP.@constraint(pm.model, p_fr .<= rate_a.*z) - JuMP.@constraint(pm.model, p_fr .>= -rate_a.*z) + JuMP.@constraint(pm.model, p_fr .<= s_rating.*z) + JuMP.@constraint(pm.model, p_fr .>= -s_rating.*z) end "" -function constraint_mc_thermal_limit_to_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, rate_a) +function constraint_mc_thermal_limit_to_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, s_rating) p_to =_PMs.var(pm, n, :p, t_idx) z =_PMs.var(pm, n, :z_branch, i) - JuMP.@constraint(pm.model, p_to .<= rate_a.*z) - JuMP.@constraint(pm.model, p_to .>= -rate_a.*z) + JuMP.@constraint(pm.model, p_to .<= s_rating.*z) + JuMP.@constraint(pm.model, p_to .>= -s_rating.*z) end "" -function constraint_mc_thermal_limit_from_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, rate_a) +function constraint_mc_thermal_limit_from_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, s_rating) p_fr =_PMs.var(pm, n, :p_ne, f_idx) z =_PMs.var(pm, n, :branch_ne, i) - JuMP.@constraint(pm.model, p_fr .<= rate_a.*z) - JuMP.@constraint(pm.model, p_fr .>= -rate_a.*z) + JuMP.@constraint(pm.model, p_fr .<= s_rating.*z) + JuMP.@constraint(pm.model, p_fr .>= -s_rating.*z) end "" -function constraint_mc_thermal_limit_to_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, rate_a) +function constraint_mc_thermal_limit_to_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, s_rating) p_to =_PMs.var(pm, n, :p_ne, t_idx) z =_PMs.var(pm, n, :branch_ne, i) - JuMP.@constraint(pm.model, p_to .<= rate_a.*z) - JuMP.@constraint(pm.model, p_to .>= -rate_a.*z) + JuMP.@constraint(pm.model, p_to .<= s_rating.*z) + JuMP.@constraint(pm.model, p_to .>= -s_rating.*z) end diff --git a/src/form/dcp.jl b/src/form/dcp.jl index 7abd7e5d2..7073aa7ac 100644 --- a/src/form/dcp.jl +++ b/src/form/dcp.jl @@ -81,17 +81,11 @@ function variable_mc_branch_flow_active(pm::_PMs.AbstractAPLossLessModels; nw::I ) for (l,i,j) in _PMs.ref(pm, nw, :arcs_from)) if bounded - for cnd in cnds - flow_lb, flow_ub = _PMs.ref_calc_branch_flow_bounds(_PMs.ref(pm, nw, :branch), _PMs.ref(pm, nw, :bus), cnd) - - for arc in _PMs.ref(pm, nw, :arcs_from) - l,i,j = arc - if !isinf(flow_lb[l]) - JuMP.set_lower_bound(p[arc][cnd], flow_lb[l]) - end - if !isinf(flow_ub[l]) - JuMP.set_upper_bound(p[arc][cnd], flow_ub[l]) - end + for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) + smax = _calc_branch_power_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + if !ismissing(smax) + JuMP.set_upper_bound.(p[(l,i,j)], smax) + JuMP.set_lower_bound.(p[(l,i,j)], -smax) end end end diff --git a/src/form/ivr.jl b/src/form/ivr.jl index d92adcf72..cb5839109 100644 --- a/src/form/ivr.jl +++ b/src/form/ivr.jl @@ -262,8 +262,8 @@ function constraint_mc_current_balance_load(pm::_PMs.AbstractIVRModel, n::Int, i end end -"`p[f_idx]^2 + q[f_idx]^2 <= rate_a^2`" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractIVRModel, n::Int, f_idx, rate_a) +"`p[f_idx]^2 + q[f_idx]^2 <= s_rating^2`" +function constraint_mc_thermal_limit_from(pm::_PMs.AbstractIVRModel, n::Int, f_idx, s_rating) (l, f_bus, t_bus) = f_idx vr = _PMs.var(pm, n, :vr, f_bus) @@ -275,12 +275,12 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractIVRModel, n::Int, f_i ncnds = length(cnds) for c in cnds - JuMP.@NLconstraint(pm.model, (vr[c]^2 + vi[c]^2)*(crf[c]^2 + cif[c]^2) <= rate_a[c]^2) + JuMP.@NLconstraint(pm.model, (vr[c]^2 + vi[c]^2)*(crf[c]^2 + cif[c]^2) <= s_rating[c]^2) end end -"`p[t_idx]^2 + q[t_idx]^2 <= rate_a^2`" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractIVRModel, n::Int, t_idx, rate_a) +"`p[t_idx]^2 + q[t_idx]^2 <= s_rating^2`" +function constraint_mc_thermal_limit_to(pm::_PMs.AbstractIVRModel, n::Int, t_idx, s_rating) (l, t_bus, f_bus) = t_idx vr = _PMs.var(pm, n, :vr, t_bus) @@ -292,7 +292,7 @@ function constraint_mc_thermal_limit_to(pm::_PMs.AbstractIVRModel, n::Int, t_idx ncnds = length(cnds) for c in cnds - JuMP.@NLconstraint(pm.model, (vr[c]^2 + vi[c]^2)*(crt[c]^2 + cit[c]^2) <= rate_a[c]^2) + JuMP.@NLconstraint(pm.model, (vr[c]^2 + vi[c]^2)*(crt[c]^2 + cit[c]^2) <= s_rating[c]^2) end end diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl index 0405c5ba3..e6264b1ae 100644 --- a/src/io/common_dm.jl +++ b/src/io/common_dm.jl @@ -26,5 +26,9 @@ function parse_file_dm(file::String; kwargs...) pmd_data = open(file) do io parse_file_dm(io; filetype=split(lowercase(file), '.')[end], kwargs...) end + data_model_map!(pmd_data) + data_model_make_pu!(pmd_data) + data_model_index!(pmd_data) + data_model_make_compatible_v8!(pmd_data) return pmd_data end diff --git a/src/io/data_model.md b/src/io/data_model.md new file mode 100644 index 000000000..9321a962d --- /dev/null +++ b/src/io/data_model.md @@ -0,0 +1,188 @@ + +# Shared patterns +- Each component has a unique (amonst components of the same type) identifier `id`. +- Everything is defined in SI units, except when a base is explicitly mentioned in the description. +- The default sometimes only mentions the default element, not the default value (e.g. `true` and not `fill(fill(true, 3), 3)`) + +# Data model + +## Bus + +The data model below allows us to include buses of arbitrary many terminals (i.e., more than the usual four). This would be useful for +- underground lines with multiple neutrals which are not joined at every bus; +- distribution lines that carry several conventional lines in parallel (see for example the quad circuits in NEVTestCase). + +name | default | type | description +------|----------|-----|----------- +terminals|[1,2,3,4]|Vector +vm_max | / | Vector | maximum conductor-to-ground voltage magnitude +vm_min | / | Vector | minimum conductor-to-ground voltage magnitude +vm_cd_max | / | Vector{Tuple} | e.g. [(1,2,210)] means \|U1-U2\|>210 +vm_cd_min | / | Vector{Tuple} | e.g. [(1,2,230)] means \|U1-U2\|<230 +grounded | [] | Vector | a list of terminals which are grounded +rg | [] | Vector | resistance of each defined grounding +xg | [] | Vector | reactance of each defined grounding + +The tricky part is how to encode bounds for these type of buses. The most general is defining a list of three-tupples. Take for example a typical bus in a three-phase, four-wire network, where `terminals=[a,b,c,n]`. Such a bus might have +- phase-to-neutral bounds `vm_pn_max=250`, `vm_pn_min=210` +- and phase-to-phase bounds `vm_pp_max=440`, `vm_pp_min=360 +`. + +We can then define this equivalently as +- `vm_cd_max = [(a,n,250), (b,n,250), (c,n,250), (a,b,440), (b,c,440), (c,a,440)]` +- `vm_cd_min = [(a,n,210), (b,n,210), (c,n,210), (a,b,360), (b,c,360), (c,a,360)]` + +Since this might be confusing for novice users, we also allow the user to define bounds through the following properties. + + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +phases | [1,2,3] | Vector +neutral | 4 | | maximum conductor-to-ground voltage magnitude +vm_pn_max | / | Real | maximum phase-to-neutral voltage magnitude for all phases +vm_pn_min | / | Real | minimum phase-to-neutral voltage magnitude for all phases +vm_pp_max | / | Real | maximum phase-to-phase voltage magnitude for all phases +vm_pp_min | / | Real | minimum phase-to-phase voltage magnitude for all phases + +## Line + +This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` and `g_to`; when these properties are additionally specified, they overwrite the one supplied through the linecode. + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +f_bus | / | | +f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects +t_bus | / | | +t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects +linecode | / | | a linecode +rs | / | | series resistance matrix, size of n_conductors x n_conductors +xs | / | | series reactance matrix, size of n_conductors x n_conductors +g_fr | / | | from-side conductance +b_fr | / | | from-side susceptance +g_to | / | | to-side conductance +b_to | / | | to-side susceptance +c_rating | / | | symmetrically applicable current rating +s_rating | / | | symmetrically applicable power rating + +## Linecode + +- Should the linecode also include a `c_rating` and/`s_rating`? + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +rs | / | | series resistance matrix +xs | / | | series reactance matrix n_conductors +g_fr | / | | from-side conductance +b_fr | / | | from-side susceptance +g_to | / | | to-side conductance +b_to | / | | to-side susceptance + +## Shunt + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +bus | / | | +connections | / | | +g | / | | conductance, size should be \|connections\|x\|connections\| +b | / | | susceptance, size should be \|connections\|x\|connections\| + +## Capacitor + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +bus | / | | +connections | / | | +qd_ref | / | | conductance, size should be \|connections\|x\|connections\| +vnom | / | | conductance, size should be \|connections\|x\|connections\| + +## Load + +name | default | type | description +------|----------|----|----------- +id | / | | unique identifier +bus | / | | +connections | / | | +configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral +model | / | | indicates the type of voltage-dependency + +### `model=constant_power` + +name | default | type | description +------|----------|----|----------- +pd | / | Vector{Real} | +qd | / | Vector{Real} | + +### `model=constant_current/impedance` + +name | default | type | description +------|----------|----|----------- +pd_ref | / | Vector{Real} | +qd_ref | / | Vector{Real} | +vnom | / | Real | + +### `model=exponential` + +name | default | type | description +------|----------|----|----------- +pd_ref | / | Vector{Real} | +qd_ref | / | Vector{Real} | +vnom | / | Real | +exp_p | / | Vector{Real} | +exp_q | / | Vector{Real} | + +## Generator + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +bus | / | | +connections | / | | +configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral +pg_min | / | | lower bound on active power generation per phase +pg_max | / | | upper bound on active power generation per phase +qg_min | / | | lower bound on reactive power generation per phase +qg_max | / | | upper bound on reactive power generation per phase + +## AL2W Transformer +These are transformers are assymetric (A), lossless (L) and two-winding (2W). Assymetric refers to the fact that the secondary is always has a `wye` configuration, whilst the primary can be `delta`. + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +n_phases | size(rs)[1] | Int>0 | number of phases +f_bus | / | | +f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects +t_bus | / | | +t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects +configuration | / | {wye, delta} | for the from-side; the to-side is always connected in wye +tm_nom | / | Real | nominal tap ratio for the transformer +tm_max | / | Vector | maximum tap ratio for each phase (base=`tm_nom`) +tm_min | / | Vector | minimum tap ratio for each phase (base=`tm_nom`) +tm_set | fill(1.0, `n_phases`) | Vector | set tap ratio for each phase (base=`tm_nom`) +tm_fix | fill(true, `n_phases`) | Vector | indicates for each phase whether the tap ratio is fixed + +TODO: add tm stuff + +## Transformer +These are n-winding, n-phase, lossy transformers. Note that most properties are now Vectors (or Vectors of Vectors), indexed over the windings. + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +n_phases | size(rs)[1] | Int>0 | number of phases +n_windings | size(rs)[1] | Int>0 | number of windings +bus | / | Vector | list of bus for each winding +connections | | Vector{Vector} | list of connection for each winding +configurations | | Vector{{wye, delta}} | list of configuration for each winding +xsc | 0.0 | Vector | list of short-circuit reactances between each pair of windings; enter as a list of the upper-triangle elements +rs | 0.0 | Vector | list of the winding resistance for each winding +tm_nom | / | Vector{Real} | nominal tap ratio for the transformer +tm_max | / | Vector{Vector} | maximum tap ratio for each winding and phase (base=`tm_nom`) +tm_min | / | Vector{Vector} | minimum tap ratio for for each winding and phase (base=`tm_nom`) +tm_set | 1.0 | Vector{Vector} | set tap ratio for each winding and phase (base=`tm_nom`) +tm_fix | true | Vector{Vector} | indicates for each winding and phase whether the tap ratio is fixed diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl index 621c4d528..6d8faecaf 100644 --- a/src/io/data_model_components.jl +++ b/src/io/data_model_components.jl @@ -37,7 +37,7 @@ function check_dtypes(dict, dtypes, comp_type, id) if haskey(dtypes, symb) @assert(isa(dict[key], dtypes[symb]), "$comp_type $id: the property $key should be a $(dtypes[symb]), not a $(typeof(dict[key])).") else - @assert(false, "$comp_type $id: the property $key is unknown.") + #@assert(false, "$comp_type $id: the property $key is unknown.") end end end @@ -83,9 +83,13 @@ function check_data_model(data) end -function create_data_model(;kwargs...) - data_model = Dict{String, Any}() - add_kwarg!(data_model, kwargs, :v_var_scalar, 1E3) +function create_data_model(; kwargs...) + data_model = Dict{String, Any}("settings"=>Dict{String, Any}()) + + add_kwarg!(data_model["settings"], kwargs, :v_var_scalar, 1E3) + + _add_unused_kwargs!(data_model["settings"], kwargs) + return data_model end @@ -102,7 +106,7 @@ DTYPES[:autotransformer] = Dict() DTYPES[:synchronous_generator] = Dict() DTYPES[:zip_load] = Dict() DTYPES[:grounding] = Dict( - :bus => String, + :bus => Any, :rg => Real, :xg => Real, ) @@ -220,7 +224,13 @@ DTYPES[:line] = Dict( :c_rating =>Vector{<:Real}, :s_rating =>Vector{<:Real}, :angmin=>Vector{<:Real}, - :angmax=>Vector{<:Real} + :angmax=>Vector{<:Real}, + :rs => Array{<:Real, 2}, + :xs => Array{<:Real, 2}, + :g_fr => Array{<:Real, 2}, + :g_to => Array{<:Real, 2}, + :b_fr => Array{<:Real, 2}, + :b_to => Array{<:Real, 2}, ) REQUIRED_FIELDS[:line] = [:id, :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length] @@ -228,8 +238,8 @@ REQUIRED_FIELDS[:line] = [:id, :status, :f_bus, :f_connections, :t_bus, :t_conne CHECKS[:line] = function check_line(data, line) i = line["id"] - # for now, always require a lione code - if true || haskey(line, "linecode") + # for now, always require a line code + if haskey(line, "linecode") # line is defined with a linecode @assert(haskey(line, "length"), "line $i: a line defined through a linecode, should have a length property.") @@ -267,6 +277,22 @@ function create_line(; kwargs...) add_kwarg!(line, kwargs, :angmin, fill(-60/180*pi, N)) add_kwarg!(line, kwargs, :angmax, fill( 60/180*pi, N)) + # if no linecode, then populate loss parameters with zero + if !haskey(kwargs, :linecode) + n_conductors = 0 + for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] + if haskey(kwargs, key) + n_conductors = size(kwargs[key])[1] + end + end + add_kwarg!(line, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) + end + _add_unused_kwargs!(line, kwargs) return line @@ -277,6 +303,7 @@ end DTYPES[:bus] = Dict( :id => Any, :status => Int, + :bus_type => Int, :terminals => Array{<:Any}, :phases => Array{<:Int}, :neutral => Union{Int, Missing}, @@ -314,6 +341,7 @@ function create_bus(; kwargs...) add_kwarg!(bus, kwargs, :status, 1) add_kwarg!(bus, kwargs, :terminals, collect(1:4)) add_kwarg!(bus, kwargs, :grounded, []) + add_kwarg!(bus, kwargs, :bus_type, 1) add_kwarg!(bus, kwargs, :rg, Array{Float64, 1}()) add_kwarg!(bus, kwargs, :xg, Array{Float64, 1}()) @@ -327,8 +355,8 @@ end DTYPES[:load] = Dict( :id => Any, :status => Int, - :bus => String, - :connections => Array{<:Int}, + :bus => Any, + :connections => Vector, :configuration => String, :model => String, :pd => Array{<:Real, 1}, @@ -389,9 +417,10 @@ end DTYPES[:generator] = Dict( :id => Any, :status => Int, - :bus => String, - :connections => Array{<:Int}, + :bus => Any, + :connections => Vector, :configuration => String, + :cost => Vector{<:Real}, :pg => Array{<:Real, 1}, :qg => Array{<:Real, 1}, :pg_min => Array{<:Real, 1}, @@ -416,6 +445,7 @@ function create_generator(; kwargs...) add_kwarg!(generator, kwargs, :status, 1) add_kwarg!(generator, kwargs, :configuration, "wye") + add_kwarg!(generator, kwargs, :cost, [1.0, 0.0]*1E-3) add_kwarg!(generator, kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) _add_unused_kwargs!(generator, kwargs) @@ -431,7 +461,7 @@ DTYPES[:transformer_nw] = Dict( :id => Any, :status => Int, :bus => Array{<:AbstractString, 1}, - :connections => Array{<:Array{<:Any, 1}, 1}, + :connections => Vector, :vnom => Array{<:Real, 1}, :snom => Array{<:Real, 1}, :configuration => Array{String, 1}, @@ -540,8 +570,8 @@ end DTYPES[:capacitor] = Dict( :id => Any, :status => Int, - :bus => String, - :connections => Array{Int, 1}, + :bus => Any, + :connections => Vector, :configuration => String, :qd_ref => Array{<:Real, 1}, :vnom => Real, @@ -587,8 +617,8 @@ end DTYPES[:shunt] = Dict( :id => Any, :status => Int, - :bus => String, - :connections => Array{Int, 1}, + :bus => Any, + :connections => Vector, :g_sh => Array{<:Real, 2}, :b_sh => Array{<:Real, 2}, ) @@ -602,12 +632,14 @@ CHECKS[:shunt] = function check_shunt(data, shunt) end -function add_shunt!(; kwargs...) +function create_shunt(; kwargs...) shunt = Dict{String,Any}() + N = length(kwargs[:connections]) + add_kwarg!(shunt, kwargs, :status, 1) - add_kwarg!(shunt, kwargs, :g_sh, fill(0.0, length(terminals), length(terminals))) - add_kwarg!(shunt, kwargs, :b_sh, fill(0.0, length(terminals), length(terminals))) + add_kwarg!(shunt, kwargs, :g_sh, fill(0.0, N, N)) + add_kwarg!(shunt, kwargs, :b_sh, fill(0.0, N, N)) _add_unused_kwargs!(shunt, kwargs) @@ -620,8 +652,8 @@ end DTYPES[:voltage_source] = Dict( :id => Any, :status => Int, - :bus => String, - :connections => Array{Int, 1}, + :bus => Any, + :connections => Vector, :vm =>Array{<:Real}, :va =>Array{<:Real}, :pg_max =>Array{<:Real}, diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl index f9fa7576c..85fdbb0fd 100644 --- a/src/io/data_model_test.jl +++ b/src/io/data_model_test.jl @@ -1,4 +1,5 @@ using PowerModelsDistribution +import LinearAlgebra function make_test_data_model() @@ -75,8 +76,10 @@ function make_3wire_data_model() add_linecode!(data_model, id="3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3))) - add_voltage_source!(data_model, id="source", bus="sourcebus", vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], - #pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), + add_voltage_source!(data_model, id="source", bus="sourcebus", connections=collect(1:3), + vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], + pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), + rs=ones(3,3)/10 ) # 3 phase conductors @@ -126,15 +129,16 @@ end data_model = make_3wire_data_model() check_data_model(data_model) -## +data_model +# data_model_map!(data_model) #bsh = data_model["shunt"]["_virtual_1"]["b_sh"] -# +## data_model_make_pu!(data_model, vbases=Dict("sourcebus"=>0.230)) # data_model_index!(data_model) data_model_make_compatible_v8!(data_model) -# +## import PowerModelsDistribution PMD = PowerModelsDistribution import PowerModels diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl index c4bd442d0..7b88765d0 100644 --- a/src/io/data_model_util.jl +++ b/src/io/data_model_util.jl @@ -9,12 +9,12 @@ function scale(dict, key, scale) end -function add_virtual_get_id!(data_model, comp_type, comp) +function add_virtual!(data_model, comp_type, comp) if !haskey(data_model, comp_type) data_model[comp_type] = Dict{Any, Any}() end comp_dict = data_model[comp_type] - virtual_ids = [parse(Int, x[1]) for x in [match(r"_virtual_([1-9]{1}[0-9]*)", id) for id in keys(comp_dict) if isa(id, AbstractString)] if !isnothing(x)] + virtual_ids = [parse(Int, x[1]) for x in [match(r"_virtual_([1-9]{1}[0-9]*)", id) for id in keys(comp_dict) if isa(id, AbstractString)] if x!=nothing] if isempty(virtual_ids) id = "_virtual_1" else @@ -22,10 +22,10 @@ function add_virtual_get_id!(data_model, comp_type, comp) end comp["id"] = id comp_dict[id] = comp - return id + return comp end -add_virtual! = add_virtual_get_id! +add_virtual_get_id!(data_model, comp_type, comp) = add_virtual!(data_model, comp_type, comp)["id"] function delete_component!(data_model, comp_type, comp::Dict) delete!(data_model[comp_type], comp["id"]) @@ -126,3 +126,39 @@ function delete_solution!(solution, comp_type, id) end end end + +## +function _get_new_ground(terminals) + if isa(terminals, Vector{Int}) + return maximum(terminals)+1 + else + nrs = [parse(Int, x[1]) for x in [match(r"n([1-9]{1}[0-9]*)", string(t)) for t in terminals] if x!=nothing] + new = isempty(nrs) ? 1 : maximum(nrs)+1 + if isa(terminals, Vector{Symbol}) + return Symbol("g$new") + else + return "g$new" + end + end +end + + +function _get_ground!(bus) + # find perfect groundings (true ground) + grounded_perfect = [] + for i in 1:length(bus["grounded"]) + if bus["rg"][i]==0 && bus["xg"][i]==0 + push!(grounded_perfect, bus["grounded"][i]) + end + end + + if !isempty(grounded_perfect) + return grounded_perfect[1] + else + g = _get_new_ground(bus["terminals"]) + push!(bus["terminals"], g) + push!(bus["rg"], 0.0) + push!(bus["xg"], 0.0) + return g + end +end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index d721c7248..7d2c95ce5 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -1153,6 +1153,13 @@ function _decompose_transformers!(pmd_data; import_all::Bool=false) "grounded"=>true, "vm_nom"=>1.0 ) + # temporary fix for prop renaming + trans_dict["configuration"] = trans_dict["config_fr"]["type"] + trans_dict["f_connections"] = trans_dict["config_fr"]["cnd"] + trans_dict["t_connections"] = trans_dict["config_to"]["cnd"] + scale = trans_dict["configuration"]=="delta" ? sqrt(3) : 1.0 + trans_dict["tm_nom"] = trans_dict["config_fr"]["vm_nom"]*scale + trans_dict["f_bus"] = trans["buses"][w] # make virtual bus and mark it for reduction vbus_tr = _create_vbus!(pmd_data, basekv=1.0, name="tr$(tr_id)_w$(w)_b1") diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 676195365..564f8b9e2 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -77,45 +77,34 @@ end Adds PowerModels-style buses to `pmd_data` from `dss_data`. """ function _dss2pmd_bus_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1) - if !haskey(pmd_data, "bus") - pmd_data["bus"] = Dict{String, Any}() - end buses = _discover_buses(dss_data) for (n, (bus, nodes)) in enumerate(buses) - busDict = Dict{String,Any}() - - busDict["id"] = bus @assert(!(length(bus)>=8 && bus[1:8]=="_virtual"), "Bus $bus: identifiers should not start with _virtual.") - busDict["bus_type"] = 1 - - - pmd_data["bus"][busDict["id"]] = busDict + add_bus!(pmd_data, id=bus, status=1, bus_type=1) end # create virtual sourcebus circuit = _create_vsource(get(dss_data["circuit"][1], "bus1", "sourcebus"), dss_data["circuit"][1]["name"]; _to_sym_keys(dss_data["circuit"][1])...) - busDict = Dict{String,Any}() - nodes = Array{Bool}([1 1 1 0]) ph1_ang = circuit["angle"] - vm = circuit["pu"] + vm_pu = circuit["pu"] vmi = circuit["pu"] - circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) vma = circuit["pu"] + circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) - busDict["id"] = "_virtual_sourcebus" - - busDict["bus_type"] = 3 - phases = circuit["phases"] - busDict["vm_fix"] = fill(vm, 3) - busDict["vnom"] = pmd_data["basekv"]/sqrt(3) - busDict["va_fix"] = [rad2deg(2*pi/phases*(i-1))+ph1_ang for i in 1:phases] + vnom = pmd_data["settings"]["set_vbase_val"] - pmd_data["bus"][busDict["id"]] = busDict + vm = fill(vm_pu, 3)*vnom + va = _wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases]) + + add_voltage_source!(pmd_data, id="source", bus="sourcebus", connections=collect(1:phases), + vm=vm, va=va, + rs=circuit["rmatrix"], xs=circuit["xmatrix"], + ) end @@ -157,50 +146,11 @@ end Adds PowerModels-style loads to `pmd_data` from `dss_data`. """ function _dss2pmd_load_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool, ground_terminal::Int=4) - if !haskey(pmd_data, "load") - pmd_data["load"] = Dict{String, Any}() - end for load in get(dss_data, "load", []) _apply_like!(load, dss_data, "load") defaults = _apply_ordered_properties(_create_load(load["bus1"], load["name"]; _to_sym_keys(load)...), load) - nphases = defaults["phases"] - conn = defaults["conn"] - - loadDict = Dict{String,Any}() - - loadDict["id"] = defaults["name"] - - load_name = defaults["name"] - - # connections - bus = _parse_busname(defaults["bus1"])[1] - loadDict["bus"] = bus - terminals_default = conn=="wye" ? [collect(1:nphases)..., 0] : collect(1:nphases) - terminals = _get_conductors_ordered_dm(defaults["bus1"], default=terminals_default, check_length=false) - # if wye connected and neutral not specified, append ground - if conn=="wye" && length(terminals)==nphases - terminals = [terminals..., 0] - end - # if the ground is used directly, register load - if 0 in terminals - if !haskey(pmd_data["bus"][bus], "awaiting_ground") - pmd_data["bus"][bus]["awaiting_ground"] = [] - end - push!(pmd_data["bus"][bus]["awaiting_ground"], loadDict) - end - loadDict["configuration"] = conn - loadDict["terminals"] = terminals - - kv = defaults["kv"] - if conn=="wye" && nphases in [2, 3] - kv = kv/sqrt(3) - end - loadDict["pd"] = fill(defaults["kw"]/nphases, nphases) - loadDict["qd"] = fill(defaults["kvar"]/nphases, nphases) - loadDict["vnom"] = fill(kv, nphases) - # parse the model model = defaults["model"] # some info on OpenDSS load models @@ -237,16 +187,54 @@ function _dss2pmd_load_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool, gro end # save adjusted model type to dict, human-readable model_int2str = Dict(1=>"constant_power", 2=>"constant_impedance", 5=>"constant_current") - loadDict["model"] = model_int2str[model] + model = model_int2str[model] + + nphases = defaults["phases"] + conf = defaults["conn"] + + + # connections + bus = _parse_busname(defaults["bus1"])[1] + + connections_default = conf=="wye" ? [collect(1:nphases)..., 0] : collect(1:nphases) + connections = _get_conductors_ordered_dm(defaults["bus1"], default=connections_default, check_length=false) + # if wye connected and neutral not specified, append ground + if conf=="wye" && length(connections)==nphases + connections = [connections..., 0] + end + + # now we can create the load; if you do not have the correct model, + # pd/qd fields will be populated by default (should not happen for constant current/impedance) + loadDict = add_load!(pmd_data, id=defaults["name"], model=model, connections=connections, bus=bus, configuration=conf) + + # if the ground is used directly, register load + if 0 in connections + if !haskey(pmd_data["bus"][bus], "awaiting_ground") + pmd_data["bus"][bus]["awaiting_ground"] = [] + end + push!(pmd_data["bus"][bus]["awaiting_ground"], loadDict) + end + + kv = defaults["kv"] + if conf=="wye" && nphases in [2, 3] + kv = kv/sqrt(3) + end + + if model=="constant_power" + loadDict["pd"] = fill(defaults["kw"]/nphases, nphases) + loadDict["qd"] = fill(defaults["kvar"]/nphases, nphases) + else + loadDict["pd_ref"] = fill(defaults["kw"]/nphases, nphases) + loadDict["qd_ref"] = fill(defaults["kvar"]/nphases, nphases) + loadDict["vnom"] = kv + end #loadDict["status"] = convert(Int, defaults["enabled"]) #loadDict["source_id"] = "load.$load_name" used = ["phases", "bus1", "name"] - #_PMs._import_remaining!(loadDict, defaults, import_all; exclude=used) - - pmd_data["load"][loadDict["id"]] = loadDict + _PMs._import_remaining!(loadDict, defaults, import_all; exclude=used) end end @@ -257,19 +245,11 @@ end Adds PowerModels-style shunts to `pmd_data` from `dss_data`. """ function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "shunt") - pmd_data["shunt"] = Dict{String, Any}() - end for shunt in get(dss_data, "capacitor", []) _apply_like!(shunt, dss_data, "capacitor") defaults = _apply_ordered_properties(_create_capacitor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) - shuntDict = Dict{String,Any}() - - shuntDict["id"] = "capacitor.$(defaults["name"])" - shuntDict["status"] = convert(Int, defaults["enabled"]) - nphases = defaults["phases"] dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") @@ -280,7 +260,6 @@ function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) if bus_name!=bus2_name Memento.error("Capacitor $(defaults["name"]): bus1 and bus2 should connect to the same bus.") end - shuntDict["bus"] = bus_name f_terminals = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) if conn=="wye" @@ -308,10 +287,10 @@ function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) # if one terminal is ground (0), reduce shunt addmittance matrix terminals, B = ground_shunt(terminals, B, 0) - shuntDict["terminals"] = terminals - shuntDict["gs"] = fill(0.0, size(B)...) - shuntDict["bs"] = B - + shuntDict = add_shunt!(pmd_data, id=defaults["name"], status=convert(Int, defaults["enabled"]), + bus=bus_name, connections=terminals, + g_sh=fill(0.0, size(B)...), b_sh=B + ) used = ["bus1", "phases", "name"] _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) @@ -319,36 +298,36 @@ function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) pmd_data["shunt"][shuntDict["id"]] = shuntDict end - - for shunt in get(dss_data, "reactor", []) - if !haskey(shunt, "bus2") - _apply_like!(shunt, dss_data, "reactor") - defaults = _apply_ordered_properties(_create_reactor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) - - shuntDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - Zbase = (pmd_data["basekv"] / sqrt(3.0))^2 * nconductors / pmd_data["baseMVA"] # Use single-phase base impedance for each phase - Gcap = Zbase * sum(defaults["kvar"]) / (nconductors * 1e3 * (pmd_data["basekv"] / sqrt(3.0))^2) - - shuntDict["shunt_bus"] = find_bus(name, pmd_data) - shuntDict["name"] = defaults["name"] - shuntDict["gs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) # TODO: - shuntDict["bs"] = _PMs.MultiConductorVector(_parse_array(Gcap, nodes, nconductors)) - shuntDict["status"] = convert(Int, defaults["enabled"]) - shuntDict["index"] = length(pmd_data["shunt"]) + 1 - - shuntDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - shuntDict["source_id"] = "reactor.$(defaults["name"])" - - used = ["bus1", "phases", "name"] - _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) - - push!(pmd_data["shunt"], shuntDict) - end - end + #TODO revisit this in the future + # for shunt in get(dss_data, "reactor", []) + # if !haskey(shunt, "bus2") + # _apply_like!(shunt, dss_data, "reactor") + # defaults = _apply_ordered_properties(_create_reactor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) + # + # shuntDict = Dict{String,Any}() + # + # nconductors = pmd_data["conductors"] + # name, nodes = _parse_busname(defaults["bus1"]) + # + # Zbase = (pmd_data["basekv"] / sqrt(3.0))^2 * nconductors / pmd_data["baseMVA"] # Use single-phase base impedance for each phase + # Gcap = Zbase * sum(defaults["kvar"]) / (nconductors * 1e3 * (pmd_data["basekv"] / sqrt(3.0))^2) + # + # shuntDict["shunt_bus"] = find_bus(name, pmd_data) + # shuntDict["name"] = defaults["name"] + # shuntDict["gs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) # TODO: + # shuntDict["bs"] = _PMs.MultiConductorVector(_parse_array(Gcap, nodes, nconductors)) + # shuntDict["status"] = convert(Int, defaults["enabled"]) + # shuntDict["index"] = length(pmd_data["shunt"]) + 1 + # + # shuntDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + # shuntDict["source_id"] = "reactor.$(defaults["name"])" + # + # used = ["bus1", "phases", "name"] + # _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) + # + # push!(pmd_data["shunt"], shuntDict) + # end + # end end @@ -414,49 +393,49 @@ end Adds PowerModels-style generators to `pmd_data` from `dss_data`. """ -function _dss2pmd_gen!(pmd_data::Dict, dss_data::Dict, import_all::Bool) +function _dss2pmd_gen_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) if !haskey(pmd_data, "gen") pmd_data["gen"] = [] end - # sourcebus generator (created by circuit) - circuit = dss_data["circuit"][1] - defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), "sourcegen"; _to_sym_keys(circuit)...) - - genDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - genDict["gen_bus"] = find_bus("virtual_sourcebus", pmd_data) - genDict["name"] = defaults["name"] - genDict["gen_status"] = convert(Int, defaults["enabled"]) - - # TODO: populate with VSOURCE properties - genDict["pg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) - genDict["qg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) - - genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) - genDict["qmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) - - genDict["pmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) - genDict["pmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) - - genDict["model"] = 2 - genDict["startup"] = 0.0 - genDict["shutdown"] = 0.0 - genDict["ncost"] = 3 - genDict["cost"] = [0.0, 1.0, 0.0] - - genDict["index"] = length(pmd_data["gen"]) + 1 - - genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - genDict["source_id"] = "vsource.$(defaults["name"])" - - used = ["name", "phases", "bus1"] - _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) - - push!(pmd_data["gen"], genDict) + # # sourcebus generator (created by circuit) + # circuit = dss_data["circuit"][1] + # defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), "sourcegen"; _to_sym_keys(circuit)...) + # + # genDict = Dict{String,Any}() + # + # nconductors = pmd_data["conductors"] + # name, nodes = _parse_busname(defaults["bus1"]) + # + # genDict["gen_bus"] = find_bus("virtual_sourcebus", pmd_data) + # genDict["name"] = defaults["name"] + # genDict["gen_status"] = convert(Int, defaults["enabled"]) + # + # # TODO: populate with VSOURCE properties + # genDict["pg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) + # genDict["qg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) + # + # genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) + # genDict["qmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) + # + # genDict["pmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) + # genDict["pmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) + # + # genDict["model"] = 2 + # genDict["startup"] = 0.0 + # genDict["shutdown"] = 0.0 + # genDict["ncost"] = 3 + # genDict["cost"] = [0.0, 1.0, 0.0] + # + # genDict["index"] = length(pmd_data["gen"]) + 1 + # + # genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + # genDict["source_id"] = "vsource.$(defaults["name"])" + # + # used = ["name", "phases", "bus1"] + # _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) + # + # push!(pmd_data["gen"], genDict) for gen in get(dss_data, "generator", []) @@ -589,7 +568,7 @@ function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) end linecode["units"] = get(line, "units", "none") == "none" ? "none" : get(linecode, "units", "none") - linecode["circuit_basefreq"] = pmd_data["basefreq"] + linecode["circuit_basefreq"] = pmd_data["settings"]["basefreq"] linecode = _create_linecode(get(linecode, "name", ""); _to_sym_keys(linecode)...) delete!(linecode, "name") @@ -597,9 +576,9 @@ function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) linecode = Dict{String,Any}() end - if haskey(line, "basefreq") && line["basefreq"] != pmd_data["basefreq"] - Memento.warn(_LOGGER, "basefreq=$(line["basefreq"]) on line $(line["name"]) does not match circuit basefreq=$(pmd_data["basefreq"])") - line["circuit_basefreq"] = pmd_data["basefreq"] + if haskey(line, "basefreq") && line["basefreq"] != pmd_data["settings"]["basefreq"] + Memento.warn(_LOGGER, "basefreq=$(line["basefreq"]) on line $(line["name"]) does not match circuit basefreq=$(pmd_data["settings"]["basefreq"])") + line["circuit_basefreq"] = pmd_data["settings"]["basefreq"] end defaults = _apply_ordered_properties(_create_line(line["bus1"], line["bus2"], line["name"]; _to_sym_keys(line)...), line; linecode=linecode) @@ -619,8 +598,8 @@ function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) xmatrix = defaults["xmatrix"] cmatrix = defaults["cmatrix"] - lineDict["f_terminals"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) - lineDict["t_terminals"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) + lineDict["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) + lineDict["t_connections"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) lineDict["rs"] = rmatrix * defaults["length"] lineDict["xs"] = xmatrix * defaults["length"] @@ -628,8 +607,8 @@ function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) lineDict["g_fr"] = fill(0.0, nphases, nphases) lineDict["g_to"] = fill(0.0, nphases, nphases) - lineDict["b_fr"] = (2.0 * pi * pmd_data["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 - lineDict["b_to"] = (2.0 * pi * pmd_data["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 + lineDict["b_fr"] = (2.0 * pi * pmd_data["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 + lineDict["b_to"] = (2.0 * pi * pmd_data["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 #lineDict["c_rating_a"] = fill(defaults["normamps"], nphases) #lineDict["c_rating_b"] = fill(defaults["emergamps"], nphases) @@ -653,8 +632,8 @@ end Adds ThreePhasePowerModels-style transformers to `pmd_data` from `dss_data`. """ function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "transformer_nw_lossy") - pmd_data["transformer_nw_lossy"] = Dict{String,Any}() + if !haskey(pmd_data, "transformer_nw") + pmd_data["transformer_nw"] = Dict{String,Any}() end for transformer in get(dss_data, "transformer", []) @@ -679,33 +658,34 @@ function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bo transDict = Dict{String, Any}() transDict["id"] = defaults["name"] - transDict["buses"] = Array{String, 1}(undef, nrw) - transDict["terminals"] = Array{Array{Int, 1}, 1}(undef, nrw) - transDict["tm_fix"] = Array{Array{Real, 1}, 1}(undef, nrw) + transDict["bus"] = Array{String, 1}(undef, nrw) + transDict["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) + transDict["tm"] = Array{Array{Real, 1}, 1}(undef, nrw) + transDict["fixed"] = [[true for i in 1:nphases] for j in 1:nrw] transDict["vnom"] = [defaults["kvs"][w] for w in 1:nrw] transDict["snom"] = [defaults["kvas"][w] for w in 1:nrw] - transDict["terminals"] = Array{Array{Int, 1}, 1}(undef, nrw) + transDict["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) transDict["configuration"] = Array{String, 1}(undef, nrw) transDict["polarity"] = Array{String, 1}(undef, nrw) for w in 1:nrw - transDict["buses"][w] = _parse_busname(defaults["buses"][w])[1] + transDict["bus"][w] = _parse_busname(defaults["buses"][w])[1] conn = dyz_map[defaults["conns"][w]] transDict["configuration"][w] = conn terminals_default = conn=="wye" ? [1:nphases..., 0] : collect(1:nphases) terminals_w = _get_conductors_ordered_dm(defaults["buses"][w], default=terminals_default) - transDict["terminals"][w] = terminals_w + transDict["connections"][w] = terminals_w if 0 in terminals_w - bus = transDict["buses"][w] + bus = transDict["bus"][w] if !haskey(pmd_data["bus"][bus], "awaiting_ground") pmd_data["bus"][bus]["awaiting_ground"] = [] end push!(pmd_data["bus"][bus]["awaiting_ground"], transDict) end transDict["polarity"][w] = "forward" - transDict["tm_fix"][w] = fill(defaults["taps"][w], nphases) + transDict["tm"][w] = fill(defaults["taps"][w], nphases) end #transDict["source_id"] = "transformer.$(defaults["name"])" @@ -723,7 +703,9 @@ function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bo transDict["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 end - pmd_data["transformer_nw_lossy"][transDict["id"]] = transDict + add_virtual!(pmd_data, "transformer_nw", create_transformer_nw(; + Dict(Symbol.(keys(transDict)).=>values(transDict))... + )) end end @@ -733,7 +715,7 @@ end Adds PowerModels-style branch components based on DSS reactors to `pmd_data` from `dss_data` """ -function _dss2pmd_reactor!(pmd_data::Dict, dss_data::Dict, import_all::Bool) +function _dss2pmd_reactor_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) if !haskey(pmd_data, "branch") pmd_data["branch"] = [] end @@ -803,7 +785,7 @@ end Adds PowerModels-style pvsystems to `pmd_data` from `dss_data`. """ -function _dss2pmd_pvsystem!(pmd_data::Dict, dss_data::Dict, import_all::Bool) +function _dss2pmd_pvsystem_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) if !haskey(pmd_data, "pvsystem") pmd_data["pvsystem"] = [] end @@ -841,7 +823,7 @@ end Adds PowerModels-style storage to `pmd_data` from `dss_data` """ -function _dss2pmd_storage!(pmd_data::Dict, dss_data::Dict, import_all::Bool) +function _dss2pmd_storage_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) if !haskey(pmd_data, "storage") pmd_data["storage"] = [] end @@ -889,46 +871,6 @@ function _dss2pmd_storage!(pmd_data::Dict, dss_data::Dict, import_all::Bool) end -""" - _adjust_sourcegen_bounds!(pmd_data) - -Changes the bounds for the sourcebus generator by checking the emergamps of all -of the lines attached to the sourcebus and taking the sum of non-infinite -values. Defaults to Inf if all emergamps connected to sourcebus are also Inf. -This method was updated to include connected transformers as well. It know -has to occur after the call to InfrastructureModels.arrays_to_dicts, so the code -was adjusted to accomodate that. -""" -function _adjust_sourcegen_bounds!(pmd_data) - emergamps = Array{Float64,1}([0.0]) - sourcebus_n = find_bus(pmd_data["sourcebus"], pmd_data) - for (_,line) in pmd_data["branch"] - if (line["f_bus"] == sourcebus_n || line["t_bus"] == sourcebus_n) && !startswith(line["source_id"], "virtual") - append!(emergamps, get(line, "c_rating_b", get(line, "rate_b", missing)).values) - end - end - - if haskey(pmd_data, "transformer") - for (_,trans) in pmd_data["transformer"] - if trans["f_bus"] == sourcebus_n || trans["t_bus"] == sourcebus_n - append!(emergamps, trans["rate_b"].values) - end - end - end - - bound = sum(emergamps) - - pmd_data["gen"]["1"]["pmin"] = _PMs.MultiConductorVector(fill(-bound, size(pmd_data["gen"]["1"]["pmin"]))) - pmd_data["gen"]["1"]["pmax"] = _PMs.MultiConductorVector(fill( bound, size(pmd_data["gen"]["1"]["pmin"]))) - pmd_data["gen"]["1"]["qmin"] = _PMs.MultiConductorVector(fill(-bound, size(pmd_data["gen"]["1"]["pmin"]))) - pmd_data["gen"]["1"]["qmax"] = _PMs.MultiConductorVector(fill( bound, size(pmd_data["gen"]["1"]["pmin"]))) - - # set current rating of vbranch modelling internal impedance - vbranch = [br for (id, br) in pmd_data["branch"] if br["name"]=="sourcebus_vbranch"][1] - vbranch["rate_a"] = _PMs.MultiConductorVector(fill(bound, length(vbranch["rate_a"]))) -end - - "This function appends a component to a component dictionary of a pmd data model" function _push_dict_ret_key!(dict::Dict{String, Any}, v::Dict{String, Any}; assume_no_gaps=false) if isempty(dict) @@ -994,10 +936,12 @@ function _create_sourcebus_vbranch_dm!(pmd_data::Dict, circuit::Dict) rs = circuit["rmatrix"] xs = circuit["xmatrix"] - id = "_virtual_source_imp" - pmd_data["line"][id] = create_line(id, "_virtual_sourcebus", "sourcebus", size(rs)[1], - rs=rs, - xs=xs + N = size(rs)[1] + + add_line!(pmd_data, id="_virtual_source_imp", + f_bus="_virtual_sourcebus", t_bus="sourcebus", + f_connections=collect(1:N), t_connections=collect(1:N), + rs=rs, xs=xs ) #vbranch = _create_vbranch!(pmd_data, sourcebus, vsourcebus; name="sourcebus_vbranch", br_r=br_r, br_x=br_x) end @@ -1090,7 +1034,7 @@ end "Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" function parse_opendss_dm(dss_data::Dict; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict - pmd_data = Dict{String,Any}() + pmd_data = create_data_model() _correct_duplicate_components!(dss_data) @@ -1117,10 +1061,12 @@ function parse_opendss_dm(dss_data::Dict; import_all::Bool=false, vmin::Float64= defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), circuit["name"]; _to_sym_keys(circuit)...) #pmd_data["name"] = defaults["name"] - pmd_data["basekv"] = defaults["basekv"] - pmd_data["v_var_scalar"] = 1E3 - #pmd_data["baseMVA"] = defaults["basemva"] - pmd_data["basefreq"] = pop!(pmd_data, "defaultbasefreq") + pmd_data["settings"]["v_var_scalar"] = 1E3 + pmd_data["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))*1E3/pmd_data["settings"]["v_var_scalar"] + pmd_data["settings"]["set_vbase_bus"] = "sourcebus" + + pmd_data["settings"]["set_sbase_val"] = defaults["basemva"]*1E6/pmd_data["settings"]["v_var_scalar"] + pmd_data["settings"]["basefreq"] = pop!(pmd_data, "defaultbasefreq") #pmd_data["pu"] = defaults["pu"] #pmd_data["conductors"] = defaults["phases"] #pmd_data["sourcebus"] = defaults["bus1"] @@ -1137,7 +1083,6 @@ function parse_opendss_dm(dss_data::Dict; import_all::Bool=false, vmin::Float64= _dss2pmd_shunt_dm!(pmd_data, dss_data, import_all) - _discover_terminals!(pmd_data) #_dss2pmd_reactor!(pmd_data, dss_data, import_all) #_dss2pmd_gen!(pmd_data, dss_data, import_all) #_dss2pmd_pvsystem!(pmd_data, dss_data, import_all) @@ -1152,7 +1097,9 @@ function parse_opendss_dm(dss_data::Dict; import_all::Bool=false, vmin::Float64= _bank_transformers!(pmd_data) end - _create_sourcebus_vbranch_dm!(pmd_data, defaults) + #_create_sourcebus_vbranch_dm!(pmd_data, defaults) + + _discover_terminals!(pmd_data) #_adjust_sourcegen_bounds!(pmd_data) @@ -1165,8 +1112,8 @@ function _discover_terminals!(pmd_data) terminals = Dict{String, Set{Int}}([(bus["id"], Set{Int}()) for (_,bus) in pmd_data["bus"]]) for (_,line) in pmd_data["line"] - push!(terminals[line["f_bus"]], line["f_terminals"]...) - push!(terminals[line["t_bus"]], line["t_terminals"]...) + push!(terminals[line["f_bus"]], line["f_connections"]...) + push!(terminals[line["t_bus"]], line["t_connections"]...) end if haskey(pmd_data, "transformer_nw3ph_lossy") @@ -1195,18 +1142,18 @@ function _discover_terminals!(pmd_data) end bus["neutral"] = neutral if haskey(bus, "awaiting_ground") - bus["grounded"] = true - bus["rg"] = 0.0 - bus["xg"] = 0.0 + bus["grounded"] = [neutral] + bus["rg"] = [0.0] + bus["xg"] = [0.0] for comp in bus["awaiting_ground"] - if eltype(comp["terminals"])<:Array - for w in 1:length(comp["terminals"]) - if comp["buses"][w]==id - comp["terminals"][w] .+= (comp["terminals"][w].==0)*neutral + if eltype(comp["connections"])<:Array + for w in 1:length(comp["connections"]) + if comp["bus"][w]==id + comp["connections"][w] .+= (comp["connections"][w].==0)*neutral end end else - comp["terminals"] .+= (comp["terminals"].==0)*neutral + comp["connections"] .+= (comp["connections"].==0)*neutral end end delete!(bus, "awaiting_ground") @@ -1220,18 +1167,19 @@ end function _find_neutrals(pmd_data) vertices = [(id, t) for (id, bus) in pmd_data["bus"] for t in bus["terminals"]] neutrals = [] - edges = Set([((line["f_bus"], line["f_terminals"][c]),(line["t_bus"], line["t_terminals"][c])) for (id, line) in pmd_data["line"] for c in 1:length(line["f_terminals"])]) + edges = Set([((line["f_bus"], line["f_connections"][c]),(line["t_bus"], line["t_connections"][c])) for (id, line) in pmd_data["line"] for c in 1:length(line["f_connections"])]) bus_neutrals = [(id,bus["neutral"]) for (id,bus) in pmd_data["bus"] if haskey(bus, "neutral")] trans_neutrals = [] - for (_, tr) in pmd_data["transformer_nw_lossy"] - for w in 1:length(tr["terminals"]) + for (_, tr) in pmd_data["transformer_nw"] + for w in 1:length(tr["connections"]) if tr["configuration"][w] == "wye" - push!(trans_neutrals, (tr["buses"][w], tr["terminals"][w][end])) + @show tr + push!(trans_neutrals, (tr["bus"][w], tr["connections"][w][end])) end end end - load_neutrals = [(load["bus"],load["terminals"][end]) for (_,load) in pmd_data["load"] if load["configuration"]=="wye"] + load_neutrals = [(load["bus"],load["connections"][end]) for (_,load) in pmd_data["load"] if load["configuration"]=="wye"] neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) stack = deepcopy(neutrals) @@ -1275,7 +1223,8 @@ function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_l else return default end - if check_length && (default)!=length(ret) + + if check_length && length(default)!=length(ret) Memento.error("An incorrect number of nodes was specified; |$(parts[2])|!=$(length(default)).") end return ret diff --git a/test/data/opendss/case2_diag.dss b/test/data/opendss/case2_diag.dss index 24b39009b..99a2c7db9 100644 --- a/test/data/opendss/case2_diag.dss +++ b/test/data/opendss/case2_diag.dss @@ -13,7 +13,7 @@ New linecode.556MCM nphases=3 basefreq=50 ! ohms per 5 mile !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 ! 5 mile line +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 ! 5 mile line !Loads - single phase diff --git a/test/data/opendss/case3_balanced.dss b/test/data/opendss/case3_balanced.dss index d567d62ae..4ee15474c 100755 --- a/test/data/opendss/case3_balanced.dss +++ b/test/data/opendss/case3_balanced.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_balanced_basefreq.dss b/test/data/opendss/case3_balanced_basefreq.dss index 52b40328d..ee943afb7 100644 --- a/test/data/opendss/case3_balanced_basefreq.dss +++ b/test/data/opendss/case3_balanced_basefreq.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_balanced_battery.dss b/test/data/opendss/case3_balanced_battery.dss index 4057b067e..82a2fe70e 100644 --- a/test/data/opendss/case3_balanced_battery.dss +++ b/test/data/opendss/case3_balanced_battery.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_balanced_cap.dss b/test/data/opendss/case3_balanced_cap.dss index de92933dc..4edbf6113 100644 --- a/test/data/opendss/case3_balanced_cap.dss +++ b/test/data/opendss/case3_balanced_cap.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_balanced_isc.dss b/test/data/opendss/case3_balanced_isc.dss index 0ab8ab77e..ac840b17e 100644 --- a/test/data/opendss/case3_balanced_isc.dss +++ b/test/data/opendss/case3_balanced_isc.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_balanced_prop-order.dss b/test/data/opendss/case3_balanced_prop-order.dss index 665eefd81..e08fab186 100644 --- a/test/data/opendss/case3_balanced_prop-order.dss +++ b/test/data/opendss/case3_balanced_prop-order.dss @@ -18,8 +18,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM rmatrix=( 0.1000 | 0.0400 0.1000 | 0.0400 0.0400 0.1000) length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 like=OHLine linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM rmatrix=( 0.1000 | 0.0400 0.1000 | 0.0400 0.0400 0.1000) length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 like=OHLine linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_balanced_pv.dss b/test/data/opendss/case3_balanced_pv.dss index 04f9d2b4c..c1e68929b 100644 --- a/test/data/opendss/case3_balanced_pv.dss +++ b/test/data/opendss/case3_balanced_pv.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_balanced_switch.dss b/test/data/opendss/case3_balanced_switch.dss index 1c20f4b76..9df19d97a 100755 --- a/test/data/opendss/case3_balanced_switch.dss +++ b/test/data/opendss/case3_balanced_switch.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 switch=y ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 switch=y ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_delta_gens.dss b/test/data/opendss/case3_delta_gens.dss index cedf1f6aa..5f4ebd496 100644 --- a/test/data/opendss/case3_delta_gens.dss +++ b/test/data/opendss/case3_delta_gens.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=0.01 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=0.01 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=0.01 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=0.01 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_lm_1230.dss b/test/data/opendss/case3_lm_1230.dss index c06a2c559..3749ffe35 100644 --- a/test/data/opendss/case3_lm_1230.dss +++ b/test/data/opendss/case3_lm_1230.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=0.000000001 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=0.000000001 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=0.000000001 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=0.000000001 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_lm_models.dss b/test/data/opendss/case3_lm_models.dss index ff4d7fdbc..de59ebb85 100644 --- a/test/data/opendss/case3_lm_models.dss +++ b/test/data/opendss/case3_lm_models.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=0.01 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=0.01 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=0.01 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=0.01 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_unbalanced.dss b/test/data/opendss/case3_unbalanced.dss index 4652b3eb5..6e6ae6cfb 100755 --- a/test/data/opendss/case3_unbalanced.dss +++ b/test/data/opendss/case3_unbalanced.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_unbalanced_1phase-pv.dss b/test/data/opendss/case3_unbalanced_1phase-pv.dss index b953a1e10..0efdac936 100644 --- a/test/data/opendss/case3_unbalanced_1phase-pv.dss +++ b/test/data/opendss/case3_unbalanced_1phase-pv.dss @@ -17,8 +17,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 Primary.1.2.3.0 linecode = 556MCM length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/case3_unbalanced_assym_swap.dss b/test/data/opendss/case3_unbalanced_assym_swap.dss index 2fb6ecf55..adcc98bc8 100644 --- a/test/data/opendss/case3_unbalanced_assym_swap.dss +++ b/test/data/opendss/case3_unbalanced_assym_swap.dss @@ -19,8 +19,8 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines -New Line.OHLine bus1=sourcebus.3.2.1.0 Primary.3.2.1.0 linecode = 556MCM length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3.0 loadbus.1.2.3.0 linecode = 4/0QUAD length=1 ! 100 ft +New Line.OHLine bus1=sourcebus.3.2.1 Primary.3.2.1 linecode = 556MCM length=1 ! 5 mile line +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase @@ -34,4 +34,4 @@ Set tolerance=0.000001 set defaultbasefreq=50 Calcvoltagebases -Solve \ No newline at end of file +Solve diff --git a/test/data/opendss/case5_phase_drop.dss b/test/data/opendss/case5_phase_drop.dss index f8b5285e0..70cd3a209 100644 --- a/test/data/opendss/case5_phase_drop.dss +++ b/test/data/opendss/case5_phase_drop.dss @@ -16,7 +16,7 @@ new linecode.lc1_1ph nphases=1 ~ xmatrix = [ 0.01 ] ~ cmatrix = [ 50 ] -new line.line1 bus1=sourcebus.1.2.3.0 bus2=midbus.1.2.3.0 linecode=lc1_3ph +new line.line1 bus1=sourcebus.1.2.3 bus2=midbus.1.2.3 linecode=lc1_3ph new line.line2 bus1=midbus.1 bus2=l1.1 linecode=lc1_1ph new line.line3 bus1=midbus.2.3 bus2=l2.2.3 linecode=lc1_2ph new line.line4 bus1=midbus.3 bus2=cap1.3 linecode=lc1_1ph diff --git a/test/data/opendss/case_mxshunt.dss b/test/data/opendss/case_mxshunt.dss index 16097e23b..4fd525a04 100644 --- a/test/data/opendss/case_mxshunt.dss +++ b/test/data/opendss/case_mxshunt.dss @@ -16,7 +16,7 @@ New linecode.LINECODE1 nphases=3 basefreq=50 ! ohms per 5 mile !Define lines -New Line.LINE1 bus1=sourcebus.1.2.3.0 loadbus.1.2.3.0 linecode = LINECODE1 length=0.5 +New Line.LINE1 bus1=sourcebus.1.2.3 loadbus.1.2.3 linecode = LINECODE1 length=0.5 !Loads - single phase ! single-phase loads diff --git a/test/data/opendss/case_mxshunt_2.dss b/test/data/opendss/case_mxshunt_2.dss index 2bb9f00da..d6566e667 100644 --- a/test/data/opendss/case_mxshunt_2.dss +++ b/test/data/opendss/case_mxshunt_2.dss @@ -16,7 +16,7 @@ New linecode.LINECODE1 nphases=3 basefreq=50 ! ohms per 5 mile !Define lines -New Line.LINE1 bus1=sourcebus.1.2.3.0 loadbus.1.2.3.0 linecode = LINECODE1 length=0.5 +New Line.LINE1 bus1=sourcebus.1.2.3 loadbus.1.2.3 linecode = LINECODE1 length=0.5 !Loads - single phase ! This will parse to a matrix shunt diff --git a/test/data/opendss/loadparser_error.dss b/test/data/opendss/loadparser_error.dss index ec77457d9..02b48b12d 100644 --- a/test/data/opendss/loadparser_error.dss +++ b/test/data/opendss/loadparser_error.dss @@ -13,7 +13,7 @@ New linecode.556MCM nphases=3 basefreq=50 ! ohms per 5 mile !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 loadbus.1.2.3.0 linecode = 556MCM length=0.000000001 ! 5 mile line +New Line.OHLine bus1=sourcebus.1.2.3 loadbus.1.2.3 linecode = 556MCM length=0.000000001 ! 5 mile line !Loads - single phase diff --git a/test/data/opendss/loadparser_warn_model.dss b/test/data/opendss/loadparser_warn_model.dss index e5a3ba98d..95a4a9199 100644 --- a/test/data/opendss/loadparser_warn_model.dss +++ b/test/data/opendss/loadparser_warn_model.dss @@ -13,7 +13,7 @@ New linecode.556MCM nphases=3 basefreq=50 ! ohms per 5 mile !Define lines -New Line.OHLine bus1=sourcebus.1.2.3.0 loadbus.1.2.3.0 linecode = 556MCM length=0.000000001 ! 5 mile line +New Line.OHLine bus1=sourcebus.1.2.3 loadbus.1.2.3 linecode = 556MCM length=0.000000001 ! 5 mile line !Loads - single phase diff --git a/test/data/opendss/test2_master.dss b/test/data/opendss/test2_master.dss index 456170cfc..dcec4eac3 100644 --- a/test/data/opendss/test2_master.dss +++ b/test/data/opendss/test2_master.dss @@ -15,7 +15,7 @@ New Line.L1-2 bus1=b3-1.2 bus2=b4.2 length=0.032175613 units=km Linecode=lc1 New Line.L2 bus1=b5 bus2=b6_check-chars length=0.013516796 units=none linecode=lc/2 New "Line.L3" phases=3 bus1=b7.1.2.3 bus2=b9.1.2.3 linecode=300 normamps=400 emergamps=600 faultrate=0.1 pctperm=20 repair=0 length=2.58 New "Line._L4" phases=3 bus1=b8.1.2.3 bus2=b10.1.2.3 linecode=300 normamps=400 emergamps=600 faultrate=0.1 pctperm=20 repair=0 length=1.73 switch=y -New line.l5 phases=3 bus1=_b2.1.2.3.0 bus2=b7.1.2.3.0 linecode=lc8 +New line.l5 phases=3 bus1=_b2.1.2.3 bus2=b7.1.2.3 linecode=lc8 New line.l6 phases=3 bus1=b1.1.2.3 bus2=b10.1.2.3 linecode=lc9 new line.l7 bus1=_b2 bus2=b10 like=L2 linecode=lc10 diff --git a/test/data/opendss/virtual_sourcebus.dss b/test/data/opendss/virtual_sourcebus.dss index e0f000d6c..57004b5ce 100644 --- a/test/data/opendss/virtual_sourcebus.dss +++ b/test/data/opendss/virtual_sourcebus.dss @@ -16,7 +16,7 @@ New linecode.LINECODE1 nphases=3 basefreq=50 ! ohms per 5 mile !Define lines -New Line.LINE1 bus1=sourcebus.1.2.3.0 loadbus.1.2.3.0 linecode = LINECODE1 length=0.5 +New Line.LINE1 bus1=sourcebus.1.2.3 loadbus.1.2.3 linecode = LINECODE1 length=0.5 !Loads - single phase ! single-phase loads From f6ff62fca49b3101752a996d8539fed2285d5a77 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Thu, 20 Feb 2020 19:40:32 +0100 Subject: [PATCH 011/224] rm data model changes --- src/PowerModelsDistribution.jl | 10 - src/io/common_dm.jl | 34 - src/io/data_model.md | 188 ----- src/io/data_model_components.jl | 691 ----------------- src/io/data_model_test.jl | 161 ---- src/io/data_model_test_3w.jl | 84 +++ src/io/data_model_util.jl | 164 ---- src/io/opendss_dm.jl | 1231 ------------------------------- 8 files changed, 84 insertions(+), 2479 deletions(-) delete mode 100644 src/io/common_dm.jl delete mode 100644 src/io/data_model.md delete mode 100644 src/io/data_model_components.jl delete mode 100644 src/io/data_model_test.jl create mode 100644 src/io/data_model_test_3w.jl delete mode 100644 src/io/data_model_util.jl delete mode 100644 src/io/opendss_dm.jl diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 2b1af1fe1..80ee7ff68 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -39,22 +39,12 @@ module PowerModelsDistribution include("core/constraint_template.jl") include("core/relaxation_scheme.jl") - include("io/common_dm.jl") - include("io/opendss_dm.jl") - include("io/common.jl") include("io/json.jl") include("io/dss_parse.jl") include("io/dss_structs.jl") include("io/opendss.jl") - #include("io/common_dm.jl") - #include("io/opendss_dm.jl") - include("io/data_model_components.jl") - include("core/data_model_mapping.jl") - include("core/data_model_pu.jl") - include("io/data_model_util.jl") - include("prob/mld.jl") include("prob/opf.jl") include("prob/opf_iv.jl") diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl deleted file mode 100644 index e6264b1ae..000000000 --- a/src/io/common_dm.jl +++ /dev/null @@ -1,34 +0,0 @@ -""" - parse_file(io) - -Parses the IOStream of a file into a Three-Phase PowerModels data structure. -""" -function parse_file_dm(io::IO; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, filetype::AbstractString="json", bank_transformers::Bool=true) - if filetype == "m" - pmd_data = PowerModelsDistribution.parse_matlab(io) - elseif filetype == "dss" - Memento.warn(_LOGGER, "Not all OpenDSS features are supported, currently only minimal support for lines, loads, generators, and capacitors as shunts. Transformers and reactors as transformer branches are included, but value translation is not fully supported.") - pmd_data = PowerModelsDistribution.parse_opendss_dm(io; import_all=import_all, vmin=vmin, vmax=vmax, bank_transformers=bank_transformers) - elseif filetype == "json" - pmd_data = PowerModels.parse_json(io; validate=false) - else - Memento.error(_LOGGER, "only .m and .dss files are supported") - end - - #correct_network_data!(pmd_data) - - return pmd_data -end - - -"" -function parse_file_dm(file::String; kwargs...) - pmd_data = open(file) do io - parse_file_dm(io; filetype=split(lowercase(file), '.')[end], kwargs...) - end - data_model_map!(pmd_data) - data_model_make_pu!(pmd_data) - data_model_index!(pmd_data) - data_model_make_compatible_v8!(pmd_data) - return pmd_data -end diff --git a/src/io/data_model.md b/src/io/data_model.md deleted file mode 100644 index 9321a962d..000000000 --- a/src/io/data_model.md +++ /dev/null @@ -1,188 +0,0 @@ - -# Shared patterns -- Each component has a unique (amonst components of the same type) identifier `id`. -- Everything is defined in SI units, except when a base is explicitly mentioned in the description. -- The default sometimes only mentions the default element, not the default value (e.g. `true` and not `fill(fill(true, 3), 3)`) - -# Data model - -## Bus - -The data model below allows us to include buses of arbitrary many terminals (i.e., more than the usual four). This would be useful for -- underground lines with multiple neutrals which are not joined at every bus; -- distribution lines that carry several conventional lines in parallel (see for example the quad circuits in NEVTestCase). - -name | default | type | description -------|----------|-----|----------- -terminals|[1,2,3,4]|Vector -vm_max | / | Vector | maximum conductor-to-ground voltage magnitude -vm_min | / | Vector | minimum conductor-to-ground voltage magnitude -vm_cd_max | / | Vector{Tuple} | e.g. [(1,2,210)] means \|U1-U2\|>210 -vm_cd_min | / | Vector{Tuple} | e.g. [(1,2,230)] means \|U1-U2\|<230 -grounded | [] | Vector | a list of terminals which are grounded -rg | [] | Vector | resistance of each defined grounding -xg | [] | Vector | reactance of each defined grounding - -The tricky part is how to encode bounds for these type of buses. The most general is defining a list of three-tupples. Take for example a typical bus in a three-phase, four-wire network, where `terminals=[a,b,c,n]`. Such a bus might have -- phase-to-neutral bounds `vm_pn_max=250`, `vm_pn_min=210` -- and phase-to-phase bounds `vm_pp_max=440`, `vm_pp_min=360 -`. - -We can then define this equivalently as -- `vm_cd_max = [(a,n,250), (b,n,250), (c,n,250), (a,b,440), (b,c,440), (c,a,440)]` -- `vm_cd_min = [(a,n,210), (b,n,210), (c,n,210), (a,b,360), (b,c,360), (c,a,360)]` - -Since this might be confusing for novice users, we also allow the user to define bounds through the following properties. - - -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -phases | [1,2,3] | Vector -neutral | 4 | | maximum conductor-to-ground voltage magnitude -vm_pn_max | / | Real | maximum phase-to-neutral voltage magnitude for all phases -vm_pn_min | / | Real | minimum phase-to-neutral voltage magnitude for all phases -vm_pp_max | / | Real | maximum phase-to-phase voltage magnitude for all phases -vm_pp_min | / | Real | minimum phase-to-phase voltage magnitude for all phases - -## Line - -This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` and `g_to`; when these properties are additionally specified, they overwrite the one supplied through the linecode. - -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -f_bus | / | | -f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects -t_bus | / | | -t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects -linecode | / | | a linecode -rs | / | | series resistance matrix, size of n_conductors x n_conductors -xs | / | | series reactance matrix, size of n_conductors x n_conductors -g_fr | / | | from-side conductance -b_fr | / | | from-side susceptance -g_to | / | | to-side conductance -b_to | / | | to-side susceptance -c_rating | / | | symmetrically applicable current rating -s_rating | / | | symmetrically applicable power rating - -## Linecode - -- Should the linecode also include a `c_rating` and/`s_rating`? - -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -rs | / | | series resistance matrix -xs | / | | series reactance matrix n_conductors -g_fr | / | | from-side conductance -b_fr | / | | from-side susceptance -g_to | / | | to-side conductance -b_to | / | | to-side susceptance - -## Shunt - -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -bus | / | | -connections | / | | -g | / | | conductance, size should be \|connections\|x\|connections\| -b | / | | susceptance, size should be \|connections\|x\|connections\| - -## Capacitor - -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -bus | / | | -connections | / | | -qd_ref | / | | conductance, size should be \|connections\|x\|connections\| -vnom | / | | conductance, size should be \|connections\|x\|connections\| - -## Load - -name | default | type | description -------|----------|----|----------- -id | / | | unique identifier -bus | / | | -connections | / | | -configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral -model | / | | indicates the type of voltage-dependency - -### `model=constant_power` - -name | default | type | description -------|----------|----|----------- -pd | / | Vector{Real} | -qd | / | Vector{Real} | - -### `model=constant_current/impedance` - -name | default | type | description -------|----------|----|----------- -pd_ref | / | Vector{Real} | -qd_ref | / | Vector{Real} | -vnom | / | Real | - -### `model=exponential` - -name | default | type | description -------|----------|----|----------- -pd_ref | / | Vector{Real} | -qd_ref | / | Vector{Real} | -vnom | / | Real | -exp_p | / | Vector{Real} | -exp_q | / | Vector{Real} | - -## Generator - -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -bus | / | | -connections | / | | -configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral -pg_min | / | | lower bound on active power generation per phase -pg_max | / | | upper bound on active power generation per phase -qg_min | / | | lower bound on reactive power generation per phase -qg_max | / | | upper bound on reactive power generation per phase - -## AL2W Transformer -These are transformers are assymetric (A), lossless (L) and two-winding (2W). Assymetric refers to the fact that the secondary is always has a `wye` configuration, whilst the primary can be `delta`. - -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -n_phases | size(rs)[1] | Int>0 | number of phases -f_bus | / | | -f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects -t_bus | / | | -t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects -configuration | / | {wye, delta} | for the from-side; the to-side is always connected in wye -tm_nom | / | Real | nominal tap ratio for the transformer -tm_max | / | Vector | maximum tap ratio for each phase (base=`tm_nom`) -tm_min | / | Vector | minimum tap ratio for each phase (base=`tm_nom`) -tm_set | fill(1.0, `n_phases`) | Vector | set tap ratio for each phase (base=`tm_nom`) -tm_fix | fill(true, `n_phases`) | Vector | indicates for each phase whether the tap ratio is fixed - -TODO: add tm stuff - -## Transformer -These are n-winding, n-phase, lossy transformers. Note that most properties are now Vectors (or Vectors of Vectors), indexed over the windings. - -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -n_phases | size(rs)[1] | Int>0 | number of phases -n_windings | size(rs)[1] | Int>0 | number of windings -bus | / | Vector | list of bus for each winding -connections | | Vector{Vector} | list of connection for each winding -configurations | | Vector{{wye, delta}} | list of configuration for each winding -xsc | 0.0 | Vector | list of short-circuit reactances between each pair of windings; enter as a list of the upper-triangle elements -rs | 0.0 | Vector | list of the winding resistance for each winding -tm_nom | / | Vector{Real} | nominal tap ratio for the transformer -tm_max | / | Vector{Vector} | maximum tap ratio for each winding and phase (base=`tm_nom`) -tm_min | / | Vector{Vector} | minimum tap ratio for for each winding and phase (base=`tm_nom`) -tm_set | 1.0 | Vector{Vector} | set tap ratio for each winding and phase (base=`tm_nom`) -tm_fix | true | Vector{Vector} | indicates for each winding and phase whether the tap ratio is fixed diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl deleted file mode 100644 index 6d8faecaf..000000000 --- a/src/io/data_model_components.jl +++ /dev/null @@ -1,691 +0,0 @@ -#TODO -# Can buses in a voltage zone have different terminals? -# Add current/power bounds to data model - - -function copy_kwargs_to_dict_if_present!(dict, kwargs, args) - for arg in args - if haskey(kwargs, arg) - dict[string(arg)] = kwargs[arg] - end - end -end - -function add_kwarg!(dict, kwargs, name, default) - if haskey(kwargs, name) - dict[string(name)] = kwargs[name] - else - dict[string(name)] = default - end -end - -function component_dict_from_list!(list) - dict = Dict{String, Any}() - for comp_dict in list - dict[comp_dict["id"]] = comp_dict - end - return dict -end - -REQUIRED_FIELDS = Dict{Symbol, Any}() -DTYPES = Dict{Symbol, Any}() -CHECKS = Dict{Symbol, Any}() - -function check_dtypes(dict, dtypes, comp_type, id) - for key in keys(dict) - symb = Symbol(key) - if haskey(dtypes, symb) - @assert(isa(dict[key], dtypes[symb]), "$comp_type $id: the property $key should be a $(dtypes[symb]), not a $(typeof(dict[key])).") - else - #@assert(false, "$comp_type $id: the property $key is unknown.") - end - end -end - - -function add!(data_model, comp_type, comp_dict) - @assert(haskey(comp_dict, "id"), "The component does not have an id defined.") - id = comp_dict["id"] - if !haskey(data_model, comp_type) - data_model[comp_type] = Dict{Any, Any}() - else - @assert(!haskey(data_model[comp_type], id), "There is already a $comp_type with id $id.") - end - data_model[comp_type][id] = comp_dict -end - -function _add_unused_kwargs!(comp_dict, kwargs) - for (prop, val) in kwargs - if !haskey(comp_dict, "$prop") - comp_dict["$prop"] = val - end - end -end - -function check_data_model(data) - for component in keys(DTYPES) - if haskey(data, string(component)) - for (id, comp_dict) in data[string(component)] - if haskey(REQUIRED_FIELDS, component) - for field in REQUIRED_FIELDS[component] - @assert(haskey(comp_dict, string(field)), "The property \'$field\' is missing for $component $id.") - end - end - if haskey(DTYPES, component) - check_dtypes(comp_dict, DTYPES[component], component, id) - end - if haskey(CHECKS, component) - CHECKS[component](data, comp_dict) - end - end - end - end -end - - -function create_data_model(; kwargs...) - data_model = Dict{String, Any}("settings"=>Dict{String, Any}()) - - add_kwarg!(data_model["settings"], kwargs, :v_var_scalar, 1E3) - - _add_unused_kwargs!(data_model["settings"], kwargs) - - return data_model -end - -# COMPONENTS -#*################# - -DTYPES[:ev] = Dict() -DTYPES[:storage] = Dict() -DTYPES[:pv] = Dict() -DTYPES[:wind] = Dict() -DTYPES[:switch] = Dict() -DTYPES[:shunt] = Dict() -DTYPES[:autotransformer] = Dict() -DTYPES[:synchronous_generator] = Dict() -DTYPES[:zip_load] = Dict() -DTYPES[:grounding] = Dict( - :bus => Any, - :rg => Real, - :xg => Real, -) -DTYPES[:synchronous_generator] = Dict() -DTYPES[:boundary] = Dict() -DTYPES[:meter] = Dict() - - -function _check_same_size(data, keys; context=missing) - size_comp = size(data[string(keys[1])]) - for key in keys[2:end] - @assert(all(size(data[string(key)]).==size_comp), "$context: the property $key should have the same size as $(keys[1]).") - end -end - - -function _check_has_size(data, keys, size_comp; context=missing, allow_missing=true) - for key in keys - if haskey(data, key) || !allow_missing - @assert(all(size(data[string(key)]).==size_comp), "$context: the property $key should have as size $size_comp.") - end - end -end - -function _check_connectivity(data, comp_dict; context=missing) - - if haskey(comp_dict, "f_bus") - # two-port element - _check_bus_and_terminals(data, comp_dict["f_bus"], comp_dict["f_connections"], context) - _check_bus_and_terminals(data, comp_dict["t_bus"], comp_dict["t_connections"], context) - elseif haskey(comp_dict, "bus") - if isa(comp_dict["bus"], Vector) - for i in 1:length(comp_dict["bus"]) - _check_bus_and_terminals(data, comp_dict["bus"][i], comp_dict["connections"][i], context) - end - else - _check_bus_and_terminals(data, comp_dict["bus"], comp_dict["connections"], context) - end - end -end - - -function _check_bus_and_terminals(data, bus_id, terminals, context=missing) - @assert(haskey(data, "bus") && haskey(data["bus"], bus_id), "$context: the bus $bus_id is not defined.") - bus = data["bus"][bus_id] - for t in terminals - @assert(t in bus["terminals"], "$context: bus $(bus["id"]) does not have terminal \'$t\'.") - end -end - - -function _check_has_keys(comp_dict, keys; context=missing) - for key in keys - @assert(haskey(comp_dict, key), "$context: the property $key is missing.") - end -end - -function _check_configuration_infer_dim(comp_dict; context=missing) - conf = comp_dict["configuration"] - @assert(conf in ["delta", "wye"], "$context: the configuration should be \'delta\' or \'wye\', not \'$conf\'.") - return conf=="wye" ? length(comp_dict["connections"])-1 : length(comp_dict["connections"]) -end - - -# linecode - -DTYPES[:linecode] = Dict( - :id => Any, - :rs => Array{<:Real, 2}, - :xs => Array{<:Real, 2}, - :g_fr => Array{<:Real, 2}, - :g_to => Array{<:Real, 2}, - :b_fr => Array{<:Real, 2}, - :b_to => Array{<:Real, 2}, -) - -REQUIRED_FIELDS[:linecode] = keys(DTYPES[:linecode]) - -CHECKS[:linecode] = function check_linecode(data, linecode) - _check_same_size(linecode, [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to]) -end - -function create_linecode(; kwargs...) - linecode = Dict{String,Any}() - - n_conductors = 0 - for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] - if haskey(kwargs, key) - n_conductors = size(kwargs[key])[1] - end - end - add_kwarg!(linecode, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) - - _add_unused_kwargs!(linecode, kwargs) - - return linecode -end - -# line - -DTYPES[:line] = Dict( - :id => Any, - :status => Int, - :f_bus => AbstractString, - :t_bus => AbstractString, - :f_connections => Vector{<:Int}, - :t_connections => Vector{<:Int}, - :linecode => AbstractString, - :length => Real, - :c_rating =>Vector{<:Real}, - :s_rating =>Vector{<:Real}, - :angmin=>Vector{<:Real}, - :angmax=>Vector{<:Real}, - :rs => Array{<:Real, 2}, - :xs => Array{<:Real, 2}, - :g_fr => Array{<:Real, 2}, - :g_to => Array{<:Real, 2}, - :b_fr => Array{<:Real, 2}, - :b_to => Array{<:Real, 2}, -) - -REQUIRED_FIELDS[:line] = [:id, :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length] - -CHECKS[:line] = function check_line(data, line) - i = line["id"] - - # for now, always require a line code - if haskey(line, "linecode") - # line is defined with a linecode - @assert(haskey(line, "length"), "line $i: a line defined through a linecode, should have a length property.") - - linecode_id = line["linecode"] - @assert(haskey(data, "linecode") && haskey(data["linecode"], "$linecode_id"), "line $i: the linecode $linecode_id is not defined.") - linecode = data["linecode"]["$linecode_id"] - - for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - @assert(!haskey(line, key), "line $i: a line with a linecode, should not specify $key; this is already done by the linecode.") - end - - N = size(linecode["rs"])[1] - @assert(length(line["f_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") - @assert(length(line["t_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") - else - # normal line - @assert(!haskey(line, "length"), "line $i: length only makes sense for linees defined through linecodes.") - for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - @assert(haskey(line, key), "line $i: a line without linecode, should specify $key.") - end - end - - _check_connectivity(data, line, context="line $(line["id"])") -end - - -function create_line(; kwargs...) - line = Dict{String,Any}() - - add_kwarg!(line, kwargs, :status, 1) - add_kwarg!(line, kwargs, :f_connections, collect(1:4)) - add_kwarg!(line, kwargs, :t_connections, collect(1:4)) - - N = length(line["f_connections"]) - add_kwarg!(line, kwargs, :angmin, fill(-60/180*pi, N)) - add_kwarg!(line, kwargs, :angmax, fill( 60/180*pi, N)) - - # if no linecode, then populate loss parameters with zero - if !haskey(kwargs, :linecode) - n_conductors = 0 - for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] - if haskey(kwargs, key) - n_conductors = size(kwargs[key])[1] - end - end - add_kwarg!(line, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) - end - - _add_unused_kwargs!(line, kwargs) - - return line -end - -# Bus - -DTYPES[:bus] = Dict( - :id => Any, - :status => Int, - :bus_type => Int, - :terminals => Array{<:Any}, - :phases => Array{<:Int}, - :neutral => Union{Int, Missing}, - :grounded => Array{<:Any}, - :rg => Array{<:Real}, - :xg => Array{<:Real}, - :vm_pn_min => Real, - :vm_pn_max => Real, - :vm_pp_min => Real, - :vm_pp_max => Real, - :vm_min => Array{<:Real, 1}, - :vm_max => Array{<:Real, 1}, - :vm_fix => Array{<:Real, 1}, - :va_fix => Array{<:Real, 1}, -) - -REQUIRED_FIELDS[:bus] = [:id, :status, :terminals, :grounded, :rg, :xg] - -CHECKS[:bus] = function check_bus(data, bus) - id = bus["id"] - - _check_same_size(bus, ["grounded", "rg", "xg"], context="bus $id") - - N = length(bus["terminals"]) - _check_has_size(bus, ["vm_max", "vm_min", "vm", "va"], N, context="bus $id") - - if haskey(bus, "neutral") - assert(haskey(bus, "phases"), "bus $id: has a neutral, but no phases.") - end -end - -function create_bus(; kwargs...) - bus = Dict{String,Any}() - - add_kwarg!(bus, kwargs, :status, 1) - add_kwarg!(bus, kwargs, :terminals, collect(1:4)) - add_kwarg!(bus, kwargs, :grounded, []) - add_kwarg!(bus, kwargs, :bus_type, 1) - add_kwarg!(bus, kwargs, :rg, Array{Float64, 1}()) - add_kwarg!(bus, kwargs, :xg, Array{Float64, 1}()) - - _add_unused_kwargs!(bus, kwargs) - - return bus -end - -# Load - -DTYPES[:load] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :configuration => String, - :model => String, - :pd => Array{<:Real, 1}, - :qd => Array{<:Real, 1}, - :pd_ref => Array{<:Real, 1}, - :qd_ref => Array{<:Real, 1}, - :vnom => Array{<:Real, 1}, - :alpha => Array{<:Real, 1}, - :beta => Array{<:Real, 1}, -) - -REQUIRED_FIELDS[:load] = [:id, :status, :bus, :connections, :configuration] - -CHECKS[:load] = function check_load(data, load) - id = load["id"] - - N = _check_configuration_infer_dim(load; context="load $id") - - model = load["model"] - @assert(model in ["constant_power", "constant_impedance", "constant_current", "exponential"]) - if model=="constant_power" - _check_has_keys(load, ["pd", "qd"], context="load $id, $model:") - _check_has_size(load, ["pd", "qd"], N, context="load $id, $model:") - elseif model=="exponential" - _check_has_keys(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], context="load $id, $model") - _check_has_size(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], N, context="load $id, $model:") - else - _check_has_keys(load, ["pd_ref", "qd_ref", "vnom"], context="load $id, $model") - _check_has_size(load, ["pd_ref", "qd_ref", "vnom"], N, context="load $id, $model:") - end - - _check_connectivity(data, load; context="load $id") -end - - -function create_load(; kwargs...) - load = Dict{String,Any}() - - add_kwarg!(load, kwargs, :status, 1) - add_kwarg!(load, kwargs, :configuration, "wye") - add_kwarg!(load, kwargs, :connections, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) - add_kwarg!(load, kwargs, :model, "constant_power") - if load["model"]=="constant_power" - add_kwarg!(load, kwargs, :pd, fill(0.0, 3)) - add_kwarg!(load, kwargs, :qd, fill(0.0, 3)) - else - add_kwarg!(load, kwargs, :pd_ref, fill(0.0, 3)) - add_kwarg!(load, kwargs, :qd_ref, fill(0.0, 3)) - end - - _add_unused_kwargs!(load, kwargs) - - return load -end - -# generator - -DTYPES[:generator] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :configuration => String, - :cost => Vector{<:Real}, - :pg => Array{<:Real, 1}, - :qg => Array{<:Real, 1}, - :pg_min => Array{<:Real, 1}, - :pg_max => Array{<:Real, 1}, - :qg_min => Array{<:Real, 1}, - :qg_max => Array{<:Real, 1}, -) - -REQUIRED_FIELDS[:generator] = [:id, :status, :bus, :connections] - -CHECKS[:generator] = function check_generator(data, generator) - id = generator["id"] - - N = _check_configuration_infer_dim(generator; context="generator $id") - _check_has_size(generator, ["pd", "qd", "pd_min", "pd_max", "qd_min", "qd_max"], N, context="generator $id") - - _check_connectivity(data, generator; context="generator $id") -end - -function create_generator(; kwargs...) - generator = Dict{String,Any}() - - add_kwarg!(generator, kwargs, :status, 1) - add_kwarg!(generator, kwargs, :configuration, "wye") - add_kwarg!(generator, kwargs, :cost, [1.0, 0.0]*1E-3) - add_kwarg!(generator, kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) - - _add_unused_kwargs!(generator, kwargs) - - return generator -end - - -# Transformer, n-windings three-phase lossy - - -DTYPES[:transformer_nw] = Dict( - :id => Any, - :status => Int, - :bus => Array{<:AbstractString, 1}, - :connections => Vector, - :vnom => Array{<:Real, 1}, - :snom => Array{<:Real, 1}, - :configuration => Array{String, 1}, - :polarity => Array{Bool, 1}, - :xsc => Array{<:Real, 1}, - :rs => Array{<:Real, 1}, - :noloadloss => Real, - :imag => Real, - :tm_fix => Array{Array{Bool, 1}, 1}, - :tm => Array{<:Array{<:Real, 1}, 1}, - :tm_min => Array{<:Array{<:Real, 1}, 1}, - :tm_max => Array{<:Array{<:Real, 1}, 1}, - :tm_step => Array{<:Array{<:Real, 1}, 1}, -) - -REQUIRED_FIELDS[:transformer_nw] = keys(DTYPES[:transformer_nw]) - - -CHECKS[:transformer_nw] = function check_transformer_nw(data, trans) - id = trans["id"] - nrw = length(trans["bus"]) - _check_has_size(trans, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_min", "tm_max", "tm_step"], nrw, context="trans $id") - @assert(length(trans["xsc"])==(nrw^2-nrw)/2) - - nphs = [] - for w in 1:nrw - @assert(trans["configuration"][w] in ["wye", "delta"]) - conf = trans["configuration"][w] - conns = trans["connections"][w] - nph = conf=="wye" ? length(conns)-1 : length(conns) - @assert(all(nph.==nphs), "transformer $id: winding $w has a different number of phases than the previous ones.") - push!(nphs, nph) - #TODO check length other properties - end - - _check_connectivity(data, trans; context="transformer_nw $id") -end - - -function create_transformer_nw(; kwargs...) - trans = Dict{String,Any}() - - @assert(haskey(kwargs, :bus), "You have to specify at least the buses.") - n_windings = length(kwargs[:bus]) - add_kwarg!(trans, kwargs, :status, 1) - add_kwarg!(trans, kwargs, :configuration, fill("wye", n_windings)) - add_kwarg!(trans, kwargs, :polarity, fill(true, n_windings)) - add_kwarg!(trans, kwargs, :rs, fill(0.0, n_windings)) - add_kwarg!(trans, kwargs, :xsc, fill(0.0, n_windings^2-n_windings)) - add_kwarg!(trans, kwargs, :noloadloss, 0.0) - add_kwarg!(trans, kwargs, :imag, 0.0) - add_kwarg!(trans, kwargs, :tm, fill(fill(1.0, 3), n_windings)) - add_kwarg!(trans, kwargs, :tm_min, fill(fill(0.9, 3), n_windings)) - add_kwarg!(trans, kwargs, :tm_max, fill(fill(1.1, 3), n_windings)) - add_kwarg!(trans, kwargs, :tm_step, fill(fill(1/32, 3), n_windings)) - add_kwarg!(trans, kwargs, :tm_fix, fill(fill(true, 3), n_windings)) - - _add_unused_kwargs!(trans, kwargs) - - return trans -end - -# -# # Transformer, two-winding three-phase -# -# DTYPES[:transformer_2w_ideal] = Dict( -# :id => Any, -# :f_bus => String, -# :t_bus => String, -# :configuration => String, -# :f_terminals => Array{Int, 1}, -# :t_terminals => Array{Int, 1}, -# :tm_nom => Real, -# :tm_set => Real, -# :tm_min => Real, -# :tm_max => Real, -# :tm_step => Real, -# :tm_fix => Real, -# ) -# -# -# CHECKS[:transformer_2w_ideal] = function check_transformer_2w_ideal(data, trans) -# end -# -# -# function create_transformer_2w_ideal(id, f_bus, t_bus, tm_nom; kwargs...) -# trans = Dict{String,Any}() -# trans["id"] = id -# trans["f_bus"] = f_bus -# trans["t_bus"] = t_bus -# trans["tm_nom"] = tm_nom -# add_kwarg!(trans, kwargs, :configuration, "wye") -# add_kwarg!(trans, kwargs, :f_terminals, trans["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) -# add_kwarg!(trans, kwargs, :t_terminals, [1, 2, 3, 4]) -# add_kwarg!(trans, kwargs, :tm_set, 1.0) -# add_kwarg!(trans, kwargs, :tm_min, 0.9) -# add_kwarg!(trans, kwargs, :tm_max, 1.1) -# add_kwarg!(trans, kwargs, :tm_step, 1/32) -# add_kwarg!(trans, kwargs, :tm_fix, true) -# return trans -# end - - -# Capacitor - -DTYPES[:capacitor] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :configuration => String, - :qd_ref => Array{<:Real, 1}, - :vnom => Real, -) - -REQUIRED_FIELDS[:capacitor] = keys(DTYPES[:capacitor]) - - -CHECKS[:capacitor] = function check_capacitor(data, cap) - id = cap["id"] - N = length(cap["connections"]) - config = cap["configuration"] - if config=="wye" - @assert(length(cap["qd_ref"])==N-1, "capacitor $id: qd_ref should have $(N-1) elements.") - else - @assert(length(cap["qd_ref"])==N, "capacitor $id: qd_ref should have $N elements.") - end - @assert(config in ["delta", "wye", "wye-grounded", "wye-floating"]) - if config=="delta" - @assert(N>=3, "Capacitor $id: delta-connected capacitors should have at least 3 elements.") - end - - _check_connectivity(data, cap; context="capacitor $id") -end - - -function create_capacitor(; kwargs...) - cap = Dict{String,Any}() - - add_kwarg!(cap, kwargs, :status, 1) - add_kwarg!(cap, kwargs, :configuration, "wye") - add_kwarg!(cap, kwargs, :connections, collect(1:4)) - add_kwarg!(cap, kwargs, :qd_ref, fill(0.0, 3)) - - _add_unused_kwargs!(cap, kwargs) - - return cap -end - - -# Shunt - -DTYPES[:shunt] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :g_sh => Array{<:Real, 2}, - :b_sh => Array{<:Real, 2}, -) - -REQUIRED_FIELDS[:shunt] = keys(DTYPES[:shunt]) - - -CHECKS[:shunt] = function check_shunt(data, shunt) - _check_connectivity(data, shunt; context="shunt $id") - -end - - -function create_shunt(; kwargs...) - shunt = Dict{String,Any}() - - N = length(kwargs[:connections]) - - add_kwarg!(shunt, kwargs, :status, 1) - add_kwarg!(shunt, kwargs, :g_sh, fill(0.0, N, N)) - add_kwarg!(shunt, kwargs, :b_sh, fill(0.0, N, N)) - - _add_unused_kwargs!(shunt, kwargs) - - return shunt -end - - -# voltage source - -DTYPES[:voltage_source] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :vm =>Array{<:Real}, - :va =>Array{<:Real}, - :pg_max =>Array{<:Real}, - :pg_min =>Array{<:Real}, - :qg_max =>Array{<:Real}, - :qg_min =>Array{<:Real}, -) - -REQUIRED_FIELDS[:voltage_source] = [:id, :status, :bus, :connections, :vm, :va] - -CHECKS[:voltage_source] = function check_voltage_source(data, vs) - id = vs["id"] - _check_connectivity(data, vs; context="voltage source $id") - N = length(vs["connections"]) - _check_has_size(vs, ["vm", "va", "pg_max", "pg_min", "qg_max", "qg_min"], N, context="voltage source $id") - -end - - -function create_voltage_source(; kwargs...) - vs = Dict{String,Any}() - - add_kwarg!(vs, kwargs, :status, 1) - add_kwarg!(vs, kwargs, :connections, collect(1:3)) - - _add_unused_kwargs!(vs, kwargs) - - return vs -end - - -# create add_comp! methods -for comp in keys(DTYPES) - eval(Meta.parse("add_$(comp)!(data_model; kwargs...) = add!(data_model, \"$comp\", create_$comp(; kwargs...))")) -end diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl deleted file mode 100644 index 85fdbb0fd..000000000 --- a/src/io/data_model_test.jl +++ /dev/null @@ -1,161 +0,0 @@ -using PowerModelsDistribution -import LinearAlgebra - -function make_test_data_model() - - data_model = create_data_model() - - add_linecode!(data_model, id="6_conds", rs=ones(6, 6), xs=ones(6, 6)) - add_linecode!(data_model, id="4_conds", rs=ones(4, 4), xs=ones(4, 4)) - add_linecode!(data_model, id="3_conds", rs=ones(3, 3), xs=ones(3, 3)) - add_linecode!(data_model, id="2_conds", rs=ones(2, 2), xs=ones(2, 2)) - - # 3 phase + 3 neutral conductors - add_line!(data_model, id="1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6)) - add_line!(data_model, id="2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4]) - # 3 phase + 1 neutral conductors - add_line!(data_model, id="3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2) - # 3 phase conductors - add_line!(data_model, id="4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) - # 2 phase + 1 neutral conductors - add_line!(data_model, id="5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4]) - # 1 phase + 1 neutral conductors - add_line!(data_model, id="6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4]) - # 2 phase conductors - add_line!(data_model, id="7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) - for i in 8:1000 - add_line!(data_model, id="$i", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) - end - - add_bus!(data_model, id="1", terminals=collect(1:4)) - add_bus!(data_model, id="2", terminals=collect(1:6)) - add_bus!(data_model, id="3", terminals=collect(1:4)) - add_bus!(data_model, id="4") - add_bus!(data_model, id="5", terminals=collect(1:4)) - add_bus!(data_model, id="6", terminals=[1,3,4]) - add_bus!(data_model, id="7", terminals=[2,4]) - add_bus!(data_model, id="8", terminals=[1,2]) - add_bus!(data_model, id="9", terminals=[1,2,3,4]) - add_bus!(data_model, id="10", terminals=[1,2,3]) - - # - add_load!(data_model, id="1", bus="7", connections=[2,4], pd=[1.0], qd=[1.0]) - add_load!(data_model, id="2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)]) - add_load!(data_model, id="3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230]) - add_load!(data_model, id="4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5]) - add_load!(data_model, id="5", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3)) - add_load!(data_model, id="6", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3)) - add_load!(data_model, id="7", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3)) - add_load!(data_model, id="8", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]) - add_load!(data_model, id="9", bus="5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3)) - - add_generator!(data_model, id="1", bus="1", configuration="wye") - - add_transformer_nw!(data_model, id="1", bus=["5", "9", "10"], connections=[[1,2,3], [1,2,3,4], [1,2,3]], - vnom=[0.230, 0.230, 0.230], snom=[0.230, 0.230, 0.230], - configuration=["delta", "wye", "delta"], - xsc=[0.0, 0.0, 0.0], - rs=[0.0, 0.0, 1.0], - noloadloss=0.05, - imag=0.05, - ) - - add_capacitor!(data_model, id="cap_3ph", bus="3", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3]) - add_capacitor!(data_model, id="cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3]) - add_capacitor!(data_model, id="cap_2ph_yg", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-grounded") - add_capacitor!(data_model, id="cap_2ph_yfl", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-floating") - add_capacitor!(data_model, id="cap_2ph_y", bus="5", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4]) - - return data_model -end - - -function make_3wire_data_model() - - data_model = create_data_model() - - add_linecode!(data_model, id="3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3))) - - add_voltage_source!(data_model, id="source", bus="sourcebus", connections=collect(1:3), - vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], - pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), - rs=ones(3,3)/10 - ) - - # 3 phase conductors - add_line!(data_model, id=:test, f_bus="sourcebus", t_bus="tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) - - add_bus!(data_model, id="sourcebus", terminals=collect(1:3)) - add_bus!(data_model, id="tr_prim", terminals=collect(1:4)) - add_bus!(data_model, id="tr_sec", terminals=collect(1:4)) - #add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) - - # add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], - # [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], - # configuration=["delta", "wye", "delta"], - # xsc=[0.0, 0.0, 0.0], - # rs=[0.0, 0.0, 0.0], - # loadloss=0.00, - # imag=0.00, - # )) - - add_transformer_nw!(data_model, id="1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], - vnom=[0.230, 0.230], snom=[0.230, 0.230], - configuration=["wye", "wye"], - xsc=[0.0], - rs=[0.0, 0.0], - noloadloss=0.00, - imag=0.00, - ) - - - - # - add_load!(data_model, id="1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0]) - - # add!(data_model, "generator", create_generator("1", "source", - # connections=[1, 2, 3, 4], - # pg_min=fill(-100, 3), - # pg_max=fill( 100, 3), - # qg_min=fill(-100, 3), - # qg_max=fill( 100, 3), - # )) - - #add!(data_model, "capacitor", create_capacitor(1, "tr_sec", 0.230*sqrt(3), qd_ref=[1.0, 1.0, 1.0]*1E-3, connections=[1,2,3], configuration="delta")) - - return data_model -end - - -data_model = make_3wire_data_model() -check_data_model(data_model) -data_model -# -data_model_map!(data_model) -#bsh = data_model["shunt"]["_virtual_1"]["b_sh"] -## -data_model_make_pu!(data_model, vbases=Dict("sourcebus"=>0.230)) -# -data_model_index!(data_model) -data_model_make_compatible_v8!(data_model) -## -import PowerModelsDistribution -PMD = PowerModelsDistribution -import PowerModels -PMs = PowerModels -import InfrastructureModels -IM = InfrastructureModels - -import JuMP, Ipopt - -ipopt_solver = JuMP.with_optimizer(Ipopt.Optimizer) -pm = PMs.instantiate_model(data_model, PMs.IVRPowerModel, PMD.build_mc_opf_iv, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) -sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) - -solution_unmake_pu!(sol["solution"], data_model) -solution_identify!(sol["solution"], data_model) -solution_unmap!(sol["solution"], data_model) -#vm = sol["solution"]["bus"]["tr_sec"]["vm"] -#va = sol["solution"]["bus"]["tr_sec"]["va"] -#v = vm.*exp.(im*va/180*pi) -sol["solution"] diff --git a/src/io/data_model_test_3w.jl b/src/io/data_model_test_3w.jl new file mode 100644 index 000000000..7ec20ef04 --- /dev/null +++ b/src/io/data_model_test_3w.jl @@ -0,0 +1,84 @@ +BASE_DIR = "/Users/sclaeys/code/PowerModelsDistribution.jl/src/io" +include("$BASE_DIR/data_model_util.jl") +include("$BASE_DIR/data_model_components.jl") +include("$BASE_DIR/../core/data_model_mapping.jl") +include("$BASE_DIR/../core/data_model_pu.jl") + +## BUILD DATA MODEL + +data_model = create_data_model() + +add!(data_model, "linecode", create_linecode(id="3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3)))) + +add!(data_model, "voltage_source", create_voltage_source(id="source", bus="sourcebus", vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], + #pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), +)) + +add!(data_model, "line", create_line(id=:test, f_bus="sourcebus", t_bus="tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3))) + +add!(data_model, "bus", create_bus(id="sourcebus", terminals=collect(1:3))) +add!(data_model, "bus", create_bus(id="tr_prim", terminals=collect(1:4))) +add!(data_model, "bus", create_bus(id="tr_sec", terminals=collect(1:4))) +#add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) + +# add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], +# [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], +# configuration=["delta", "wye", "delta"], +# xsc=[0.0, 0.0, 0.0], +# rs=[0.0, 0.0, 0.0], +# loadloss=0.00, +# imag=0.00, +# )) + +add!(data_model, "transformer_nw", create_transformer_nw(id="1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], + vnom=[0.230, 100], snom=[0.230, 0.230], + configuration=["wye", "wye"], + xsc=[0.0], + rs=[0.0, 0.0], + noloadloss=0.00, + imag=0.00, +)) + +add!(data_model, "load", create_load(id="1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0])) + +# add!(data_model, "generator", create_generator("1", "source", +# connections=[1, 2, 3, 4], +# pg_min=fill(-100, 3), +# pg_max=fill( 100, 3), +# qg_min=fill(-100, 3), +# qg_max=fill( 100, 3), +# )) + +#add!(data_model, "capacitor", create_capacitor(1, "tr_sec", 0.230*sqrt(3), qd_ref=[1.0, 1.0, 1.0]*1E-3, connections=[1,2,3], configuration="delta")) + +check_data_model(data_model) +# PREP THE MODEL FOR OPTIMIZATION + +data_model_map!(data_model) +#bsh = data_model["shunt"]["_virtual_1"]["b_sh"] +# +data_model_make_pu!(data_model, vbases=Dict("sourcebus"=>0.230)) +# +data_model_index!(data_model) +data_model_make_compatible_v8!(data_model) + +# OPTIMIZE THE MODEL + +import PowerModelsDistribution +PMD = PowerModelsDistribution +import PowerModels +PMs = PowerModels +import InfrastructureModels +IM = InfrastructureModels + +import JuMP, Ipopt + +ipopt_solver = JuMP.with_optimizer(Ipopt.Optimizer) +pm = PMs.instantiate_model(data_model, PMs.ACPPowerModel, PMD.build_mc_opf, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) +sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + +solution_unmake_pu!(sol["solution"], data_model) +solution_identify!(sol["solution"], data_model) +solution_unmap!(sol["solution"], data_model) + +sol["solution"] diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl deleted file mode 100644 index 7b88765d0..000000000 --- a/src/io/data_model_util.jl +++ /dev/null @@ -1,164 +0,0 @@ - -#import PowerModelsDistribution -#get = PowerModelsDistribution.get - -function scale(dict, key, scale) - if haskey(dict, key) - dict[key] *= scale - end -end - - -function add_virtual!(data_model, comp_type, comp) - if !haskey(data_model, comp_type) - data_model[comp_type] = Dict{Any, Any}() - end - comp_dict = data_model[comp_type] - virtual_ids = [parse(Int, x[1]) for x in [match(r"_virtual_([1-9]{1}[0-9]*)", id) for id in keys(comp_dict) if isa(id, AbstractString)] if x!=nothing] - if isempty(virtual_ids) - id = "_virtual_1" - else - id = "_virtual_$(maximum(virtual_ids)+1)" - end - comp["id"] = id - comp_dict[id] = comp - return comp -end - -add_virtual_get_id!(data_model, comp_type, comp) = add_virtual!(data_model, comp_type, comp)["id"] - -function delete_component!(data_model, comp_type, comp::Dict) - delete!(data_model[comp_type], comp["id"]) - if isempty(data_model[comp_type]) - delete!(data_model, comp_type) - end -end - -function delete_component!(data_model, comp_type, id::Any) - delete!(data_model[comp_type], id) - if isempty(data_model[comp_type]) - delete!(data_model, comp_type) - end -end - -function add_mappings!(data_model::Dict{String, Any}, mapping_type::String, mappings::Vector) - if !haskey(data_model, "mappings") - data_model["mappings"] = [] - end - - append!(data_model["mappings"], [(mapping_type, mapping) for mapping in mappings]) -end - - -function data_model_index!(data_model; components=["line", "shunt", "generator", "load", "transformer_2wa"]) - bus_id2ind = Dict() - - for (i, id) in enumerate(keys(data_model["bus"])) - data_model["bus"][id]["index"] = i - bus_id2ind[id] = i - end - data_model["bus"] = Dict{String, Any}(string(bus_id2ind[id])=>bus for (id, bus) in data_model["bus"]) - - for comp_type in components - comp_dict = Dict{String, Any}() - for (i,(id,comp)) in enumerate(data_model[comp_type]) - @assert(!haskey(comp, "index"), "$comp_type $id: component already has an index.") - comp["index"] = i - comp["id"] = id - comp_dict["$i"] = comp - for bus_key in ["f_bus", "t_bus", "bus"] - if haskey(comp, bus_key) - comp[bus_key] = bus_id2ind[comp[bus_key]] - end - end - end - data_model[comp_type] = comp_dict - end - return data_model -end - - -function solution_identify!(solution, data_model; id_prop="id") - for comp_type in keys(solution) - if isa(solution[comp_type], Dict) - comp_dict = Dict{Any, Any}() - for (ind, comp) in solution[comp_type] - id = data_model[comp_type][ind][id_prop] - comp_dict[id] = comp - end - solution[comp_type] = comp_dict - end - end - - return solution -end - -function add_solution!(solution, comp_type, id, data) - if !haskey(solution, comp_type) - solution[comp_type] = Dict() - end - - if !haskey(solution[comp_type], id) - solution[comp_type][id] = Dict{String, Any}() - end - - for (key, prop) in data - solution[comp_type][id][key] = prop - end -end - - -function delete_solution!(solution, comp_type, id, props) - if haskey(solution, comp_type) - if haskey(solution[comp_type], id) - for prop in props - delete!(solution[comp_type][id], prop) - end - end - end -end - - -function delete_solution!(solution, comp_type, id) - if haskey(solution, comp_type) - if haskey(solution[comp_type], id) - delete!(solution[comp_type], id) - end - end -end - -## -function _get_new_ground(terminals) - if isa(terminals, Vector{Int}) - return maximum(terminals)+1 - else - nrs = [parse(Int, x[1]) for x in [match(r"n([1-9]{1}[0-9]*)", string(t)) for t in terminals] if x!=nothing] - new = isempty(nrs) ? 1 : maximum(nrs)+1 - if isa(terminals, Vector{Symbol}) - return Symbol("g$new") - else - return "g$new" - end - end -end - - -function _get_ground!(bus) - # find perfect groundings (true ground) - grounded_perfect = [] - for i in 1:length(bus["grounded"]) - if bus["rg"][i]==0 && bus["xg"][i]==0 - push!(grounded_perfect, bus["grounded"][i]) - end - end - - if !isempty(grounded_perfect) - return grounded_perfect[1] - else - g = _get_new_ground(bus["terminals"]) - push!(bus["terminals"], g) - push!(bus["rg"], 0.0) - push!(bus["xg"], 0.0) - return g - end -end diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl deleted file mode 100644 index 564f8b9e2..000000000 --- a/src/io/opendss_dm.jl +++ /dev/null @@ -1,1231 +0,0 @@ -# OpenDSS parser -import LinearAlgebra: isdiag, diag, pinv - - -"Structure representing OpenDSS `dss_source_id` giving the type of the component `dss_type`, its name `dss_name`, and the active phases `active_phases`" -struct DSSSourceId - dss_type::AbstractString - dss_name::AbstractString - active_phases::Set{Int} -end - - -"Parses a component's OpenDSS source information into the `dss_source_id` struct" -function _parse_dss_source_id(component::Dict)::DSSSourceId - dss_type, dss_name = split(component["source_id"], '.') - return DSSSourceId(dss_type, dss_name, Set(component["active_phases"])) -end - - -"returns the linecode with name `id`" -function _get_linecode(dss_data::Dict, id::AbstractString) - if haskey(dss_data, "linecode") - for item in dss_data["linecode"] - if item["name"] == id - return item - end - end - end - return Dict{String,Any}() -end - - -""" - _discover_buses(dss_data) - -Discovers all of the buses (not separately defined in OpenDSS), from "lines". -""" -function _discover_buses(dss_data::Dict)::Array - bus_names = [] - buses = [] - for compType in ["line", "transformer", "reactor"] - if haskey(dss_data, compType) - compList = dss_data[compType] - for compObj in compList - if compType == "transformer" - compObj = _create_transformer(compObj["name"]; _to_sym_keys(compObj)...) - for bus in compObj["buses"] - name, nodes = _parse_busname(bus) - if !(name in bus_names) - push!(bus_names, name) - push!(buses, (name, nodes)) - end - end - elseif haskey(compObj, "bus2") - for key in ["bus1", "bus2"] - name, nodes = _parse_busname(compObj[key]) - if !(name in bus_names) - push!(bus_names, name) - push!(buses, (name, nodes)) - end - end - end - end - end - end - if length(buses) == 0 - Memento.error(_LOGGER, "dss_data has no lines!") - else - return buses - end -end - - -""" - _dss2pmd_bus!(pmd_data, dss_data) - -Adds PowerModels-style buses to `pmd_data` from `dss_data`. -""" -function _dss2pmd_bus_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1) - - buses = _discover_buses(dss_data) - for (n, (bus, nodes)) in enumerate(buses) - - @assert(!(length(bus)>=8 && bus[1:8]=="_virtual"), "Bus $bus: identifiers should not start with _virtual.") - - add_bus!(pmd_data, id=bus, status=1, bus_type=1) - end - - # create virtual sourcebus - circuit = _create_vsource(get(dss_data["circuit"][1], "bus1", "sourcebus"), dss_data["circuit"][1]["name"]; _to_sym_keys(dss_data["circuit"][1])...) - - nodes = Array{Bool}([1 1 1 0]) - ph1_ang = circuit["angle"] - vm_pu = circuit["pu"] - vmi = circuit["pu"] - circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) - vma = circuit["pu"] + circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) - - phases = circuit["phases"] - vnom = pmd_data["settings"]["set_vbase_val"] - - vm = fill(vm_pu, 3)*vnom - va = _wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases]) - - add_voltage_source!(pmd_data, id="source", bus="sourcebus", connections=collect(1:phases), - vm=vm, va=va, - rs=circuit["rmatrix"], xs=circuit["xmatrix"], - ) -end - - -""" - find_component(pmd_data, name, compType) - -Returns the component of `compType` with `name` from `data` of type -Dict{String,Array}. -""" -function find_component(data::Dict, name::AbstractString, compType::AbstractString)::Dict - for comp in values(data[compType]) - if comp["name"] == name - return comp - end - end - Memento.warn(_LOGGER, "Could not find $compType \"$name\"") - return Dict{String,Any}() -end - - -""" - find_bus(busname, pmd_data) - -Finds the index number of the bus in existing data from the given `busname`. -""" -function find_bus(busname::AbstractString, pmd_data::Dict) - bus = find_component(pmd_data, busname, "bus") - if haskey(bus, "bus_i") - return bus["bus_i"] - else - Memento.error(_LOGGER, "cannot find connected bus with id \"$busname\"") - end -end - - -""" - _dss2pmd_load!(pmd_data, dss_data, import_all) - -Adds PowerModels-style loads to `pmd_data` from `dss_data`. -""" -function _dss2pmd_load_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool, ground_terminal::Int=4) - - for load in get(dss_data, "load", []) - _apply_like!(load, dss_data, "load") - defaults = _apply_ordered_properties(_create_load(load["bus1"], load["name"]; _to_sym_keys(load)...), load) - - # parse the model - model = defaults["model"] - # some info on OpenDSS load models - ################################## - # Constant can still be scaled by other settings, fixed cannot - # Note that in the current feature set, fixed therefore equals constant - # 1: Constant P and Q, default - if model == 2 - # 2: Constant Z - elseif model == 3 - # 3: Constant P and quadratic Q - Memento.warn(_LOGGER, "$load_name: load model 3 not supported. Treating as model 1.") - model = 1 - elseif model == 4 - # 4: Exponential - Memento.warn(_LOGGER, "$load_name: load model 4 not supported. Treating as model 1.") - model = 1 - elseif model == 5 - # 5: Constant I - #warn(_LOGGER, "$name: load model 5 not supported. Treating as model 1.") - #model = 1 - elseif model == 6 - # 6: Constant P and fixed Q - Memento.warn(_LOGGER, "$load_name: load model 6 identical to model 1 in current feature set. Treating as model 1.") - model = 1 - elseif model == 7 - # 7: Constant P and quadratic Q (i.e., fixed reactance) - Memento.warn(_LOGGER, "$load_name: load model 7 not supported. Treating as model 1.") - model = 1 - elseif model == 8 - # 8: ZIP - Memento.warn(_LOGGER, "$load_name: load model 8 not supported. Treating as model 1.") - model = 1 - end - # save adjusted model type to dict, human-readable - model_int2str = Dict(1=>"constant_power", 2=>"constant_impedance", 5=>"constant_current") - model = model_int2str[model] - - nphases = defaults["phases"] - conf = defaults["conn"] - - - # connections - bus = _parse_busname(defaults["bus1"])[1] - - connections_default = conf=="wye" ? [collect(1:nphases)..., 0] : collect(1:nphases) - connections = _get_conductors_ordered_dm(defaults["bus1"], default=connections_default, check_length=false) - # if wye connected and neutral not specified, append ground - if conf=="wye" && length(connections)==nphases - connections = [connections..., 0] - end - - # now we can create the load; if you do not have the correct model, - # pd/qd fields will be populated by default (should not happen for constant current/impedance) - loadDict = add_load!(pmd_data, id=defaults["name"], model=model, connections=connections, bus=bus, configuration=conf) - - # if the ground is used directly, register load - if 0 in connections - if !haskey(pmd_data["bus"][bus], "awaiting_ground") - pmd_data["bus"][bus]["awaiting_ground"] = [] - end - push!(pmd_data["bus"][bus]["awaiting_ground"], loadDict) - end - - kv = defaults["kv"] - if conf=="wye" && nphases in [2, 3] - kv = kv/sqrt(3) - end - - if model=="constant_power" - loadDict["pd"] = fill(defaults["kw"]/nphases, nphases) - loadDict["qd"] = fill(defaults["kvar"]/nphases, nphases) - else - loadDict["pd_ref"] = fill(defaults["kw"]/nphases, nphases) - loadDict["qd_ref"] = fill(defaults["kvar"]/nphases, nphases) - loadDict["vnom"] = kv - end - - #loadDict["status"] = convert(Int, defaults["enabled"]) - - #loadDict["source_id"] = "load.$load_name" - - used = ["phases", "bus1", "name"] - _PMs._import_remaining!(loadDict, defaults, import_all; exclude=used) - end -end - - -""" - _dss2pmd_shunt!(pmd_data, dss_data, import_all) - -Adds PowerModels-style shunts to `pmd_data` from `dss_data`. -""" -function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - - for shunt in get(dss_data, "capacitor", []) - _apply_like!(shunt, dss_data, "capacitor") - defaults = _apply_ordered_properties(_create_capacitor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) - - nphases = defaults["phases"] - - dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") - conn = dyz_map[defaults["conn"]] - - bus_name = _parse_busname(defaults["bus1"])[1] - bus2_name = _parse_busname(defaults["bus2"])[1] - if bus_name!=bus2_name - Memento.error("Capacitor $(defaults["name"]): bus1 and bus2 should connect to the same bus.") - end - - f_terminals = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) - if conn=="wye" - t_terminals = _get_conductors_ordered_dm(defaults["bus2"], default=fill(0,nphases)) - else - # if delta connected, ignore bus2 and generate t_terminals such that - # it corresponds to a delta winding - t_terminals = [f_terminals[2:end]..., f_terminals[1]] - end - - - # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) - #TODO figure out for more than 3 phases - vnom_ln = defaults["kv"] - if defaults["phases"] in [2,3] - vnom_ln = vnom_ln/sqrt(3) - end - # 'kvar' is specified for all phases at once; we want the per-phase one, in MVar - qnom = (defaults["kvar"]/1E3)/nphases - b = fill(qnom/vnom_ln^2, nphases) - - # convert to a shunt matrix - terminals, B = calc_shunt(f_terminals, t_terminals, b) - - # if one terminal is ground (0), reduce shunt addmittance matrix - terminals, B = ground_shunt(terminals, B, 0) - - shuntDict = add_shunt!(pmd_data, id=defaults["name"], status=convert(Int, defaults["enabled"]), - bus=bus_name, connections=terminals, - g_sh=fill(0.0, size(B)...), b_sh=B - ) - - used = ["bus1", "phases", "name"] - _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) - - pmd_data["shunt"][shuntDict["id"]] = shuntDict - end - - #TODO revisit this in the future - # for shunt in get(dss_data, "reactor", []) - # if !haskey(shunt, "bus2") - # _apply_like!(shunt, dss_data, "reactor") - # defaults = _apply_ordered_properties(_create_reactor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) - # - # shuntDict = Dict{String,Any}() - # - # nconductors = pmd_data["conductors"] - # name, nodes = _parse_busname(defaults["bus1"]) - # - # Zbase = (pmd_data["basekv"] / sqrt(3.0))^2 * nconductors / pmd_data["baseMVA"] # Use single-phase base impedance for each phase - # Gcap = Zbase * sum(defaults["kvar"]) / (nconductors * 1e3 * (pmd_data["basekv"] / sqrt(3.0))^2) - # - # shuntDict["shunt_bus"] = find_bus(name, pmd_data) - # shuntDict["name"] = defaults["name"] - # shuntDict["gs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) # TODO: - # shuntDict["bs"] = _PMs.MultiConductorVector(_parse_array(Gcap, nodes, nconductors)) - # shuntDict["status"] = convert(Int, defaults["enabled"]) - # shuntDict["index"] = length(pmd_data["shunt"]) + 1 - # - # shuntDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - # shuntDict["source_id"] = "reactor.$(defaults["name"])" - # - # used = ["bus1", "phases", "name"] - # _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) - # - # push!(pmd_data["shunt"], shuntDict) - # end - # end -end - - -""" -Given a vector and a list of elements to find, this method will return a list -of the positions of the elements in that vector. -""" -function get_inds(vec::Array{<:Any, 1}, els::Array{<:Any, 1}) - ret = Array{Int, 1}(undef, length(els)) - for (i,f) in enumerate(els) - for (j,l) in enumerate(vec) - if f==l - ret[i] = j - end - end - end - return ret -end - - -""" -Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the -conductors 't_cnds', this method will return a list of conductors 'cnd' and a -matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. -""" -function calc_shunt(f_cnds, t_cnds, y) - cnds = unique([f_cnds..., t_cnds...]) - e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) - Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) - return (cnds, Y) -end - - - -""" -Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this -method will calculate the reduced addmittance matrix if terminal 'ground' is -grounded. -""" -function ground_shunt(cnds, Y, ground) - if ground in cnds - cndsr = setdiff(cnds, ground) - cndsr_inds = get_inds(cnds, cndsr) - Yr = Y[cndsr_inds, cndsr_inds] - return (cndsr, Yr) - else - return cnds, Y - end -end - - -function rm_floating_cnd(cnds, Y, f) - P = setdiff(cnds, f) - f_inds = get_inds(cnds, [f]) - P_inds = get_inds(cnds, P) - Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] - return (P,Yrm) -end - - -""" - _dss2pmd_gen!(pmd_data, dss_data, import_all) - -Adds PowerModels-style generators to `pmd_data` from `dss_data`. -""" -function _dss2pmd_gen_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "gen") - pmd_data["gen"] = [] - end - - # # sourcebus generator (created by circuit) - # circuit = dss_data["circuit"][1] - # defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), "sourcegen"; _to_sym_keys(circuit)...) - # - # genDict = Dict{String,Any}() - # - # nconductors = pmd_data["conductors"] - # name, nodes = _parse_busname(defaults["bus1"]) - # - # genDict["gen_bus"] = find_bus("virtual_sourcebus", pmd_data) - # genDict["name"] = defaults["name"] - # genDict["gen_status"] = convert(Int, defaults["enabled"]) - # - # # TODO: populate with VSOURCE properties - # genDict["pg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) - # genDict["qg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) - # - # genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) - # genDict["qmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) - # - # genDict["pmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) - # genDict["pmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) - # - # genDict["model"] = 2 - # genDict["startup"] = 0.0 - # genDict["shutdown"] = 0.0 - # genDict["ncost"] = 3 - # genDict["cost"] = [0.0, 1.0, 0.0] - # - # genDict["index"] = length(pmd_data["gen"]) + 1 - # - # genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - # genDict["source_id"] = "vsource.$(defaults["name"])" - # - # used = ["name", "phases", "bus1"] - # _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) - # - # push!(pmd_data["gen"], genDict) - - - for gen in get(dss_data, "generator", []) - _apply_like!(gen, dss_data, "generator") - defaults = _apply_ordered_properties(_create_generator(gen["bus1"], gen["name"]; _to_sym_keys(gen)...), gen) - - genDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - genDict["gen_bus"] = find_bus(name, pmd_data) - genDict["name"] = defaults["name"] - genDict["gen_status"] = convert(Int, defaults["enabled"]) - genDict["pg"] = _PMs.MultiConductorVector(_parse_array(defaults["kw"] / (1e3 * nconductors), nodes, nconductors)) - genDict["qg"] = _PMs.MultiConductorVector(_parse_array(defaults["kvar"] / (1e3 * nconductors), nodes, nconductors)) - genDict["vg"] = _PMs.MultiConductorVector(_parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors)) - - genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(defaults["minkvar"] / (1e3 * nconductors), nodes, nconductors)) - genDict["qmax"] = _PMs.MultiConductorVector(_parse_array(defaults["maxkvar"] / (1e3 * nconductors), nodes, nconductors)) - - genDict["apf"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - - genDict["pmax"] = genDict["pg"] # Assumes generator is at rated power - genDict["pmin"] = 0.0 * genDict["pg"] # 0% of pmax - - genDict["pc1"] = genDict["pmax"] - genDict["pc2"] = genDict["pmin"] - genDict["qc1min"] = genDict["qmin"] - genDict["qc1max"] = genDict["qmax"] - genDict["qc2min"] = genDict["qmin"] - genDict["qc2max"] = genDict["qmax"] - - # For distributed generation ramp rates are not usually an issue - # and they are not supported in OpenDSS - genDict["ramp_agc"] = genDict["pmax"] - - genDict["ramp_q"] = _PMs.MultiConductorVector(_parse_array(max.(abs.(genDict["qmin"].values), abs.(genDict["qmax"].values)), nodes, nconductors)) - genDict["ramp_10"] = genDict["pmax"] - genDict["ramp_30"] = genDict["pmax"] - - genDict["control_model"] = defaults["model"] - - # if PV generator mode convert attached bus to PV bus - if genDict["control_model"] == 3 - pmd_data["bus"][genDict["gen_bus"]]["bus_type"] = 2 - end - - genDict["model"] = 2 - genDict["startup"] = 0.0 - genDict["shutdown"] = 0.0 - genDict["ncost"] = 3 - genDict["cost"] = [0.0, 1.0, 0.0] - - genDict["index"] = length(pmd_data["gen"]) + 1 - - genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - genDict["source_id"] = "generator.$(defaults["name"])" - - used = ["name", "phases", "bus1"] - _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) - - push!(pmd_data["gen"], genDict) - end - - for pv in get(dss_data, "pvsystem", []) - Memento.warn(_LOGGER, "Converting PVSystem \"$(pv["name"])\" into generator with limits determined by OpenDSS property 'kVA'") - - _apply_like!(pv, dss_data, "pvsystem") - defaults = _apply_ordered_properties(_create_pvsystem(pv["bus1"], pv["name"]; _to_sym_keys(pv)...), pv) - - pvDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - pvDict["name"] = defaults["name"] - pvDict["gen_bus"] = find_bus(name, pmd_data) - - pvDict["pg"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) - pvDict["qg"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) - pvDict["vg"] = _PMs.MultiConductorVector(_parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors)) - - pvDict["pmin"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - pvDict["pmax"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) - - pvDict["qmin"] = -_PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) - pvDict["qmax"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) - - pvDict["gen_status"] = convert(Int, defaults["enabled"]) - - pvDict["model"] = 2 - pvDict["startup"] = 0.0 - pvDict["shutdown"] = 0.0 - pvDict["ncost"] = 3 - pvDict["cost"] = [0.0, 1.0, 0.0] - - pvDict["index"] = length(pmd_data["gen"]) + 1 - - pvDict["active_phases"] = [nodes[n] > 0 ? 1 : 0 for n in 1:nconductors] - pvDict["source_id"] = "pvsystem.$(defaults["name"])" - - used = ["name", "phases", "bus1"] - _PMs._import_remaining!(pvDict, defaults, import_all; exclude=used) - - push!(pmd_data["gen"], pvDict) - end -end - - -""" - _dss2pmd_line!(pmd_data, dss_data, import_all) - -Adds PowerModels-style lines to `pmd_data` from `dss_data`. -""" -function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "branch") - pmd_data["line"] = Dict{String, Any}() - end - - #nconductors = pmd_data["conductors"] - - for line in get(dss_data, "line", []) - _apply_like!(line, dss_data, "line") - - if haskey(line, "linecode") - linecode = deepcopy(_get_linecode(dss_data, get(line, "linecode", ""))) - if haskey(linecode, "like") - linecode = merge(find_component(dss_data, linecode["like"], "linecode"), linecode) - end - - linecode["units"] = get(line, "units", "none") == "none" ? "none" : get(linecode, "units", "none") - linecode["circuit_basefreq"] = pmd_data["settings"]["basefreq"] - - linecode = _create_linecode(get(linecode, "name", ""); _to_sym_keys(linecode)...) - delete!(linecode, "name") - else - linecode = Dict{String,Any}() - end - - if haskey(line, "basefreq") && line["basefreq"] != pmd_data["settings"]["basefreq"] - Memento.warn(_LOGGER, "basefreq=$(line["basefreq"]) on line $(line["name"]) does not match circuit basefreq=$(pmd_data["settings"]["basefreq"])") - line["circuit_basefreq"] = pmd_data["settings"]["basefreq"] - end - - defaults = _apply_ordered_properties(_create_line(line["bus1"], line["bus2"], line["name"]; _to_sym_keys(line)...), line; linecode=linecode) - - lineDict = Dict{String,Any}() - - lineDict["id"] = defaults["name"] - lineDict["f_bus"] = _parse_busname(defaults["bus1"])[1] - lineDict["t_bus"] = _parse_busname(defaults["bus2"])[1] - - #lineDict["length"] = defaults["length"] - - nphases = defaults["phases"] - lineDict["n_conductors"] = nphases - - rmatrix = defaults["rmatrix"] - xmatrix = defaults["xmatrix"] - cmatrix = defaults["cmatrix"] - - lineDict["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) - lineDict["t_connections"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) - - lineDict["rs"] = rmatrix * defaults["length"] - lineDict["xs"] = xmatrix * defaults["length"] - - lineDict["g_fr"] = fill(0.0, nphases, nphases) - lineDict["g_to"] = fill(0.0, nphases, nphases) - - lineDict["b_fr"] = (2.0 * pi * pmd_data["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 - lineDict["b_to"] = (2.0 * pi * pmd_data["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 - - #lineDict["c_rating_a"] = fill(defaults["normamps"], nphases) - #lineDict["c_rating_b"] = fill(defaults["emergamps"], nphases) - #lineDict["c_rating_c"] = fill(defaults["emergamps"], nphases) - - lineDict["status"] = convert(Int, defaults["enabled"]) - - #lineDict["source_id"] = "line.$(defaults["name"])" - - #used = ["name", "bus1", "bus2", "rmatrix", "xmatrix"] - #_PMs._import_remaining!(lineDict, defaults, import_all; exclude=used) - - pmd_data["line"][lineDict["id"]] = lineDict - end -end - - -""" - _dss2pmd_transformer!(pmd_data, dss_data, import_all) - -Adds ThreePhasePowerModels-style transformers to `pmd_data` from `dss_data`. -""" -function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "transformer_nw") - pmd_data["transformer_nw"] = Dict{String,Any}() - end - - for transformer in get(dss_data, "transformer", []) - _apply_like!(transformer, dss_data, "transformer") - defaults = _apply_ordered_properties(_create_transformer(transformer["name"]; _to_sym_keys(transformer)...), transformer) - - nphases = defaults["phases"] - nrw = defaults["windings"] - - dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") - confs = [dyz_map[x] for x in defaults["conns"]] - - # test if this transformer conforms with limitations - if nphases<3 && "delta" in confs - Memento.error("Transformers with delta windings should have at least 3 phases to be well-defined.") - end - if nrw>3 - # All of the code is compatible with any number of windings, - # except for the parsing of the loss model (the pair-wise reactance) - Memento.error(_LOGGER, "For now parsing of xscarray is not supported. At most 3 windings are allowed, not $nrw.") - end - - transDict = Dict{String, Any}() - transDict["id"] = defaults["name"] - transDict["bus"] = Array{String, 1}(undef, nrw) - transDict["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) - transDict["tm"] = Array{Array{Real, 1}, 1}(undef, nrw) - transDict["fixed"] = [[true for i in 1:nphases] for j in 1:nrw] - transDict["vnom"] = [defaults["kvs"][w] for w in 1:nrw] - transDict["snom"] = [defaults["kvas"][w] for w in 1:nrw] - transDict["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) - transDict["configuration"] = Array{String, 1}(undef, nrw) - transDict["polarity"] = Array{String, 1}(undef, nrw) - - for w in 1:nrw - transDict["bus"][w] = _parse_busname(defaults["buses"][w])[1] - - conn = dyz_map[defaults["conns"][w]] - transDict["configuration"][w] = conn - - terminals_default = conn=="wye" ? [1:nphases..., 0] : collect(1:nphases) - terminals_w = _get_conductors_ordered_dm(defaults["buses"][w], default=terminals_default) - transDict["connections"][w] = terminals_w - if 0 in terminals_w - bus = transDict["bus"][w] - if !haskey(pmd_data["bus"][bus], "awaiting_ground") - pmd_data["bus"][bus]["awaiting_ground"] = [] - end - push!(pmd_data["bus"][bus]["awaiting_ground"], transDict) - end - transDict["polarity"][w] = "forward" - transDict["tm"][w] = fill(defaults["taps"][w], nphases) - end - - #transDict["source_id"] = "transformer.$(defaults["name"])" - if !isempty(defaults["bank"]) - transDict["bank"] = defaults["bank"] - end - - # loss model (converted to SI units, referred to secondary) - transDict["rs"] = [defaults["%rs"][w]/100 for w in 1:nrw] - transDict["noloadloss"] = defaults["%noloadloss"]/100 - transDict["imag"] = defaults["%imag"]/100 - if nrw==2 - transDict["xsc"] = [defaults["xhl"]]/100 - elseif nrw==3 - transDict["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 - end - - add_virtual!(pmd_data, "transformer_nw", create_transformer_nw(; - Dict(Symbol.(keys(transDict)).=>values(transDict))... - )) - end -end - - -""" - _dss2pmd_reactor!(pmd_data, dss_data, import_all) - -Adds PowerModels-style branch components based on DSS reactors to `pmd_data` from `dss_data` -""" -function _dss2pmd_reactor_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "branch") - pmd_data["branch"] = [] - end - - if haskey(dss_data, "reactor") - Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating like line") - for reactor in dss_data["reactor"] - if haskey(reactor, "bus2") - _apply_like!(reactor, dss_data, "reactor") - defaults = _apply_ordered_properties(_create_reactor(reactor["bus1"], reactor["name"], reactor["bus2"]; _to_sym_keys(reactor)...), reactor) - - reactDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - - f_bus, nodes = _parse_busname(defaults["bus1"]) - t_bus = _parse_busname(defaults["bus2"])[1] - - reactDict["name"] = defaults["name"] - reactDict["f_bus"] = find_bus(f_bus, pmd_data) - reactDict["t_bus"] = find_bus(t_bus, pmd_data) - - reactDict["br_r"] = _PMs.MultiConductorMatrix(_parse_matrix(diagm(0 => fill(0.2, nconductors)), nodes, nconductors)) - reactDict["br_x"] = _PMs.MultiConductorMatrix(_parse_matrix(zeros(nconductors, nconductors), nodes, nconductors)) - - reactDict["g_fr"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - reactDict["g_to"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - reactDict["b_fr"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - reactDict["b_to"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - - for key in ["g_fr", "g_to", "b_fr", "b_to"] - reactDict[key] = _PMs.MultiConductorMatrix(LinearAlgebra.diagm(0=>reactDict[key].values)) - end - - reactDict["c_rating_a"] = _PMs.MultiConductorVector(_parse_array(defaults["normamps"], nodes, nconductors)) - reactDict["c_rating_b"] = _PMs.MultiConductorVector(_parse_array(defaults["emergamps"], nodes, nconductors)) - reactDict["c_rating_c"] = _PMs.MultiConductorVector(_parse_array(defaults["emergamps"], nodes, nconductors)) - - reactDict["tap"] = _PMs.MultiConductorVector(_parse_array(1.0, nodes, nconductors, NaN)) - reactDict["shift"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - - reactDict["br_status"] = convert(Int, defaults["enabled"]) - - reactDict["angmin"] = _PMs.MultiConductorVector(_parse_array(-60.0, nodes, nconductors, -60.0)) - reactDict["angmax"] = _PMs.MultiConductorVector(_parse_array( 60.0, nodes, nconductors, 60.0)) - - reactDict["transformer"] = true - - reactDict["index"] = length(pmd_data["branch"]) + 1 - - nodes = .+([_parse_busname(defaults[n])[2] for n in ["bus1", "bus2"]]...) - reactDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - reactDict["source_id"] = "reactor.$(defaults["name"])" - - used = [] - _PMs._import_remaining!(reactDict, defaults, import_all; exclude=used) - - push!(pmd_data["branch"], reactDict) - end - end - end -end - - -""" - _dss2pmd_pvsystem!(pmd_data, dss_data) - -Adds PowerModels-style pvsystems to `pmd_data` from `dss_data`. -""" -function _dss2pmd_pvsystem_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "pvsystem") - pmd_data["pvsystem"] = [] - end - - for pvsystem in get(dss_data, "pvsystem", []) - _apply_like!(pvsystem, dss_data, "pvsystem") - defaults = _apply_ordered_properties(_create_pvsystem(pvsystem["bus1"], pvsystem["name"]; _to_sym_keys(pvsystem)...), pvsystem) - - pvsystemDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - pvsystemDict["name"] = defaults["name"] - pvsystemDict["pv_bus"] = find_bus(name, pmd_data) - pvsystemDict["p"] = _PMs.MultiConductorVector(_parse_array(defaults["kw"] / 1e3, nodes, nconductors)) - pvsystemDict["q"] = _PMs.MultiConductorVector(_parse_array(defaults["kvar"] / 1e3, nodes, nconductors)) - pvsystemDict["status"] = convert(Int, defaults["enabled"]) - - pvsystemDict["index"] = length(pmd_data["pvsystem"]) + 1 - - pvsystemDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - pvsystemDict["source_id"] = "pvsystem.$(defaults["name"])" - - used = ["phases", "bus1", "name"] - _PMs._import_remaining!(pvsystemDict, defaults, import_all; exclude=used) - - push!(pmd_data["pvsystem"], pvsystemDict) - end -end - - -""" - _dss2pmd_storage!(pmd_data, dss_data, import_all) - -Adds PowerModels-style storage to `pmd_data` from `dss_data` -""" -function _dss2pmd_storage_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "storage") - pmd_data["storage"] = [] - end - - for storage in get(dss_data, "storage", []) - _apply_like!(storage, dss_data, "storage") - defaults = _apply_ordered_properties(_create_storage(storage["bus1"], storage["name"]; _to_sym_keys(storage)...), storage) - - storageDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - storageDict["name"] = defaults["name"] - storageDict["storage_bus"] = find_bus(name, pmd_data) - storageDict["energy"] = defaults["kwhstored"] / 1e3 - storageDict["energy_rating"] = defaults["kwhrated"] / 1e3 - storageDict["charge_rating"] = defaults["%charge"] * defaults["kwrated"] / 1e3 / 100.0 - storageDict["discharge_rating"] = defaults["%discharge"] * defaults["kwrated"] / 1e3 / 100.0 - storageDict["charge_efficiency"] = defaults["%effcharge"] / 100.0 - storageDict["discharge_efficiency"] = defaults["%effdischarge"] / 100.0 - storageDict["thermal_rating"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / 1e3 / nconductors, nodes, nconductors)) - storageDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-defaults["kvar"] / 1e3 / nconductors, nodes, nconductors)) - storageDict["qmax"] = _PMs.MultiConductorVector(_parse_array( defaults["kvar"] / 1e3 / nconductors, nodes, nconductors)) - storageDict["r"] = _PMs.MultiConductorVector(_parse_array(defaults["%r"] / 100.0, nodes, nconductors)) - storageDict["x"] = _PMs.MultiConductorVector(_parse_array(defaults["%x"] / 100.0, nodes, nconductors)) - storageDict["p_loss"] = defaults["%idlingkw"] * defaults["kwrated"] / 1e3 - storageDict["q_loss"] = defaults["%idlingkvar"] * defaults["kvar"] / 1e3 - - storageDict["status"] = convert(Int, defaults["enabled"]) - - storageDict["ps"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - storageDict["qs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - - storageDict["index"] = length(pmd_data["storage"]) + 1 - - storageDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - storageDict["source_id"] = "storage.$(defaults["name"])" - - used = ["phases", "bus1", "name"] - _PMs._import_remaining!(storageDict, defaults, import_all; exclude=used) - - push!(pmd_data["storage"], storageDict) - end -end - - -"This function appends a component to a component dictionary of a pmd data model" -function _push_dict_ret_key!(dict::Dict{String, Any}, v::Dict{String, Any}; assume_no_gaps=false) - if isempty(dict) - k = 1 - elseif assume_no_gaps - k = length(keys(dict))+1 - else - k = maximum([parse(Int, x) for x in keys(dict)])+1 - end - - dict[string(k)] = v - v["index"] = k - return k -end - - -""" - _where_is_comp(data, comp_id) - -Finds existing component of id `comp_id` in array of `data` and returns index. -Assumes all components in `data` are unique. -""" -function _where_is_comp(data::Array, comp_id::AbstractString)::Int - for (i, e) in enumerate(data) - if e["name"] == comp_id - return i - end - end - return 0 -end - - -""" - _correct_duplicate_components!(dss_data) - -Finds duplicate components in `dss_data` and merges up, meaning that older -data (lower indices) is always overwritten by newer data (higher indices). -""" -function _correct_duplicate_components!(dss_data::Dict) - out = Dict{String,Array}() - for (k, v) in dss_data - if !(k in ["options"]) - out[k] = [] - for comp in v - if isa(comp, Dict) - idx = _where_is_comp(out[k], comp["name"]) - if idx > 0 - merge!(out[k][idx], comp) - else - push!(out[k], comp) - end - end - end - end - end - merge!(dss_data, out) -end - - -"Creates a virtual branch between the `virtual_sourcebus` and `sourcebus` with the impedance given by `circuit`" -function _create_sourcebus_vbranch_dm!(pmd_data::Dict, circuit::Dict) - #TODO convert to pu - rs = circuit["rmatrix"] - xs = circuit["xmatrix"] - - N = size(rs)[1] - - add_line!(pmd_data, id="_virtual_source_imp", - f_bus="_virtual_sourcebus", t_bus="sourcebus", - f_connections=collect(1:N), t_connections=collect(1:N), - rs=rs, xs=xs - ) - #vbranch = _create_vbranch!(pmd_data, sourcebus, vsourcebus; name="sourcebus_vbranch", br_r=br_r, br_x=br_x) -end - - -"Combines transformers with 'bank' keyword into a single transformer" -function _bank_transformers!(pmd_data::Dict) - transformer_names = Dict(trans["name"] => n for (n, trans) in get(pmd_data, "transformer_comp", Dict())) - bankable_transformers = [trans for trans in values(get(pmd_data, "transformer_comp", Dict())) if haskey(trans, "bank")] - banked_transformers = Dict() - for transformer in bankable_transformers - bank = transformer["bank"] - - if !(bank in keys(banked_transformers)) - n = length(pmd_data["transformer_comp"])+length(banked_transformers)+1 - - banked_transformers[bank] = deepcopy(transformer) - banked_transformers[bank]["name"] = deepcopy(transformer["bank"]) - banked_transformers[bank]["source_id"] = "transformer.$(transformer["bank"])" - banked_transformers[bank]["index"] = n - # set impedances / admittances to zero; only the specified phases should be non-zero - for key in ["rs", "xs", "bsh", "gsh"] - inds = key=="xs" ? keys(banked_transformers[bank][key]) : 1:length(banked_transformers[bank][key]) - for w in inds - banked_transformers[bank][key][w] *= 0 - end - end - delete!(banked_transformers[bank], "bank") - end - - banked_transformer = banked_transformers[bank] - for phase in transformer["active_phases"] - push!(banked_transformer["active_phases"], phase) - for (k, v) in banked_transformer - if isa(v, _PMs.MultiConductorVector) - banked_transformer[k][phase] = deepcopy(transformer[k][phase]) - elseif isa(v, _PMs.MultiConductorMatrix) - banked_transformer[k][phase, :] .= deepcopy(transformer[k][phase, :]) - elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorVector - # most properties are arrays (indexed over the windings) - for w in 1:length(v) - banked_transformer[k][w][phase] = deepcopy(transformer[k][w][phase]) - end - elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorMatrix - # most properties are arrays (indexed over the windings) - for w in 1:length(v) - banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) - end - elseif k=="xs" - # xs is a Dictionary indexed over pairs of windings - for w in keys(v) - banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) - end - end - end - end - end - - for transformer in bankable_transformers - delete!(pmd_data["transformer_comp"], transformer_names[transformer["name"]]) - end - - for transformer in values(banked_transformers) - pmd_data["transformer_comp"]["$(transformer["index"])"] = deepcopy(transformer) - end -end - - -""" - parse_options(options) - -Parses options defined with the `set` command in OpenDSS. -""" -function parse_options(options) - out = Dict{String,Any}() - if haskey(options, "voltagebases") - out["voltagebases"] = _parse_array(Float64, options["voltagebases"]) - end - - if !haskey(options, "defaultbasefreq") - Memento.warn(_LOGGER, "defaultbasefreq is not defined, default for circuit set to 60 Hz") - out["defaultbasefreq"] = 60.0 - else - out["defaultbasefreq"] = parse(Float64, options["defaultbasefreq"]) - end - - return out -end - - -"Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" -function parse_opendss_dm(dss_data::Dict; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict - pmd_data = create_data_model() - - _correct_duplicate_components!(dss_data) - - parse_dss_with_dtypes!(dss_data, ["line", "linecode", "load", "generator", "capacitor", - "reactor", "circuit", "transformer", "pvsystem", - "storage"]) - - if haskey(dss_data, "options") - condensed_opts = [Dict{String,Any}()] - for opt in dss_data["options"] - merge!(condensed_opts[1], opt) - end - dss_data["options"] = condensed_opts - end - - merge!(pmd_data, parse_options(get(dss_data, "options", [Dict{String,Any}()])[1])) - - #pmd_data["per_unit"] = false - #pmd_data["source_type"] = "dss" - #pmd_data["source_version"] = string(VersionNumber("0")) - - if haskey(dss_data, "circuit") - circuit = dss_data["circuit"][1] - defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), circuit["name"]; _to_sym_keys(circuit)...) - - #pmd_data["name"] = defaults["name"] - pmd_data["settings"]["v_var_scalar"] = 1E3 - pmd_data["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))*1E3/pmd_data["settings"]["v_var_scalar"] - pmd_data["settings"]["set_vbase_bus"] = "sourcebus" - - pmd_data["settings"]["set_sbase_val"] = defaults["basemva"]*1E6/pmd_data["settings"]["v_var_scalar"] - pmd_data["settings"]["basefreq"] = pop!(pmd_data, "defaultbasefreq") - #pmd_data["pu"] = defaults["pu"] - #pmd_data["conductors"] = defaults["phases"] - #pmd_data["sourcebus"] = defaults["bus1"] - else - Memento.error(_LOGGER, "Circuit not defined, not a valid circuit!") - end - - _dss2pmd_bus_dm!(pmd_data, dss_data, import_all, vmin, vmax) - _dss2pmd_line_dm!(pmd_data, dss_data, import_all) - _dss2pmd_transformer_dm!(pmd_data, dss_data, import_all) - - - _dss2pmd_load_dm!(pmd_data, dss_data, import_all) - _dss2pmd_shunt_dm!(pmd_data, dss_data, import_all) - - - #_dss2pmd_reactor!(pmd_data, dss_data, import_all) - #_dss2pmd_gen!(pmd_data, dss_data, import_all) - #_dss2pmd_pvsystem!(pmd_data, dss_data, import_all) - #_dss2pmd_storage!(pmd_data, dss_data, import_all) - - #pmd_data["dcline"] = [] - #pmd_data["switch"] = [] - - #InfrastructureModels.arrays_to_dicts!(pmd_data) - - if bank_transformers - _bank_transformers!(pmd_data) - end - - #_create_sourcebus_vbranch_dm!(pmd_data, defaults) - - _discover_terminals!(pmd_data) - - #_adjust_sourcegen_bounds!(pmd_data) - - #pmd_data["files"] = dss_data["filename"] - - return pmd_data -end - -function _discover_terminals!(pmd_data) - terminals = Dict{String, Set{Int}}([(bus["id"], Set{Int}()) for (_,bus) in pmd_data["bus"]]) - - for (_,line) in pmd_data["line"] - push!(terminals[line["f_bus"]], line["f_connections"]...) - push!(terminals[line["t_bus"]], line["t_connections"]...) - end - - if haskey(pmd_data, "transformer_nw3ph_lossy") - for (_,tr) in pmd_data["transformer_nw3ph_lossy"] - for w in tr["n_windings"] - push!(terminals[buses[w]], terminals[w]...) - end - end - end - - for (id, bus) in pmd_data["bus"] - pmd_data["bus"][id]["terminals"] = sort(collect(terminals[id])) - end - - # identify neutrals and propagate along cables - bus_neutral = _find_neutrals(pmd_data) - - for (id,bus) in pmd_data["bus"] - if haskey(bus, "awaiting_ground") || haskey(bus_neutral, id) - # this bus will need a neutral - if haskey(bus_neutral, id) - neutral = bus_neutral[id] - else - neutral = maximum(bus["terminals"])+1 - push!(bus["terminals"], neutral) - end - bus["neutral"] = neutral - if haskey(bus, "awaiting_ground") - bus["grounded"] = [neutral] - bus["rg"] = [0.0] - bus["xg"] = [0.0] - for comp in bus["awaiting_ground"] - if eltype(comp["connections"])<:Array - for w in 1:length(comp["connections"]) - if comp["bus"][w]==id - comp["connections"][w] .+= (comp["connections"][w].==0)*neutral - end - end - else - comp["connections"] .+= (comp["connections"].==0)*neutral - end - end - delete!(bus, "awaiting_ground") - end - end - phases = haskey(bus, "neutral") ? setdiff(bus["terminals"], bus["neutral"]) : bus["terminals"] - bus["phases"] = phases - end -end - -function _find_neutrals(pmd_data) - vertices = [(id, t) for (id, bus) in pmd_data["bus"] for t in bus["terminals"]] - neutrals = [] - edges = Set([((line["f_bus"], line["f_connections"][c]),(line["t_bus"], line["t_connections"][c])) for (id, line) in pmd_data["line"] for c in 1:length(line["f_connections"])]) - - bus_neutrals = [(id,bus["neutral"]) for (id,bus) in pmd_data["bus"] if haskey(bus, "neutral")] - trans_neutrals = [] - for (_, tr) in pmd_data["transformer_nw"] - for w in 1:length(tr["connections"]) - if tr["configuration"][w] == "wye" - @show tr - push!(trans_neutrals, (tr["bus"][w], tr["connections"][w][end])) - end - end - end - load_neutrals = [(load["bus"],load["connections"][end]) for (_,load) in pmd_data["load"] if load["configuration"]=="wye"] - neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) - neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) - stack = deepcopy(neutrals) - while !isempty(stack) - vertex = pop!(stack) - candidates_t = [((f,t), t) for (f,t) in edges if f==vertex] - candidates_f = [((f,t), f) for (f,t) in edges if t==vertex] - for (edge,next) in [candidates_t..., candidates_f...] - delete!(edges, edge) - push!(stack, next) - push!(neutrals, next) - end - end - bus_neutral = Dict{String, Int}() - for (bus,t) in neutrals - bus_neutral[bus] = t - end - return bus_neutral -end - - -"Parses a DSS file into a PowerModels usable format" -function parse_opendss_dm(io::IOStream; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict - dss_data = parse_dss(io) - - return parse_opendss_dm(dss_data; import_all=import_all) -end - - -""" - _get_conductors_ordered(busname; neutral=true) - -Returns an ordered list of defined conductors. If ground=false, will omit any `0` -""" -function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_length=true)::Array - parts = split(busname, '.'; limit=2) - ret = [] - if length(parts)==2 - conds_str = split(parts[2], '.') - ret = [parse(Int, i) for i in conds_str] - else - return default - end - - if check_length && length(default)!=length(ret) - Memento.error("An incorrect number of nodes was specified; |$(parts[2])|!=$(length(default)).") - end - return ret -end From 15a8cae4b530537c3d317d8906cc057227bd065e Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 21 Feb 2020 01:06:51 +0100 Subject: [PATCH 012/224] fixes --- src/core/constraint_template.jl | 2 +- src/core/data.jl | 2 +- src/core/data_model_mapping.jl | 5 +---- src/core/data_model_pu.jl | 5 ----- src/io/opendss.jl | 10 ++++++---- test/opf.jl | 2 +- test/opf_bf.jl | 2 +- test/runtests.jl | 2 +- test/transformer.jl | 20 -------------------- 9 files changed, 12 insertions(+), 38 deletions(-) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 9a452a3ac..e55524d08 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -138,7 +138,7 @@ function constraint_mc_trans(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw f_cnd = trans["f_connections"][1:3] t_cnd = trans["t_connections"][1:3] tm_set = trans["tm"] - tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : trans["tm_fix"] + tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : trans["fixed"] tm_scale = calculate_tm_scale(trans, _PMs.ref(pm, nw, :bus, f_bus), _PMs.ref(pm, nw, :bus, t_bus)) #TODO change data model diff --git a/src/core/data.jl b/src/core/data.jl index 60c7b6024..45027ea96 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -114,7 +114,7 @@ end "Calculates the tap scale factor for the non-dimensionalized equations." function calculate_tm_scale(trans::Dict{String,Any}, bus_fr::Dict{String,Any}, bus_to::Dict{String,Any}) tm_nom = trans["tm_nom"] - @show bus_fr + f_vbase = haskey(bus_fr, "vbase") ? bus_fr["vbase"] : bus_fr["base_kv"] t_vbase = haskey(bus_to, "vbase") ? bus_to["vbase"] : bus_to["base_kv"] config = trans["configuration"] diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index e7e9415ef..bb04c9231 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -188,7 +188,6 @@ function _decompose_transformer_nw!(data_model) if haskey(data_model, "transformer_nw") for (tr_id, trans) in data_model["transformer_nw"] - @show trans vnom = trans["vnom"]*data_model["settings"]["v_var_scalar"] snom = trans["snom"]*data_model["settings"]["v_var_scalar"] @@ -222,7 +221,7 @@ function _decompose_transformer_nw!(data_model) # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] - @show trans + trans_2wa = add_virtual!(data_model, "transformer_2wa", Dict( "f_bus" => trans["bus"][w], "t_bus" => trans_t_bus_w[w], @@ -463,7 +462,6 @@ function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) # has to be three-phase for (_, gen) in data_model["gen"] if gen["configuration"]=="wye" - @show gen["connections"] @assert(all(gen["connections"].==[phases..., neutral])) else @assert(all(gen["connections"].==phases)) @@ -482,7 +480,6 @@ function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) data_model["branch"] = data_model["line"] for (_, br) in data_model["branch"] - @show br @assert(all(x in phases for x in br["f_connections"])) @assert(all(x in phases for x in br["t_connections"])) @assert(all(br["f_connections"].==br["t_connections"])) diff --git a/src/core/data_model_pu.jl b/src/core/data_model_pu.jl index 7ca491847..c7dd81e49 100644 --- a/src/core/data_model_pu.jl +++ b/src/core/data_model_pu.jl @@ -114,8 +114,6 @@ function data_model_make_pu!(data_model; sbase=missing, vbases=missing) bus_vbase, line_vbase = _calc_vbase(data_model, vbases) - @show bus_vbase - for (id, bus) in data_model["bus"] _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, v_var_scalar) end @@ -161,7 +159,6 @@ function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) # vnom = bus["vnom"] # _scale_props!(bus, ["vnom"], 1/vbase) # end - @show vbase _scale_props!(bus, prop_vnom, 1/vbase) z_old = 1.0 @@ -253,9 +250,7 @@ function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar) scale(gen, key, sbase_scale) end - @show gen["cost"] scale(gen, "cost", 1/sbase_scale) - @show gen["cost"] # save new vbase gen["vbase"] = vbase diff --git a/src/io/opendss.jl b/src/io/opendss.jl index b3e00fa6c..9313edb71 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -765,7 +765,7 @@ function _dss2pmd_transformer!(pmd_data::Dict, dss_data::Dict, import_all::Bool) transDict["config"] = Dict{Int,Any}() transDict["config"][1] = Dict( "type"=>dyz_primary, - "polarity"=>'+', + "polarity"=>1, "cnd"=>[1, 2, 3], "grounded"=>true, "vm_nom"=>defaults["kvs"][1] @@ -775,20 +775,20 @@ function _dss2pmd_transformer!(pmd_data::Dict, dss_data::Dict, import_all::Bool) type = dyz_map[defaults["conns"][w]] if dyz_primary==type cnd = [1,2,3] - polarity = '+' + polarity = 1 else if defaults["leadlag"] in ["ansi", "lag"] #Yd1 => (123+y,123+d) #Dy1 => (123+d,231-y) #pp_w = (type=="delta") ? "123+" : "231-" cnd = (type=="delta") ? [1, 2, 3] : [2, 3, 1] - polarity = (type=="delta") ? '+' : '-' + polarity = (type=="delta") ? 1 : -1 else # hence defaults["leadlag"] in ["euro", "lead"] #Yd11 => (123+y,312-d) #Dy11 => (123+d,123+y) #pp_w = (type=="delta") ? "312-" : "123+" cnd = (type=="delta") ? [3, 1, 2] : [1, 2, 3] - polarity = (type=="delta") ? '-' : '+' + polarity = (type=="delta") ? -1 : 1 end end transDict["config"][w] = Dict( @@ -1550,8 +1550,10 @@ function _adjust_base_rec!(pmd_data, source::Int, base_kv_new::Float64, nodes_vi base_kv_new_tr = deepcopy(base_kv_new) if source_new==t_bus base_kv_new_tr *= (trans["config_to"]["vm_nom"]/trans["config_fr"]["vm_nom"]) + trans["tm_nom"] *= (base_kv_new_tr/base_kv_prev) else base_kv_new_tr *= (trans["config_fr"]["vm_nom"]/trans["config_to"]["vm_nom"]) + trans["tm_nom"] *= (base_kv_prev/base_kv_new_tr) end # follow the edge to the adjacent node and repeat _adjust_base_rec!(pmd_data, source_new, base_kv_new_tr, nodes_visited, edges_br, edges_br_visited, edges_tr, edges_tr_visited, br_basekv_old) diff --git a/test/opf.jl b/test/opf.jl index ac4679f3c..665b9ee30 100644 --- a/test/opf.jl +++ b/test/opf.jl @@ -202,7 +202,7 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED @test sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]) < 0.0 @test sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]) < 0.0 - @test isapprox(sum(sol["solution"]["gen"]["2"]["pg"] * sol["solution"]["baseMVA"]), 0.0182074; atol=1e-4) + @test isapprox(sum(sol["solution"]["gen"]["2"]["pg"] * sol["solution"]["baseMVA"]), 0.0183685; atol=1e-4) @test isapprox(sum(sol["solution"]["gen"]["2"]["qg"] * sol["solution"]["baseMVA"]), 0.00919404; atol=1e-4) end diff --git a/test/opf_bf.jl b/test/opf_bf.jl index ecdefb8cb..c0451f064 100644 --- a/test/opf_bf.jl +++ b/test/opf_bf.jl @@ -11,7 +11,7 @@ @test isapprox(result["objective"], 44880; atol = 1e0) # @test isapprox(result["solution"]["bus"]["3"]["vm"], 0.911466*[1,1,1]; atol = 1e-3) vm = calc_vm_w(result, "3") - @test isapprox(vm, 0.911466*[1,1,1]; atol = 1e-3) + @test isapprox(vm, 0.910445*[1,1,1]; atol = 1e-3) end diff --git a/test/runtests.jl b/test/runtests.jl index 8cea69752..2b190e7cf 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,7 +26,7 @@ using LinearAlgebra pms_path = joinpath(dirname(pathof(PowerModels)), "..") pmd_path = joinpath(dirname(pathof(PowerModelsDistribution)), "..") -ipopt_solver = with_optimizer(Ipopt.Optimizer, tol=1e-6, print_level=0) +ipopt_solver = with_optimizer(Ipopt.Optimizer, tol=1e-7, print_level=0) cbc_solver = with_optimizer(Cbc.Optimizer, logLevel=0) scs_solver = with_optimizer(SCS.Optimizer, max_iters=20000, eps=1e-5, alpha=0.4, verbose=0) juniper_solver = with_optimizer(Juniper.Optimizer, nl_solver=with_optimizer(Ipopt.Optimizer, tol=1e-4, print_level=0), mip_solver=cbc_solver, log_levels=[]) diff --git a/test/transformer.jl b/test/transformer.jl index 8c801a642..0a094d638 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -107,26 +107,6 @@ @test norm(vm(sol, pmd_data, "3")-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 @test norm(va(sol, pmd_data, "3")-[30.7, -90.0, 152.0], Inf) <= 0.1 end - - @testset "3w transformer acp pf dyy - voltage base" begin - # make sure that different voltage bases lead to the same solution - # (after rescaling) - file = "../test/data/opendss/ut_trans_3w_dyy_basetest.dss" - pmd1 = PMD.parse_file(file) - pmd2 = deepcopy(pmd1) - PMD._adjust_base!(pmd2, start_at_first_tr_prim=false) - scale_2to1 = pmd2["bus"]["3"]["base_kv"]/pmd1["bus"]["3"]["base_kv"] - sol1 = PMD.run_ac_mc_pf(pmd1, ipopt_solver, multiconductor=true) - sol2 = PMD.run_ac_mc_pf(pmd2, ipopt_solver, multiconductor=true) - for bus_id_str in keys(sol1["solution"]["bus"]) - vm1 = sol1["solution"]["bus"][bus_id_str]["vm"] - vm2 = sol2["solution"]["bus"][bus_id_str]["vm"] - va1 = PMD._wrap_to_pi(sol1["solution"]["bus"][bus_id_str]["va"][:]) - va2 = PMD._wrap_to_pi(sol2["solution"]["bus"][bus_id_str]["va"][:]) - @test norm(vm1-vm2*scale_2to1, Inf) <= 1E-6 - @test norm(va1-va2, Inf) <= 1E-3 - end - end end @testset "oltc tests" begin From e26e84377ef214763735e66f2f2df413b126a08f Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 21 Feb 2020 01:53:31 +0100 Subject: [PATCH 013/224] bound wrapper --- src/core/data.jl | 81 +++++++++++++++++++++++------------- src/core/variable.jl | 99 ++++++++++++++------------------------------ 2 files changed, 84 insertions(+), 96 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index 45027ea96..5f9ff9c75 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -114,7 +114,7 @@ end "Calculates the tap scale factor for the non-dimensionalized equations." function calculate_tm_scale(trans::Dict{String,Any}, bus_fr::Dict{String,Any}, bus_to::Dict{String,Any}) tm_nom = trans["tm_nom"] - + f_vbase = haskey(bus_fr, "vbase") ? bus_fr["vbase"] : bus_fr["base_kv"] t_vbase = haskey(bus_to, "vbase") ? bus_to["vbase"] : bus_to["base_kv"] config = trans["configuration"] @@ -282,7 +282,8 @@ function _calc_gen_current_max(gen::Dict, bus::Dict) return smax./vmin else - return missing + N = 3 #TODO update for 4-wire + return fill(Inf, N) end end @@ -294,16 +295,16 @@ lower bound on the voltage magnitude of the connected buses. """ function _calc_branch_current_max(branch::Dict, bus::Dict) bounds = [] + if haskey(branch, "c_rating") push!(bounds, branch["c_rating"]) end if haskey(branch, "s_rating") && haskey(bus, "vmin") push!(bounds, branch["s_rating"]./bus["vmin"]) end - if length(bounds)==0 - return missing - end - return min.(bounds...) + + N = 3 #TODO update for 4-wire + return min.(fill(Inf, N), bounds...) end @@ -315,6 +316,7 @@ lower bound on the voltage magnitude of the connected buses. function _calc_branch_current_max_frto(branch::Dict, bus_fr::Dict, bus_to::Dict) bounds_fr = [] bounds_to = [] + if haskey(branch, "c_rating") push!(bounds_fr, branch["c_rating"]) push!(bounds_to, branch["c_rating"]) @@ -323,8 +325,9 @@ function _calc_branch_current_max_frto(branch::Dict, bus_fr::Dict, bus_to::Dict) push!(bounds_fr, branch["s_rating"]./bus_fr["vmin"]) push!(bounds_to, branch["s_rating"]./bus_to["vmin"]) end - @assert(length(bounds_fr)>=0, "no (implied/valid) current bounds defined") - return min.(bounds_fr...), min.(bounds_to...) + + N = 3 #TODO update for 4-wire + return min.(fill(Inf, N), bounds_fr...), min.(fill(Inf, N), bounds_to...) end @@ -336,18 +339,19 @@ upper bound on the voltage magnitude of the connected buses. function _calc_transformer_power_ub_frto(trans::Dict, bus_fr::Dict, bus_to::Dict) bounds_fr = [] bounds_to = [] - if haskey(trans, "c_rating") - push!(bounds_fr, trans["c_rating"].*bus_fr["vmax"]) - push!(bounds_to, trans["c_rating"].*bus_to["vmax"]) - end - if haskey(trans, "s_rating") - push!(bounds_fr, trans["s_rating"]) - push!(bounds_to, trans["s_rating"]) - end + #TODO redefine transformer bounds + # if haskey(trans, "c_rating") + # push!(bounds_fr, trans["c_rating"].*bus_fr["vmax"]) + # push!(bounds_to, trans["c_rating"].*bus_to["vmax"]) + # end + # if haskey(trans, "s_rating") + # push!(bounds_fr, trans["s_rating"]) + # push!(bounds_to, trans["s_rating"]) + # end + - bounds_fr = isempty(bounds_fr) ? missing : min.(bounds_fr...) - bounds_to = isempty(bounds_to) ? missing : min.(bounds_to...) - return bounds_fr, bounds_to + N = 3 #TODO update for 4-wire + return min.(fill(Inf, N), bounds_fr...), min.(fill(Inf, N), bounds_to...) end @@ -359,18 +363,19 @@ the voltage magnitude of the connected buses. function _calc_transformer_current_max_frto(trans::Dict, bus_fr::Dict, bus_to::Dict) bounds_fr = [] bounds_to = [] + #TODO redefine transformer bounds # if haskey(trans, "c_rating") - # push!(bounds_fr, trans["c_rating"]) - # push!(bounds_to, trans["c_rating"]) + # push!(bounds_fr, trans["c_rating"].*bus_fr["vmax"]) + # push!(bounds_to, trans["c_rating"].*bus_to["vmax"]) # end # if haskey(trans, "s_rating") - # push!(bounds_fr, trans["s_rating"]./bus_fr["vmin"]) - # push!(bounds_to, trans["s_rating"]./bus_to["vmin"]) + # push!(bounds_fr, trans["s_rating"]) + # push!(bounds_to, trans["s_rating"]) # end - bounds_fr = isempty(bounds_fr) ? missing : min.(bounds_fr...) - bounds_to = isempty(bounds_to) ? missing : min.(bounds_to...) - return bounds_fr, bounds_to + + N = 3 #TODO update for 4-wire + return min.(fill(Inf, N), bounds_fr...), min.(fill(Inf, N), bounds_to...) end @@ -381,6 +386,7 @@ upper bound on the voltage magnitude of the connected buses. """ function _calc_branch_power_max(branch::Dict, bus::Dict) bounds = [] + if haskey(branch, "c_rating") && haskey(bus, "vmax") push!(bounds, branch["c_rating"].*bus["vmax"]) end @@ -388,8 +394,8 @@ function _calc_branch_power_max(branch::Dict, bus::Dict) push!(bounds, branch["s_rating"]) end - bounds = isempty(bounds) ? missing : min.(bounds...) - return bounds + N = 3 #TODO update for 4-wire + return min.(fill(Inf, N), bounds...) end @@ -421,7 +427,8 @@ function _calc_branch_series_current_max(branch::Dict, bus_fr::Dict, bus_to::Dic c_max_to_sh = abs.(y_to)*vmax_to # now select element-wise lowest valid bound between fr and to - return min.(c_max_fr_sh.+c_max_fr_tot, c_max_to_sh.+c_max_to_tot) + N = 3 #TODO update for 4-wire + return min.(fill(Inf, N), c_max_fr_sh.+c_max_fr_tot, c_max_to_sh.+c_max_to_tot) end @@ -646,3 +653,19 @@ macro smart_constraint(model, vars, expr) end end) end + + +"Local wrapper method for JuMP.set_lower_bound, which skips NaN and infinite (-Inf only)" +function set_lower_bound(x::JuMP.VariableRef, bound) + if !(isnan(bound) || bound==-Inf) + JuMP.set_lower_bound(x, bound) + end +end + + +"Local wrapper method for JuMP.set_upper_bound, which skips NaN and infinite (+Inf only)" +function set_upper_bound(x::JuMP.VariableRef, bound) + if !(isnan(bound) || bound==Inf) + JuMP.set_upper_bound(x, bound) + end +end diff --git a/src/core/variable.jl b/src/core/variable.jl index 9f6580785..bb8b17da6 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -119,10 +119,8 @@ function variable_mc_branch_flow_active(pm::_PMs.AbstractPowerModel; nw::Int=pm. if bounded for (l,i,j) in _PMs.ref(pm, nw, :arcs) smax = _calc_branch_power_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) - if !ismissing(smax) - JuMP.set_upper_bound.(p[(l,i,j)], smax) - JuMP.set_lower_bound.(p[(l,i,j)], -smax) - end + set_upper_bound.(p[(l,i,j)], smax) + set_lower_bound.(p[(l,i,j)], -smax) end end @@ -154,10 +152,8 @@ function variable_mc_branch_flow_reactive(pm::_PMs.AbstractPowerModel; nw::Int=p if bounded for (l,i,j) in _PMs.ref(pm, nw, :arcs) smax = _calc_branch_power_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) - if !ismissing(smax) - JuMP.set_upper_bound.(q[(l,i,j)], smax) - JuMP.set_lower_bound.(q[(l,i,j)], -smax) - end + JuMP.set_upper_bound.(q[(l,i,j)], smax) + JuMP.set_lower_bound.(q[(l,i,j)], -smax) end end @@ -192,10 +188,8 @@ function variable_mc_branch_current_real(pm::_PMs.AbstractPowerModel; nw::Int=pm if bounded for (l,i,j) in _PMs.ref(pm, nw, :arcs) cmax = _calc_branch_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) - if !ismissing(cmax) - JuMP.set_upper_bound.(cr[(l,i,j)], cmax) - JuMP.set_lower_bound.(cr[(l,i,j)], -cmax) - end + set_upper_bound.(cr[(l,i,j)], cmax) + set_lower_bound.(cr[(l,i,j)], -cmax) end end @@ -219,10 +213,8 @@ function variable_mc_branch_current_imaginary(pm::_PMs.AbstractPowerModel; nw::I if bounded for (l,i,j) in _PMs.ref(pm, nw, :arcs) cmax = _calc_branch_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) - if !ismissing(cmax) - JuMP.set_upper_bound.(ci[(l,i,j)], cmax) - JuMP.set_lower_bound.(ci[(l,i,j)], -cmax) - end + set_upper_bound.(ci[(l,i,j)], cmax) + set_lower_bound.(ci[(l,i,j)], -cmax) end end @@ -246,10 +238,8 @@ function variable_mc_branch_series_current_real(pm::_PMs.AbstractPowerModel; nw: if bounded for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) cmax = _calc_branch_series_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) - if !ismissing(cmax) - JuMP.set_upper_bound.(csr[l], cmax) - JuMP.set_lower_bound.(csr[l], -cmax) - end + set_upper_bound.(csr[l], cmax) + set_lower_bound.(csr[l], -cmax) end end @@ -273,10 +263,8 @@ function variable_mc_branch_series_current_imaginary(pm::_PMs.AbstractPowerModel if bounded for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) cmax = _calc_branch_series_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) - if !ismissing(cmax) - JuMP.set_upper_bound.(csi[l], cmax) - JuMP.set_lower_bound.(csi[l], -cmax) - end + set_upper_bound.(csi[l], cmax) + set_lower_bound.(csi[l], -cmax) end end @@ -303,14 +291,10 @@ function variable_mc_transformer_current_real(pm::_PMs.AbstractPowerModel; nw::I f_bus = _PMs.ref(pm, nw, :bus, i) t_bus = _PMs.ref(pm, nw, :bus, j) cmax_fr, cmax_to = _calc_transformer_current_max_frto(trans, f_bus, t_bus) - if !ismissing(cmax_fr) - JuMP.set_lower_bound(cr[(l,i,j)], -cmax_fr) - JuMP.set_upper_bound(cr[(l,i,j)], cmax_fr) - end - if !ismissing(cmax_to) - JuMP.set_lower_bound(cr[(l,j,i)], -cmax_to) - JuMP.set_upper_bound(cr[(l,j,i)], cmax_to) - end + set_lower_bound(cr[(l,i,j)], -cmax_fr) + set_upper_bound(cr[(l,i,j)], cmax_fr) + set_lower_bound(cr[(l,j,i)], -cmax_to) + set_upper_bound(cr[(l,j,i)], cmax_to) end end @@ -337,14 +321,10 @@ function variable_mc_transformer_current_imaginary(pm::_PMs.AbstractPowerModel; f_bus = _PMs.ref(pm, nw, :bus, i) t_bus = _PMs.ref(pm, nw, :bus, j) cmax_fr, cmax_to = _calc_transformer_current_max_frto(trans, f_bus, t_bus) - if !ismissing(cmax_fr) - JuMP.set_lower_bound(ci[(l,i,j)], -cmax_fr) - JuMP.set_upper_bound(ci[(l,i,j)], cmax_fr) - end - if !ismissing(cmax_to) - JuMP.set_lower_bound(ci[(l,j,i)], -cmax_to) - JuMP.set_upper_bound(ci[(l,j,i)], cmax_to) - end + set_lower_bound(ci[(l,i,j)], -cmax_fr) + set_upper_bound(ci[(l,i,j)], cmax_fr) + set_lower_bound(ci[(l,j,i)], -cmax_to) + set_upper_bound(ci[(l,j,i)], cmax_to) end end @@ -513,16 +493,10 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractPowerModel; nw::In for arc in _PMs.ref(pm, nw, :arcs_from_trans) (t,i,j) = arc s_rating_fr, s_rating_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) - - if !ismissing(s_rating_fr) - JuMP.set_lower_bound.(pt[(t,i,j)], -s_rating_fr) - JuMP.set_upper_bound.(pt[(t,i,j)], s_rating_fr) - end - - if !ismissing(s_rating_to) - JuMP.set_lower_bound.(pt[(t,j,i)], -s_rating_fr) - JuMP.set_upper_bound.(pt[(t,j,i)], s_rating_fr) - end + set_lower_bound.(pt[(t,i,j)], -s_rating_fr) + set_upper_bound.(pt[(t,i,j)], s_rating_fr) + set_lower_bound.(pt[(t,j,i)], -s_rating_fr) + set_upper_bound.(pt[(t,j,i)], s_rating_fr) end end @@ -557,15 +531,10 @@ function variable_mc_transformer_flow_reactive(pm::_PMs.AbstractPowerModel; nw:: (t,i,j) = arc s_rating_fr, s_rating_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) - if !ismissing(s_rating_fr) - JuMP.set_lower_bound.(qt[(t,i,j)], -s_rating_fr) - JuMP.set_upper_bound.(qt[(t,i,j)], s_rating_fr) - end - - if !ismissing(s_rating_to) - JuMP.set_lower_bound.(qt[(t,j,i)], -s_rating_fr) - JuMP.set_upper_bound.(qt[(t,j,i)], s_rating_fr) - end + JuMP.set_lower_bound.(qt[(t,i,j)], -s_rating_fr) + JuMP.set_upper_bound.(qt[(t,i,j)], s_rating_fr) + JuMP.set_lower_bound.(qt[(t,j,i)], -s_rating_fr) + JuMP.set_upper_bound.(qt[(t,j,i)], s_rating_fr) end end @@ -892,10 +861,8 @@ function variable_mc_generation_current_real(pm::_PMs.AbstractPowerModel; nw::In if bounded for (i, g) in gen cmax = _calc_gen_current_max(g, _PMs.ref(pm, nw, :bus, g["gen_bus"])) - if !ismissing(cmax) - JuMP.set_lower_bound.(crg[i], -cmax) - JuMP.set_upper_bound.(crg[i], cmax) - end + set_lower_bound.(crg[i], -cmax) + set_upper_bound.(crg[i], cmax) end end @@ -917,10 +884,8 @@ function variable_mc_generation_current_imaginary(pm::_PMs.AbstractPowerModel; n if bounded for (i, g) in gen cmax = _calc_gen_current_max(g, _PMs.ref(pm, nw, :bus, g["gen_bus"])) - if !ismissing(cmax) - JuMP.set_lower_bound.(cig[i], -cmax) - JuMP.set_upper_bound.(cig[i], cmax) - end + set_lower_bound.(cig[i], -cmax) + set_upper_bound.(cig[i], cmax) end end From 8f4fc921fa14e925193535d9742a43c8ccc5f13f Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 21 Feb 2020 02:08:25 +0100 Subject: [PATCH 014/224] rm data model files --- src/core/data_model_mapping.jl | 566 --------------------------------- src/core/data_model_pu.jl | 314 ------------------ src/io/data_model_test_3w.jl | 84 ----- 3 files changed, 964 deletions(-) delete mode 100644 src/core/data_model_mapping.jl delete mode 100644 src/core/data_model_pu.jl delete mode 100644 src/io/data_model_test_3w.jl diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl deleted file mode 100644 index bb04c9231..000000000 --- a/src/core/data_model_mapping.jl +++ /dev/null @@ -1,566 +0,0 @@ -import LinearAlgebra - -# MAP DATA MODEL DOWN - -function data_model_map!(data_model) - - !haskey(data_model, "mappings") - - # needs to happen before _expand_linecode, as it might contain a linecode for the internal impedance - add_mappings!(data_model, "decompose_voltage_source", _decompose_voltage_source!(data_model)) - _expand_linecode!(data_model) - # creates shunt of 4x4; disabled for now (incompatible 3-wire kron-reduced) - #add_mappings!(data_model, "load_to_shunt", _load_to_shunt!(data_model)) - add_mappings!(data_model, "capacitor_to_shunt", _capacitor_to_shunt!(data_model)) - add_mappings!(data_model, "decompose_transformer_nw", _decompose_transformer_nw!(data_model)) - - # add low level component types if not present yet - for comp_type in ["load", "generator", "bus", "line", "shunt", "transformer_2wa", "storage", "switch"] - if !haskey(data_model, comp_type) - data_model[comp_type] = Dict{String, Any}() - end - end - return data_model -end - - -function _expand_linecode!(data_model) - # expand line codes - for (id, line) in data_model["line"] - if haskey(line, "linecode") - linecode = data_model["linecode"][line["linecode"]] - for key in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - line[key] = line["length"]*linecode[key] - end - delete!(line, "linecode") - delete!(line, "length") - end - end - delete!(data_model, "linecode") -end - - -function _load_to_shunt!(data_model) - mappings = [] - if haskey(data_model, "load") - for (id, load) in data_model["load"] - if load["model"]=="constant_impedance" - b = load["qd_ref"]./load["vnom"].^2*1E3 - g = load["pd_ref"]./load["vnom"].^2*1E3 - y = b.+im*g - N = length(b) - - if load["configuration"]=="delta" - # create delta transformation matrix Md - Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) - Md[N,1] = -1 - Y = Md'*LinearAlgebra.diagm(0=>y)*Md - - else # load["configuration"]=="wye" - Y_fr = LinearAlgebra.diagm(0=>y) - # B = [[b]; -1'*[b]]*[I -1] - Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) - end - - shunt = add_virtual!(data_model, "shunt", create_shunt(bus=load["bus"], connections=load["connections"], b_sh=imag.(Y), g_sh=real.(Y))) - - delete_component!(data_model, "load", load) - - push!(mappings, Dict( - "load" => load, - "shunt_id" => shunt["id"], - )) - end - end - end - - return mappings -end - - -function _capacitor_to_shunt!(data_model) - mappings = [] - - if haskey(data_model, "capacitor") - for (id, cap) in data_model["capacitor"] - b = cap["qd_ref"]./cap["vnom"]^2*1E3 - N = length(b) - - if cap["configuration"]=="delta" - # create delta transformation matrix Md - Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) - Md[N,1] = -1 - B = Md'*LinearAlgebra.diagm(0=>b)*Md - - elseif cap["configuration"]=="wye-grounded" - B = LinearAlgebra.diagm(0=>b) - - elseif cap["configuration"]=="wye-floating" - # this is a floating wye-segment - # B = [b]*(I-1/(b'*1)*[b';...;b']) - B = LinearAlgebra.diagm(0=>b)*(LinearAlgebra.diagm(0=>ones(N)) - 1/sum(b)*repeat(b',N,1)) - - else # cap["configuration"]=="wye" - B_fr = LinearAlgebra.diagm(0=>b) - # B = [[b]; -1'*[b]]*[I -1] - B = vcat(B_fr, -ones(N)'*B_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) - end - - shunt = create_shunt(NaN, cap["bus"], cap["connections"], b_sh=B) - add_virtual!(data_model, "shunt", shunt) - delete_component!(data_model, "capacitor", cap) - - push!(mappings, Dict( - "capacitor" => cap, - "shunt_id" => shunt["id"], - )) - end - end - - return mappings -end - - -function _decompose_voltage_source!(data_model) - mappings = [] - - if haskey(data_model, "voltage_source") - for (id, vs) in data_model["voltage_source"] - - bus = data_model["bus"][vs["bus"]] - - line_kwargs = Dict(Symbol(prop)=>vs[prop] for prop in ["rs", "xs", "g_fr", "b_fr", "g_to", "b_to", "linecode", "length"] if haskey(vs, prop)) - lossy = !isempty(line_kwargs) - - # if any loss parameters (or linecode) were supplied, then create a line and internal bus - if lossy - sourcebus = add_virtual!(data_model, "bus", create_bus(terminals=deepcopy(vs["connections"]))) - - line = add_virtual!(data_model, "line", create_line(; - f_bus=sourcebus["id"], f_connections=vs["connections"], t_bus=bus["id"], t_connections=vs["connections"], - line_kwargs... - )) - else - sourcebus = bus - end - - ground = _get_ground!(sourcebus) - gen = create_generator(bus=sourcebus["id"], connections=[vs["connections"]..., ground]) - - for prop in ["pg_max", "pg_min", "qg_max", "qg_min"] - if haskey(vs, prop) - gen[prop] = vs[prop] - end - end - - add_virtual!(data_model, "generator", gen) - - conns = vs["connections"] - terminals = bus["terminals"] - - tmp = Dict(enumerate(conns)) - sourcebus["vm"] = sourcebus["vmax"] = sourcebus["vmin"] = [haskey(tmp, t) ? vs["vm"][tmp[t]] : NaN for t in terminals] - sourcebus["va"] = [haskey(tmp, t) ? vs["va"][tmp[t]] : NaN for t in terminals] - sourcebus["bus_type"] = 3 - - delete_component!(data_model, "voltage_source", vs["id"]) - push!(mappings, Dict("voltage_source"=>vs, "gen_id"=>gen["id"], - "vbus_id" => lossy ? sourcebus["id"] : nothing, - "vline_id" => lossy ? line["id"] : nothing, - )) - end - end - - return mappings -end - - -""" - - function decompose_transformer_nw_lossy!(data_model) - -Replaces complex transformers with a composition of ideal transformers and lines -which model losses. New buses (virtual, no physical meaning) are added. -""" -function _decompose_transformer_nw!(data_model) - mappings = [] - - if haskey(data_model, "transformer_nw") - for (tr_id, trans) in data_model["transformer_nw"] - - vnom = trans["vnom"]*data_model["settings"]["v_var_scalar"] - snom = trans["snom"]*data_model["settings"]["v_var_scalar"] - - nrw = length(trans["bus"]) - - # calculate zbase in which the data is specified, and convert to SI - zbase = (vnom.^2)./snom - # x_sc is specified with respect to first winding - x_sc = trans["xsc"].*zbase[1] - # rs is specified with respect to each winding - r_s = trans["rs"].*zbase - - g_sh = (trans["noloadloss"]*snom[1]/3)/vnom[1]^2 - b_sh = (trans["imag"]*snom[1]/3)/vnom[1]^2 - - # data is measured externally, but we now refer it to the internal side - ratios = vnom/1E3 - x_sc = x_sc./ratios[1]^2 - r_s = r_s./ratios.^2 - g_sh = g_sh*ratios[1]^2 - b_sh = b_sh*ratios[1]^2 - - # convert x_sc from list of upper triangle elements to an explicit dict - y_sh = g_sh + im*b_sh - z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) - - vbuses, vlines, trans_t_bus_w = _build_loss_model!(data_model, r_s, z_sc, y_sh) - - trans_ids_w = Array{String, 1}(undef, nrw) - for w in 1:nrw - # 2-WINDING TRANSFORMER - # make virtual bus and mark it for reduction - tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] - - trans_2wa = add_virtual!(data_model, "transformer_2wa", Dict( - "f_bus" => trans["bus"][w], - "t_bus" => trans_t_bus_w[w], - "tm_nom" => tm_nom, - "f_connections" => trans["connections"][w], - "t_connections" => collect(1:4), - "configuration" => trans["configuration"][w], - "polarity" => trans["polarity"][w], - "tm" => trans["tm"][w], - "fixed" => trans["fixed"][w], - )) - - for prop in ["tm_min", "tm_max", "tm_step"] - if haskey(trans, prop) - trans_2wa[prop] = trans[prop][w] - end - end - - trans_ids_w[w] = trans_2wa["id"] - end - - delete_component!(data_model, "transformer_nw", trans) - - push!(mappings, Dict( - "trans"=>trans, - "trans_2wa"=>trans_ids_w, - "vlines"=>vlines, - "vbuses"=>vbuses, - )) - end - end - - return mappings -end - - -""" -Converts a set of short-circuit tests to an equivalent reactance network. -Reference: -R. C. Dugan, “A perspective on transformer modeling for distribution system analysis,” -in 2003 IEEE Power Engineering Society General Meeting (IEEE Cat. No.03CH37491), 2003, vol. 1, pp. 114-119 Vol. 1. -""" -function _sc2br_impedance(Zsc) - N = maximum([maximum(k) for k in keys(Zsc)]) - # check whether no keys are missing - # Zsc should contain tupples for upper triangle of NxN - for i in 1:N - for j in i+1:N - if !haskey(Zsc, (i,j)) - if haskey(Zsc, (j,i)) - # Zsc is symmetric; use value of lower triangle if defined - Zsc[(i,j)] = Zsc[(j,i)] - else - Memento.error(_LOGGER, "Short-circuit impedance between winding $i and $j is missing.") - end - end - end - end - # make Zb - Zb = zeros(Complex{Float64}, N-1,N-1) - for i in 1:N-1 - Zb[i,i] = Zsc[(1,i+1)] - end - for i in 1:N-1 - for j in 1:i-1 - Zb[i,j] = (Zb[i,i]+Zb[j,j]-Zsc[(j+1,i+1)])/2 - Zb[j,i] = Zb[i,j] - end - end - # get Ybus - Y = LinearAlgebra.pinv(Zb) - Y = [-Y*ones(N-1) Y] - Y = [-ones(1,N-1)*Y; Y] - # extract elements - Zbr = Dict() - for k in keys(Zsc) - Zbr[k] = (abs(Y[k...])==0) ? Inf : -1/Y[k...] - end - return Zbr -end - - -function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) - # precompute the minimal set of buses and lines - N = length(r_s) - tr_t_bus = collect(1:N) - buses = Set(1:2*N) - edges = [[[i,i+N] for i in 1:N]..., [[i+N,j+N] for (i,j) in keys(zsc)]...] - lines = Dict(enumerate(edges)) - z = Dict(enumerate([r_s..., values(zsc)...])) - shunts = Dict(2=>ysh) - - # remove Inf lines - - for (l,edge) in lines - if real(z[l])==Inf || imag(z[l])==Inf - delete!(lines, l) - delete!(z, l) - end - end - - # merge short circuits - - stack = Set(keys(lines)) - - while !isempty(stack) - l = pop!(stack) - if z[l] == 0 - (i,j) = lines[l] - # remove line - delete!(lines, l) - # remove bus j - delete!(buses, j) - # update lines - for (k,(edge)) in lines - if edge[1]==j - edge[1] = i - end - if edge[2]==j - edge[2] = i - end - if edge[1]==edge[2] - delete!(lines, k) - delete!(stack, k) - end - end - # move shunts - if haskey(shunts, j) - if haskey(shunts, i) - shunts[i] += shunts[j] - else - shunts[i] = shunts[j] - end - end - # update transformer buses - for w in 1:N - if tr_t_bus[w]==j - tr_t_bus[w] = i - end - end - end - end - - bus_ids = Dict() - for bus in buses - bus_ids[bus] = add_virtual_get_id!(data_model, "bus", create_bus(id="")) - end - line_ids = Dict() - for (l,(i,j)) in lines - # merge the shunts into the shunts of the pi model of the line - g_fr = b_fr = g_to = b_to = 0 - if haskey(shunts, i) - g_fr = real(shunts[i]) - b_fr = imag(shunts[i]) - delete!(shunts, i) - end - if haskey(shunts, j) - g_fr = real(shunts[j]) - b_fr = imag(shunts[j]) - delete!(shunts, j) - end - line_ids[l] = add_virtual_get_id!(data_model, "line", Dict( - "status"=>1, - "f_bus"=>bus_ids[i], "t_bus"=>bus_ids[j], - "f_connections"=>collect(1:n_phases), - "t_connections"=>collect(1:n_phases), - "rs"=>LinearAlgebra.diagm(0=>fill(real(z[l]), n_phases)), - "xs"=>LinearAlgebra.diagm(0=>fill(imag(z[l]), n_phases)), - "g_fr"=>LinearAlgebra.diagm(0=>fill(g_fr, n_phases)), - "b_fr"=>LinearAlgebra.diagm(0=>fill(b_fr, n_phases)), - "g_to"=>LinearAlgebra.diagm(0=>fill(g_to, n_phases)), - "b_to"=>LinearAlgebra.diagm(0=>fill(b_to, n_phases)), - )) - end - - return bus_ids, line_ids, [bus_ids[bus] for bus in tr_t_bus] -end - -function _alias!(dict, fr, to) - if haskey(dict, fr) - dict[to] = dict[fr] - end -end - -function _pad_props!(comp, keys, phases_comp, phases_all) - pos = Dict((x,i) for (i,x) in enumerate(phases_all)) - inds = [pos[x] for x in phases_comp] - for prop in keys - if haskey(comp, prop) - if isa(comp[prop], Vector) - tmp = zeros(length(phases_all)) - tmp[inds] = comp[prop] - comp[prop] = tmp - elseif isa(comp[prop], Matrix) - tmp = zeros(length(phases_all), length(phases_all)) - tmp[inds, inds] = comp[prop] - comp[prop] = tmp - else - error("Property is not a vector or matrix!") - end - end - end -end - -function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) - data_model["conductors"] = 3 - data_model["buspairs"] = nothing - for (_, bus) in data_model["bus"] - bus["bus_i"] = bus["index"] - terminals = bus["terminals"] - @assert(all(t in phases for t in terminals)||all(terminals.==[phases..., neutral])) - for prop in ["vm", "va", "vmin", "vmax"] - if haskey(bus, prop) - if length(bus[prop])==4 - val = bus[prop] - bus[prop] = val[terminals.!=neutral] - end - end - end - end - - for (_, load) in data_model["load"] - # remove neutral - if load["configuration"]=="wye" - @assert(load["connections"][end]==4) - load["connections"] = load["connections"][1:end-1] - _pad_props!(load, ["pd", "qd"], load["connections"], phases) - else - # three-phase loads can only be delta-connected - #@assert(all(load["connections"].==phases)) - end - - load["load_bus"] = load["bus"] - end - - data_model["gen"] = data_model["generator"] - - # has to be three-phase - for (_, gen) in data_model["gen"] - if gen["configuration"]=="wye" - @assert(all(gen["connections"].==[phases..., neutral])) - else - @assert(all(gen["connections"].==phases)) - end - - _alias!(gen, "status", "gen_status") - _alias!(gen, "bus", "gen_bus") - _alias!(gen, "pg_min", "pmin") - _alias!(gen, "qg_min", "qmin") - _alias!(gen, "pg_max", "pmax") - _alias!(gen, "qg_max", "qmax") - _alias!(gen, "configuration", "conn") - - gen["model"] = 2 - end - - data_model["branch"] = data_model["line"] - for (_, br) in data_model["branch"] - @assert(all(x in phases for x in br["f_connections"])) - @assert(all(x in phases for x in br["t_connections"])) - @assert(all(br["f_connections"].==br["t_connections"])) - _pad_props!(br, ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to", "s_rating", "c_rating"], br["f_connections"], phases) - - # rename - _alias!(br, "status", "br_status") - _alias!(br, "rs", "br_r") - _alias!(br, "xs", "br_x") - - br["tap"] = 1.0 - br["shift"] = 0 - - if !haskey(br, "angmin") - N = size(br["br_r"])[1] - br["angmin"] = fill(-pi/2, N) - br["angmax"] = fill(pi/2, N) - end - end - - for (_, shunt) in data_model["shunt"] - @assert(all(x in phases for x in shunt["connections"])) - _pad_props!(shunt, ["g_sh", "b_sh"], shunt["connections"], phases) - _alias!(shunt, "bus", "shunt_bus") - _alias!(shunt, "g_sh", "gs") - _alias!(shunt, "b_sh", "bs") - end - - data_model["dcline"] = Dict() - data_model["transformer"] = data_model["transformer_2wa"] - - data_model["per_unit"] = true - data_model["baseMVA"] = data_model["settings"]["sbase"]*data_model["settings"]["v_var_scalar"]/1E6 - data_model["name"] = "IDC" - - - return data_model -end - -# MAP SOLUTION UP - -function solution_unmap!(solution::Dict, data_model::Dict) - for i in length(data_model["mappings"]):-1:1 - (name, data) = data_model["mappings"][i] - - if name=="decompose_transformer_nw" - for bus_id in values(data["vbuses"]) - delete!(solution["bus"], bus_id) - end - - for line_id in values(data["vlines"]) - delete!(solution["branch"], line_id) - end - - pt = [solution["transformer"][tr_id]["pf"] for tr_id in data["trans_2wa"]] - qt = [solution["transformer"][tr_id]["qf"] for tr_id in data["trans_2wa"]] - for tr_id in data["trans_2wa"] - delete!(solution["transformer"], tr_id) - end - - add_solution!(solution, "transformer_nw", data["trans"]["id"], Dict("pt"=>pt, "qt"=>qt)) - elseif name=="capacitor_to_shunt" - # shunt has no solutions defined - delete_solution!(solution, "shunt", data["shunt_id"]) - add_solution!(solution, "capacitor", data["capacitor"]["id"], Dict()) - elseif name=="load_to_shunt" - # shunt has no solutions, but a load should have! - delete!(solution, "shunt", data["shunt_id"]) - add_solution!(solution, "load", data["load"]["id"], Dict()) - elseif name=="decompose_voltage_source" - gen = solution["gen"][data["gen_id"]] - delete_solution!(solution, "gen", data["gen_id"]) - add_solution!(solution, "voltage_source", data["voltage_source"]["id"], Dict("pg"=>gen["pg"], "qg"=>gen["qg"])) - - end - end - - # remove component dicts if empty - for (comp_type, comp_dict) in solution - if isa(comp_dict, Dict) && isempty(comp_dict) - delete!(solution, comp_type) - end - end -end diff --git a/src/core/data_model_pu.jl b/src/core/data_model_pu.jl deleted file mode 100644 index c7dd81e49..000000000 --- a/src/core/data_model_pu.jl +++ /dev/null @@ -1,314 +0,0 @@ - -function _find_zones(data_model) - unused_line_ids = Set(keys(data_model["line"])) - bus_lines = Dict([(id,Set()) for id in keys(data_model["bus"])]) - for (line_id,line) in data_model["line"] - f_bus = line["f_bus"] - t_bus = line["t_bus"] - push!(bus_lines[f_bus], (line_id,t_bus)) - push!(bus_lines[t_bus], (line_id,f_bus)) - end - zones = [] - buses = Set(keys(data_model["bus"])) - while !isempty(buses) - stack = [pop!(buses)] - zone = Set() - while !isempty(stack) - bus = pop!(stack) - delete!(buses, bus) - push!(zone, bus) - for (line_id,bus_to) in bus_lines[bus] - if line_id in unused_line_ids && bus_to in buses - delete!(unused_line_ids, line_id) - push!(stack, bus_to) - end - end - end - append!(zones, [zone]) - end - zones = Dict(enumerate(zones)) - - return zones -end - - -function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) - # find zones of buses connected by lines - zones = _find_zones(data_model) - bus_to_zone = Dict([(bus,zone) for (zone, buses) in zones for bus in buses]) - - # assign specified vbase to corresponding zones - zone_vbase = Dict{Int, Union{Missing,Real}}([(zone,missing) for zone in keys(zones)]) - for (bus,vbase) in vbase_sources - if !ismissing(zone_vbase[bus_to_zone[bus]]) - Memento.warn("You supplied multiple voltage bases for the same zone; ignoring all but the last one.") - end - zone_vbase[bus_to_zone[bus]] = vbase - end - - # transformers form the edges between these zones - zone_edges = Dict([(zone,[]) for zone in keys(zones)]) - edges = Set() - for (i,(_,trans)) in enumerate(data_model["transformer_2wa"]) - push!(edges,i) - f_zone = bus_to_zone[trans["f_bus"]] - t_zone = bus_to_zone[trans["t_bus"]] - tm_nom = trans["configuration"]=="delta" ? trans["tm_nom"]/sqrt(3) : trans["tm_nom"] - push!(zone_edges[f_zone], (i, t_zone, 1/tm_nom)) - push!(zone_edges[t_zone], (i, f_zone, tm_nom)) - end - - # initialize the stack with all specified zones - stack = [zone for (zone,vbase) in zone_vbase if !ismissing(vbase)] - - while !isempty(stack) - - zone = pop!(stack) - - for (edge_id, zone_to, scale) in zone_edges[zone] - delete!(edges, edge_id) - - if ismissing(zone_vbase[zone_to]) - zone_vbase[zone_to] = zone_vbase[zone]*scale - push!(stack, zone_to) - end - end - end - - bus_vbase = Dict([(bus,zone_vbase[zone]) for (bus,zone) in bus_to_zone]) - line_vbase = Dict([(id, bus_vbase[line["f_bus"]]) for (id,line) in data_model["line"]]) - return (bus_vbase, line_vbase) -end - - -function data_model_make_pu!(data_model; sbase=missing, vbases=missing) - v_var_scalar = data_model["settings"]["v_var_scalar"] - - if haskey(data_model["settings"], "sbase") - sbase_old = data_model["settings"]["sbase"] - else - sbase_old = 1 - end - - if ismissing(sbase) - if haskey(data_model["settings"], "set_sbase_val") - sbase = data_model["settings"]["set_sbase_val"] - else - sbase = 1 - end - end - - # automatically find a good vbase - if ismissing(vbases) - if haskey(data_model["settings"], "set_vbase_val") && haskey(data_model["settings"], "set_vbase_bus") - vbases = Dict(data_model["settings"]["set_vbase_bus"]=>data_model["settings"]["set_vbase_val"]) - else - buses_type_3 = [(id, sum(bus["vm"])/length(bus["vm"])) for (id,bus) in data_model["bus"] if haskey(bus, "bus_type") && bus["bus_type"]==3] - if !isempty(buses_type_3) - vbases = Dict([buses_type_3[1]]) - else - Memento.error("Please specify vbases manually; cannot make an educated guess for this data model.") - end - end - end - - bus_vbase, line_vbase = _calc_vbase(data_model, vbases) - - for (id, bus) in data_model["bus"] - _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, v_var_scalar) - end - - for (id, line) in data_model["line"] - vbase = line_vbase[id] - _rebase_pu_line!(line, line_vbase[id], sbase, sbase_old, v_var_scalar) - end - - for (id, shunt) in data_model["shunt"] - _rebase_pu_shunt!(shunt, bus_vbase[shunt["bus"]], sbase, sbase_old, v_var_scalar) - end - - for (id, load) in data_model["load"] - _rebase_pu_load!(load, bus_vbase[load["bus"]], sbase, sbase_old, v_var_scalar) - end - - for (id, gen) in data_model["generator"] - _rebase_pu_generator!(gen, bus_vbase[gen["bus"]], sbase, sbase_old, v_var_scalar) - end - - for (id, trans) in data_model["transformer_2wa"] - # voltage base across transformer does not have to be consistent with the ratio! - f_vbase = bus_vbase[trans["f_bus"]] - t_vbase = bus_vbase[trans["t_bus"]] - _rebase_pu_transformer_2w_ideal!(trans, f_vbase, t_vbase, sbase_old, sbase, v_var_scalar) - end - - data_model["settings"]["sbase"] = sbase - - return data_model -end - - -function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) - - # if not in p.u., these are normalized with respect to vnom - prop_vnom = ["vm", "vmax", "vmin", "vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] - - if !haskey(bus, "vbase") - - # if haskey(bus, "vnom") - # vnom = bus["vnom"] - # _scale_props!(bus, ["vnom"], 1/vbase) - # end - _scale_props!(bus, prop_vnom, 1/vbase) - - z_old = 1.0 - else - vbase_old = bus["vbase"] - _scale_props!(bus, [prop_vnom..., "vnom"], vbase_old/vbase) - - z_old = vbase_old^2*sbase_old*v_var_scalar - end - - # rebase grounding resistance - z_new = vbase^2/sbase*v_var_scalar - z_scale = z_old/z_new - _scale_props!(bus, ["rg", "xg"], z_scale) - - # save new vbase - bus["vbase"] = vbase -end - - -function _rebase_pu_line!(line, vbase, sbase, sbase_old, v_var_scalar) - - if !haskey(line, "vbase") - z_old = 1 - else - z_old = vbase_old^2/sbase_old*v_var_scalar - end - - z_new = vbase^2/sbase*v_var_scalar - z_scale = z_old/z_new - y_scale = 1/z_scale - - _scale_props!(line, ["rs", "xs"], z_scale) - _scale_props!(line, ["b_fr", "g_fr", "b_to", "g_to"], y_scale) - - # save new vbase - line["vbase"] = vbase -end - - -function _rebase_pu_shunt!(shunt, vbase, sbase, sbase_old, v_var_scalar) - - if !haskey(shunt, "vbase") - z_old = 1 - else - z_old = vbase_old^2/sbase_old*v_var_scalar - end - - # rebase grounding resistance - z_new = vbase^2/sbase*v_var_scalar - z_scale = z_old/z_new - y_scale = 1/z_scale - scale(shunt, "gs", y_scale) - scale(shunt, "bs", y_scale) - - # save new vbase - shunt["vbase"] = vbase -end - - -function _rebase_pu_load!(load, vbase, sbase, sbase_old, v_var_scalar) - - if !haskey(load, "vbase") - vbase_old = 1 - sbase_old = 1 - else - vbase_old = load["vbase"] - end - - vbase_old = get(load, "vbase", 1.0) - vbase_scale = vbase_old/vbase - scale(load, "vnom", vbase_scale) - - sbase_scale = sbase_old/sbase - scale(load, "pd", sbase_scale) - scale(load, "qd", sbase_scale) - - # save new vbase - load["vbase"] = vbase -end - - -function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar) - vbase_old = get(gen, "vbase", 1.0/v_var_scalar) - vbase_scale = vbase_old/vbase - sbase_scale = sbase_old/sbase - - for key in ["pd_set", "qd_set", "pd_min", "qd_min", "pd_max", "qd_max"] - scale(gen, key, sbase_scale) - end - - scale(gen, "cost", 1/sbase_scale) - - # save new vbase - gen["vbase"] = vbase -end - - -function _rebase_pu_transformer_2w_ideal!(trans, f_vbase_new, t_vbase_new, sbase_old, sbase_new, v_var_scalar) - f_vbase_old = get(trans, "f_vbase", 1.0) - t_vbase_old = get(trans, "t_vbase", 1.0) - f_vbase_scale = f_vbase_old/f_vbase_new - t_vbase_scale = t_vbase_old/t_vbase_new - - scale(trans, "tm_nom", f_vbase_scale/t_vbase_scale) - - # save new vbase - trans["f_vbase"] = f_vbase_new - trans["t_vbase"] = t_vbase_new -end - - -function _scale_props!(comp::Dict{String, Any}, prop_names::Array{String, 1}, scale::Real) - for name in prop_names - if haskey(comp, name) - comp[name] *= scale - end - end -end - -#data_model_user = make_test_data_model() -#data_model_base = map_down_data_model(data_model_user) -#bus_vbase, line_vbase = get_vbase(data_model_base, Dict("1"=>230.0)) - -#make_pu!(data_model_base) - -function add_big_M!(data_model; kwargs...) - big_M = Dict{String, Any}() - - big_M["v_phase_pu_min"] = add_kwarg!(big_M, kwargs, :v_phase_pu_min, 0.5) - big_M["v_phase_pu_max"] = add_kwarg!(big_M, kwargs, :v_phase_pu_max, 2.0) - big_M["v_neutral_pu_min"] = add_kwarg!(big_M, kwargs, :v_neutral_pu_min, 0.0) - big_M["v_neutral_pu_max"] = add_kwarg!(big_M, kwargs, :v_neutral_pu_max, 0.5) - - data_model["big_M"] = big_M -end - -function solution_unmake_pu!(solution, data_model) - sbase = data_model["sbase"] - for (comp_type, comp_dict) in [(x,y) for (x,y) in solution if isa(y, Dict)] - for (id, comp) in comp_dict - for (prop, val) in comp - if any([occursin(x, prop) for x in ["p", "q"]]) - comp[prop] = val*sbase - elseif any([occursin(x, prop) for x in ["vm", "vr", "vi"]]) - comp[prop] = val*data_model[comp_type][id]["vbase"] - end - end - end - end - - return solution -end diff --git a/src/io/data_model_test_3w.jl b/src/io/data_model_test_3w.jl deleted file mode 100644 index 7ec20ef04..000000000 --- a/src/io/data_model_test_3w.jl +++ /dev/null @@ -1,84 +0,0 @@ -BASE_DIR = "/Users/sclaeys/code/PowerModelsDistribution.jl/src/io" -include("$BASE_DIR/data_model_util.jl") -include("$BASE_DIR/data_model_components.jl") -include("$BASE_DIR/../core/data_model_mapping.jl") -include("$BASE_DIR/../core/data_model_pu.jl") - -## BUILD DATA MODEL - -data_model = create_data_model() - -add!(data_model, "linecode", create_linecode(id="3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3)))) - -add!(data_model, "voltage_source", create_voltage_source(id="source", bus="sourcebus", vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], - #pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), -)) - -add!(data_model, "line", create_line(id=:test, f_bus="sourcebus", t_bus="tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3))) - -add!(data_model, "bus", create_bus(id="sourcebus", terminals=collect(1:3))) -add!(data_model, "bus", create_bus(id="tr_prim", terminals=collect(1:4))) -add!(data_model, "bus", create_bus(id="tr_sec", terminals=collect(1:4))) -#add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) - -# add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], -# [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], -# configuration=["delta", "wye", "delta"], -# xsc=[0.0, 0.0, 0.0], -# rs=[0.0, 0.0, 0.0], -# loadloss=0.00, -# imag=0.00, -# )) - -add!(data_model, "transformer_nw", create_transformer_nw(id="1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], - vnom=[0.230, 100], snom=[0.230, 0.230], - configuration=["wye", "wye"], - xsc=[0.0], - rs=[0.0, 0.0], - noloadloss=0.00, - imag=0.00, -)) - -add!(data_model, "load", create_load(id="1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0])) - -# add!(data_model, "generator", create_generator("1", "source", -# connections=[1, 2, 3, 4], -# pg_min=fill(-100, 3), -# pg_max=fill( 100, 3), -# qg_min=fill(-100, 3), -# qg_max=fill( 100, 3), -# )) - -#add!(data_model, "capacitor", create_capacitor(1, "tr_sec", 0.230*sqrt(3), qd_ref=[1.0, 1.0, 1.0]*1E-3, connections=[1,2,3], configuration="delta")) - -check_data_model(data_model) -# PREP THE MODEL FOR OPTIMIZATION - -data_model_map!(data_model) -#bsh = data_model["shunt"]["_virtual_1"]["b_sh"] -# -data_model_make_pu!(data_model, vbases=Dict("sourcebus"=>0.230)) -# -data_model_index!(data_model) -data_model_make_compatible_v8!(data_model) - -# OPTIMIZE THE MODEL - -import PowerModelsDistribution -PMD = PowerModelsDistribution -import PowerModels -PMs = PowerModels -import InfrastructureModels -IM = InfrastructureModels - -import JuMP, Ipopt - -ipopt_solver = JuMP.with_optimizer(Ipopt.Optimizer) -pm = PMs.instantiate_model(data_model, PMs.ACPPowerModel, PMD.build_mc_opf, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) -sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) - -solution_unmake_pu!(sol["solution"], data_model) -solution_identify!(sol["solution"], data_model) -solution_unmap!(sol["solution"], data_model) - -sol["solution"] From 0df6c84b481493882cbba94659045189a71ee66d Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 21 Feb 2020 02:21:12 +0100 Subject: [PATCH 015/224] forgotten bounds --- src/form/apo.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/form/apo.jl b/src/form/apo.jl index ea3ef4efa..ce62f4023 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -107,10 +107,8 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractAPLossLessModels; (t,i,j) = arc s_rating_fr, s_rating_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) - if !(ismissing(s_rating_fr) || ismissing(s_rating_to)) - JuMP.set_lower_bound.(pt[(t,i,j)], -min.(s_rating_fr, s_rating_to)) - JuMP.set_upper_bound.(pt[(t,i,j)], min.(s_rating_fr, s_rating_to)) - end + set_lower_bound.(pt[(t,i,j)], -min.(s_rating_fr, s_rating_to)) + set_upper_bound.(pt[(t,i,j)], min.(s_rating_fr, s_rating_to)) end end From 2d9be014403954787e597b89a2be4a4836f5c2c5 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 21 Feb 2020 15:28:56 +0100 Subject: [PATCH 016/224] bound names revert --- src/core/constraint.jl | 8 ++--- src/core/constraint_template.jl | 8 ++--- src/core/data.jl | 54 ++++++++++++++++----------------- src/core/variable.jl | 20 ++++++------ src/form/apo.jl | 54 ++++++++++++++++----------------- src/form/ivr.jl | 22 +++++++------- 6 files changed, 83 insertions(+), 83 deletions(-) diff --git a/src/core/constraint.jl b/src/core/constraint.jl index 9a2f54cad..bb68b72b5 100644 --- a/src/core/constraint.jl +++ b/src/core/constraint.jl @@ -4,11 +4,11 @@ end # Generic thermal limit constraint "" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, n::Int, f_idx, s_rating) +function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, n::Int, f_idx, rate_a) p_fr = _PMs.var(pm, n, :p, f_idx) q_fr = _PMs.var(pm, n, :q, f_idx) - mu_sm_fr = JuMP.@constraint(pm.model, p_fr.^2 + q_fr.^2 .<= s_rating.^2) + mu_sm_fr = JuMP.@constraint(pm.model, p_fr.^2 + q_fr.^2 .<= rate_a.^2) if _PMs.report_duals(pm) _PMs.sol(pm, n, :branch, f_idx[1])[:mu_sm_fr] = mu_sm_fr @@ -16,11 +16,11 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, n::Int, f end "" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractPowerModel, n::Int, t_idx, s_rating) +function constraint_mc_thermal_limit_to(pm::_PMs.AbstractPowerModel, n::Int, t_idx, rate_a) p_to = _PMs.var(pm, n, :p, t_idx) q_to = _PMs.var(pm, n, :q, t_idx) - mu_sm_to = JuMP.@constraint(pm.model, p_to.^2 + q_to.^2 .<= s_rating.^2) + mu_sm_to = JuMP.@constraint(pm.model, p_to.^2 + q_to.^2 .<= rate_a.^2) if _PMs.report_duals(pm) _PMs.sol(pm, n, :branch, t_idx[1])[:mu_sm_to] = mu_sm_to diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index e55524d08..01a1fbbfb 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -389,8 +389,8 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, i::Int; n t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) - if haskey(branch, "s_rating") - constraint_mc_thermal_limit_from(pm, nw, f_idx, branch["s_rating"]) + if haskey(branch, "rate_a") + constraint_mc_thermal_limit_from(pm, nw, f_idx, branch["rate_a"]) end end @@ -402,8 +402,8 @@ function constraint_mc_thermal_limit_to(pm::_PMs.AbstractPowerModel, i::Int; nw: t_bus = branch["t_bus"] t_idx = (i, t_bus, f_bus) - if haskey(branch, "s_rating") - constraint_mc_thermal_limit_to(pm, nw, t_idx, branch["s_rating"]) + if haskey(branch, "rate_a") + constraint_mc_thermal_limit_to(pm, nw, t_idx, branch["rate_a"]) end end diff --git a/src/core/data.jl b/src/core/data.jl index 5f9ff9c75..b69a602bf 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -296,11 +296,11 @@ lower bound on the voltage magnitude of the connected buses. function _calc_branch_current_max(branch::Dict, bus::Dict) bounds = [] - if haskey(branch, "c_rating") - push!(bounds, branch["c_rating"]) + if haskey(branch, "c_rating_a") + push!(bounds, branch["c_rating_a"]) end - if haskey(branch, "s_rating") && haskey(bus, "vmin") - push!(bounds, branch["s_rating"]./bus["vmin"]) + if haskey(branch, "rate_a") && haskey(bus, "vmin") + push!(bounds, branch["rate_a"]./bus["vmin"]) end N = 3 #TODO update for 4-wire @@ -317,13 +317,13 @@ function _calc_branch_current_max_frto(branch::Dict, bus_fr::Dict, bus_to::Dict) bounds_fr = [] bounds_to = [] - if haskey(branch, "c_rating") - push!(bounds_fr, branch["c_rating"]) - push!(bounds_to, branch["c_rating"]) + if haskey(branch, "c_rating_a") + push!(bounds_fr, branch["c_rating_a"]) + push!(bounds_to, branch["c_rating_a"]) end - if haskey(branch, "s_rating") - push!(bounds_fr, branch["s_rating"]./bus_fr["vmin"]) - push!(bounds_to, branch["s_rating"]./bus_to["vmin"]) + if haskey(branch, "rate_a") + push!(bounds_fr, branch["rate_a"]./bus_fr["vmin"]) + push!(bounds_to, branch["rate_a"]./bus_to["vmin"]) end N = 3 #TODO update for 4-wire @@ -340,13 +340,13 @@ function _calc_transformer_power_ub_frto(trans::Dict, bus_fr::Dict, bus_to::Dict bounds_fr = [] bounds_to = [] #TODO redefine transformer bounds - # if haskey(trans, "c_rating") - # push!(bounds_fr, trans["c_rating"].*bus_fr["vmax"]) - # push!(bounds_to, trans["c_rating"].*bus_to["vmax"]) + # if haskey(trans, "c_rating_a") + # push!(bounds_fr, trans["c_rating_a"].*bus_fr["vmax"]) + # push!(bounds_to, trans["c_rating_a"].*bus_to["vmax"]) # end - # if haskey(trans, "s_rating") - # push!(bounds_fr, trans["s_rating"]) - # push!(bounds_to, trans["s_rating"]) + # if haskey(trans, "rate_a") + # push!(bounds_fr, trans["rate_a"]) + # push!(bounds_to, trans["rate_a"]) # end @@ -364,13 +364,13 @@ function _calc_transformer_current_max_frto(trans::Dict, bus_fr::Dict, bus_to::D bounds_fr = [] bounds_to = [] #TODO redefine transformer bounds - # if haskey(trans, "c_rating") - # push!(bounds_fr, trans["c_rating"].*bus_fr["vmax"]) - # push!(bounds_to, trans["c_rating"].*bus_to["vmax"]) + # if haskey(trans, "c_rating_a") + # push!(bounds_fr, trans["c_rating_a"].*bus_fr["vmax"]) + # push!(bounds_to, trans["c_rating_a"].*bus_to["vmax"]) # end - # if haskey(trans, "s_rating") - # push!(bounds_fr, trans["s_rating"]) - # push!(bounds_to, trans["s_rating"]) + # if haskey(trans, "rate_a") + # push!(bounds_fr, trans["rate_a"]) + # push!(bounds_to, trans["rate_a"]) # end @@ -387,11 +387,11 @@ upper bound on the voltage magnitude of the connected buses. function _calc_branch_power_max(branch::Dict, bus::Dict) bounds = [] - if haskey(branch, "c_rating") && haskey(bus, "vmax") - push!(bounds, branch["c_rating"].*bus["vmax"]) + if haskey(branch, "c_rating_a") && haskey(bus, "vmax") + push!(bounds, branch["c_rating_a"].*bus["vmax"]) end - if haskey(branch, "s_rating") - push!(bounds, branch["s_rating"]) + if haskey(branch, "rate_a") + push!(bounds, branch["rate_a"]) end N = 3 #TODO update for 4-wire @@ -501,7 +501,7 @@ function _make_multiconductor!(data::Dict{String,<:Any}, conductors::Real) #rename bounds from PMs to PMD for (_, branch) in data["branch"] if haskey(branch, "rate_a") - branch["s_rating"] = branch["rate_a"] + branch["rate_a"] = branch["rate_a"] delete!(branch, "rate_a") end end diff --git a/src/core/variable.jl b/src/core/variable.jl index bb8b17da6..1ff60e901 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -492,11 +492,11 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractPowerModel; nw::In if bounded for arc in _PMs.ref(pm, nw, :arcs_from_trans) (t,i,j) = arc - s_rating_fr, s_rating_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) - set_lower_bound.(pt[(t,i,j)], -s_rating_fr) - set_upper_bound.(pt[(t,i,j)], s_rating_fr) - set_lower_bound.(pt[(t,j,i)], -s_rating_fr) - set_upper_bound.(pt[(t,j,i)], s_rating_fr) + rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + set_lower_bound.(pt[(t,i,j)], -rate_a_fr) + set_upper_bound.(pt[(t,i,j)], rate_a_fr) + set_lower_bound.(pt[(t,j,i)], -rate_a_fr) + set_upper_bound.(pt[(t,j,i)], rate_a_fr) end end @@ -529,12 +529,12 @@ function variable_mc_transformer_flow_reactive(pm::_PMs.AbstractPowerModel; nw:: if bounded for arc in _PMs.ref(pm, nw, :arcs_from_trans) (t,i,j) = arc - s_rating_fr, s_rating_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) - JuMP.set_lower_bound.(qt[(t,i,j)], -s_rating_fr) - JuMP.set_upper_bound.(qt[(t,i,j)], s_rating_fr) - JuMP.set_lower_bound.(qt[(t,j,i)], -s_rating_fr) - JuMP.set_upper_bound.(qt[(t,j,i)], s_rating_fr) + JuMP.set_lower_bound.(qt[(t,i,j)], -rate_a_fr) + JuMP.set_upper_bound.(qt[(t,i,j)], rate_a_fr) + JuMP.set_lower_bound.(qt[(t,j,i)], -rate_a_fr) + JuMP.set_upper_bound.(qt[(t,j,i)], rate_a_fr) end end diff --git a/src/form/apo.jl b/src/form/apo.jl index ce62f4023..a611ea914 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -105,10 +105,10 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractAPLossLessModels; if bounded for arc in _PMs.ref(pm, nw, :arcs_from_trans) (t,i,j) = arc - s_rating_fr, s_rating_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) - set_lower_bound.(pt[(t,i,j)], -min.(s_rating_fr, s_rating_to)) - set_upper_bound.(pt[(t,i,j)], min.(s_rating_fr, s_rating_to)) + set_lower_bound.(pt[(t,i,j)], -min.(rate_a_fr, rate_a_to)) + set_upper_bound.(pt[(t,i,j)], min.(rate_a_fr, rate_a_to)) end end @@ -157,8 +157,8 @@ end ## From PowerModels -"`-s_rating <= p[f_idx] <= s_rating`" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, s_rating) +"`-rate_a <= p[f_idx] <= rate_a`" +function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, rate_a) cnds = _PMs.conductor_ids(pm, n) ncnds = length(cnds) mu_sm_fr = [] @@ -167,12 +167,12 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n:: p_fr =_PMs.var(pm, n, :p, f_idx)[c] if isa(p_fr, JuMP.VariableRef) && JuMP.has_lower_bound(p_fr) push!(mu_sm_fr,JuMP.LowerBoundRef(p_fr)) - JuMP.lower_bound(p_fr) < -s_rating[c] && JuMP.set_lower_bound(p_fr, -s_rating[c]) + JuMP.lower_bound(p_fr) < -rate_a[c] && JuMP.set_lower_bound(p_fr, -rate_a[c]) if JuMP.has_upper_bound(p_fr) - JuMP.upper_bound(p_fr) > s_rating[c] && JuMP.set_upper_bound(p_fr, s_rating[c]) + JuMP.upper_bound(p_fr) > rate_a[c] && JuMP.set_upper_bound(p_fr, rate_a[c]) end else - push!(mu_sm_fr, JuMP.@constraint(pm.model, p_fr <= s_rating[c])) + push!(mu_sm_fr, JuMP.@constraint(pm.model, p_fr <= rate_a[c])) end end @@ -182,7 +182,7 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n:: end "" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::Int, t_idx, s_rating) +function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::Int, t_idx, rate_a) cnds = _PMs.conductor_ids(pm, n) ncnds = length(cnds) mu_sm_to = [] @@ -191,12 +191,12 @@ function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::In p_to =_PMs.var(pm, n, :p, t_idx)[c] if isa(p_to, JuMP.VariableRef) && JuMP.has_lower_bound(p_to) push!(mu_sm_to, JuMP.LowerBoundRef(p_to)) - JuMP.lower_bound(p_to) < -s_rating[c] && JuMP.set_lower_bound(p_to, -s_rating[c]) + JuMP.lower_bound(p_to) < -rate_a[c] && JuMP.set_lower_bound(p_to, -rate_a[c]) if JuMP.has_upper_bound(p_to) - JuMP.upper_bound(p_to) > s_rating[c] && JuMP.set_upper_bound(p_to, s_rating[c]) + JuMP.upper_bound(p_to) > rate_a[c] && JuMP.set_upper_bound(p_to, rate_a[c]) end else - push!(mu_sm_to, JuMP.@constraint(pm.model, p_to <= s_rating[c])) + push!(mu_sm_to, JuMP.@constraint(pm.model, p_to <= rate_a[c])) end end @@ -206,53 +206,53 @@ function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::In end "" -function constraint_mc_current_limit(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, c_rating) +function constraint_mc_current_limit(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, c_rating_a) p_fr =_PMs.var(pm, n, :p, f_idx) cnds = _PMs.conductor_ids(pm, n) ncnds = length(cnds) for c in 1:ncnds - JuMP.lower_bound(p_fr[c]) < -c_rating[c] && JuMP.set_lower_bound(p_fr[c], -c_rating[c]) - JuMP.upper_bound(p_fr[c]) > c_rating[c] && JuMP.set_upper_bound(p_fr[c], c_rating[c]) + JuMP.lower_bound(p_fr[c]) < -c_rating_a[c] && JuMP.set_lower_bound(p_fr[c], -c_rating_a[c]) + JuMP.upper_bound(p_fr[c]) > c_rating_a[c] && JuMP.set_upper_bound(p_fr[c], c_rating_a[c]) end end "" -function constraint_mc_thermal_limit_from_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, s_rating) +function constraint_mc_thermal_limit_from_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, rate_a) p_fr =_PMs.var(pm, n, :p, f_idx) z =_PMs.var(pm, n, :z_branch, i) - JuMP.@constraint(pm.model, p_fr .<= s_rating.*z) - JuMP.@constraint(pm.model, p_fr .>= -s_rating.*z) + JuMP.@constraint(pm.model, p_fr .<= rate_a.*z) + JuMP.@constraint(pm.model, p_fr .>= -rate_a.*z) end "" -function constraint_mc_thermal_limit_to_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, s_rating) +function constraint_mc_thermal_limit_to_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, rate_a) p_to =_PMs.var(pm, n, :p, t_idx) z =_PMs.var(pm, n, :z_branch, i) - JuMP.@constraint(pm.model, p_to .<= s_rating.*z) - JuMP.@constraint(pm.model, p_to .>= -s_rating.*z) + JuMP.@constraint(pm.model, p_to .<= rate_a.*z) + JuMP.@constraint(pm.model, p_to .>= -rate_a.*z) end "" -function constraint_mc_thermal_limit_from_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, s_rating) +function constraint_mc_thermal_limit_from_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, rate_a) p_fr =_PMs.var(pm, n, :p_ne, f_idx) z =_PMs.var(pm, n, :branch_ne, i) - JuMP.@constraint(pm.model, p_fr .<= s_rating.*z) - JuMP.@constraint(pm.model, p_fr .>= -s_rating.*z) + JuMP.@constraint(pm.model, p_fr .<= rate_a.*z) + JuMP.@constraint(pm.model, p_fr .>= -rate_a.*z) end "" -function constraint_mc_thermal_limit_to_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, s_rating) +function constraint_mc_thermal_limit_to_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, rate_a) p_to =_PMs.var(pm, n, :p_ne, t_idx) z =_PMs.var(pm, n, :branch_ne, i) - JuMP.@constraint(pm.model, p_to .<= s_rating.*z) - JuMP.@constraint(pm.model, p_to .>= -s_rating.*z) + JuMP.@constraint(pm.model, p_to .<= rate_a.*z) + JuMP.@constraint(pm.model, p_to .>= -rate_a.*z) end diff --git a/src/form/ivr.jl b/src/form/ivr.jl index cb5839109..021a44de1 100644 --- a/src/form/ivr.jl +++ b/src/form/ivr.jl @@ -262,8 +262,8 @@ function constraint_mc_current_balance_load(pm::_PMs.AbstractIVRModel, n::Int, i end end -"`p[f_idx]^2 + q[f_idx]^2 <= s_rating^2`" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractIVRModel, n::Int, f_idx, s_rating) +"`p[f_idx]^2 + q[f_idx]^2 <= rate_a^2`" +function constraint_mc_thermal_limit_from(pm::_PMs.AbstractIVRModel, n::Int, f_idx, rate_a) (l, f_bus, t_bus) = f_idx vr = _PMs.var(pm, n, :vr, f_bus) @@ -275,12 +275,12 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractIVRModel, n::Int, f_i ncnds = length(cnds) for c in cnds - JuMP.@NLconstraint(pm.model, (vr[c]^2 + vi[c]^2)*(crf[c]^2 + cif[c]^2) <= s_rating[c]^2) + JuMP.@NLconstraint(pm.model, (vr[c]^2 + vi[c]^2)*(crf[c]^2 + cif[c]^2) <= rate_a[c]^2) end end -"`p[t_idx]^2 + q[t_idx]^2 <= s_rating^2`" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractIVRModel, n::Int, t_idx, s_rating) +"`p[t_idx]^2 + q[t_idx]^2 <= rate_a^2`" +function constraint_mc_thermal_limit_to(pm::_PMs.AbstractIVRModel, n::Int, t_idx, rate_a) (l, t_bus, f_bus) = t_idx vr = _PMs.var(pm, n, :vr, t_bus) @@ -292,16 +292,16 @@ function constraint_mc_thermal_limit_to(pm::_PMs.AbstractIVRModel, n::Int, t_idx ncnds = length(cnds) for c in cnds - JuMP.@NLconstraint(pm.model, (vr[c]^2 + vi[c]^2)*(crt[c]^2 + cit[c]^2) <= s_rating[c]^2) + JuMP.@NLconstraint(pm.model, (vr[c]^2 + vi[c]^2)*(crt[c]^2 + cit[c]^2) <= rate_a[c]^2) end end """ Bounds the current magnitude at both from and to side of a branch -`cr[f_idx]^2 + ci[f_idx]^2 <= c_rating^2` -`cr[t_idx]^2 + ci[t_idx]^2 <= c_rating^2` +`cr[f_idx]^2 + ci[f_idx]^2 <= c_rating_a^2` +`cr[t_idx]^2 + ci[t_idx]^2 <= c_rating_a^2` """ -function constraint_mc_current_limit(pm::_PMs.AbstractIVRModel, n::Int, f_idx, c_rating) +function constraint_mc_current_limit(pm::_PMs.AbstractIVRModel, n::Int, f_idx, c_rating_a) (l, f_bus, t_bus) = f_idx t_idx = (l, t_bus, f_bus) @@ -311,8 +311,8 @@ function constraint_mc_current_limit(pm::_PMs.AbstractIVRModel, n::Int, f_idx, c crt = _PMs.var(pm, n, :cr, t_idx) cit = _PMs.var(pm, n, :ci, t_idx) - JuMP.@constraint(pm.model, crf.^2 + cif.^2 .<= c_rating.^2) - JuMP.@constraint(pm.model, crt.^2 + cit.^2 .<= c_rating.^2) + JuMP.@constraint(pm.model, crf.^2 + cif.^2 .<= c_rating_a.^2) + JuMP.@constraint(pm.model, crt.^2 + cit.^2 .<= c_rating_a.^2) end """ From 1d97e08672fa6eb2074e7f00e9cdeec00ba6624e Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 21 Feb 2020 16:39:16 +0100 Subject: [PATCH 017/224] minor fixs --- src/core/data.jl | 10 +--------- test/data/opendss/case3_delta_gens.dss | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index b69a602bf..55d47a783 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -273,7 +273,7 @@ end Returns a current magnitude bound for the generators. """ function _calc_gen_current_max(gen::Dict, bus::Dict) - if all([haskey(gen, "pmax") for prop in ["pmax", "pmin", "qmax", "qmin"]]) && haskey(bus, "vmin") + if all([haskey(gen, prop) for prop in ["pmax", "pmin", "qmax", "qmin"]]) && haskey(bus, "vmin") pabsmax = max.(abs.(gen["pmin"]), abs.(gen["pmax"])) qabsmax = max.(abs.(gen["qmin"]), abs.(gen["qmax"])) smax = sqrt.(pabsmax.^2 + qabsmax.^2) @@ -497,14 +497,6 @@ function _make_multiconductor!(data::Dict{String,<:Any}, conductors::Real) for (_, load) in data["gen"] load["conn"] = "wye" end - - #rename bounds from PMs to PMD - for (_, branch) in data["branch"] - if haskey(branch, "rate_a") - branch["rate_a"] = branch["rate_a"] - delete!(branch, "rate_a") - end - end end diff --git a/test/data/opendss/case3_delta_gens.dss b/test/data/opendss/case3_delta_gens.dss index 5f4ebd496..9480c283a 100644 --- a/test/data/opendss/case3_delta_gens.dss +++ b/test/data/opendss/case3_delta_gens.dss @@ -20,7 +20,7 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length=0.01 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=0.01 ! 100 ft +New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=0.008 ! 100 ft !Loads - single phase From eef3854726181516a88d04092e7bbbc6e760c62a Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Sun, 23 Feb 2020 17:10:08 +0100 Subject: [PATCH 018/224] David's comments --- src/core/variable.jl | 64 +++++++++++++++--------------- src/form/bf_mx.jl | 8 ++-- test/data/opendss/test2_master.dss | 2 +- test/opf_bf.jl | 2 +- test/runtests.jl | 2 +- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/core/variable.jl b/src/core/variable.jl index 1ff60e901..bde977daa 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -41,10 +41,10 @@ function variable_mc_voltage_magnitude(pm::_PMs.AbstractPowerModel; nw::Int=pm.c if bounded for (i,bus) in _PMs.ref(pm, nw, :bus) if haskey(bus, "vmin") - JuMP.set_lower_bound.(vm[i], bus["vmin"]) + set_lower_bound.(vm[i], bus["vmin"]) end if haskey(bus, "vmax") - JuMP.set_upper_bound.(vm[i], bus["vmax"]) + set_upper_bound.(vm[i], bus["vmax"]) end end end @@ -66,8 +66,8 @@ function variable_mc_voltage_real(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, b if bounded for (i,bus) in _PMs.ref(pm, nw, :bus) if haskey(bus, "vmax") - JuMP.set_lower_bound.(vr[i], -bus["vmax"]) - JuMP.set_upper_bound.(vr[i], bus["vmax"]) + set_lower_bound.(vr[i], -bus["vmax"]) + set_upper_bound.(vr[i], bus["vmax"]) end end end @@ -89,8 +89,8 @@ function variable_mc_voltage_imaginary(pm::_PMs.AbstractPowerModel; nw::Int=pm.c if bounded for (i,bus) in _PMs.ref(pm, nw, :bus) if haskey(bus, "vmax") - JuMP.set_lower_bound.(vi[i], -bus["vmax"]) - JuMP.set_upper_bound.(vi[i], bus["vmax"]) + set_lower_bound.(vi[i], -bus["vmax"]) + set_upper_bound.(vi[i], bus["vmax"]) end end end @@ -127,11 +127,11 @@ function variable_mc_branch_flow_active(pm::_PMs.AbstractPowerModel; nw::Int=pm. for (l,branch) in _PMs.ref(pm, nw, :branch) if haskey(branch, "pf_start") f_idx = (l, branch["f_bus"], branch["t_bus"]) - JuMP.set_start_value(p[f_idx], branch["pf_start"]) + set_start_value(p[f_idx], branch["pf_start"]) end if haskey(branch, "pt_start") t_idx = (l, branch["t_bus"], branch["f_bus"]) - JuMP.set_start_value(p[t_idx], branch["pt_start"]) + set_start_value(p[t_idx], branch["pt_start"]) end end @@ -152,19 +152,19 @@ function variable_mc_branch_flow_reactive(pm::_PMs.AbstractPowerModel; nw::Int=p if bounded for (l,i,j) in _PMs.ref(pm, nw, :arcs) smax = _calc_branch_power_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) - JuMP.set_upper_bound.(q[(l,i,j)], smax) - JuMP.set_lower_bound.(q[(l,i,j)], -smax) + set_upper_bound.(q[(l,i,j)], smax) + set_lower_bound.(q[(l,i,j)], -smax) end end for (l,branch) in _PMs.ref(pm, nw, :branch) if haskey(branch, "qf_start") f_idx = (l, branch["f_bus"], branch["t_bus"]) - JuMP.set_start_value(q[f_idx], branch["qf_start"]) + set_start_value(q[f_idx], branch["qf_start"]) end if haskey(branch, "qt_start") t_idx = (l, branch["t_bus"], branch["f_bus"]) - JuMP.set_start_value(q[t_idx], branch["qt_start"]) + set_start_value(q[t_idx], branch["qt_start"]) end end @@ -355,10 +355,10 @@ function variable_mc_voltage_magnitude_sqr(pm::_PMs.AbstractPowerModel; nw::Int= if bounded for (i,bus) in _PMs.ref(pm, nw, :bus) if haskey(bus, "vmin") - JuMP.set_lower_bound.(w[i], bus["vmin"].^2) + set_lower_bound.(w[i], bus["vmin"].^2) end if haskey(bus, "vmax") - JuMP.set_upper_bound.(w[i], bus["vmax"].^2) + set_upper_bound.(w[i], bus["vmax"].^2) end end end @@ -393,10 +393,10 @@ function variable_mc_storage_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, for i in _PMs.ids(pm, nw, :storage) if !isinf(flow_lb[i]) - JuMP.set_lower_bound(ps[i][c], flow_lb[i]) + set_lower_bound(ps[i][c], flow_lb[i]) end if !isinf(flow_ub[i]) - JuMP.set_upper_bound(ps[i][c], flow_ub[i]) + set_upper_bound(ps[i][c], flow_ub[i]) end end end @@ -421,10 +421,10 @@ function variable_mc_storage_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm.cn for i in _PMs.ids(pm, nw, :storage) if !isinf(flow_lb[i]) - JuMP.set_lower_bound(qs[i][c], flow_lb[i]) + set_lower_bound(qs[i][c], flow_lb[i]) end if !isinf(flow_ub[i]) - JuMP.set_upper_bound(qs[i][c], flow_ub[i]) + set_upper_bound(qs[i][c], flow_ub[i]) end end end @@ -503,11 +503,11 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractPowerModel; nw::In for (l,transformer) in _PMs.ref(pm, nw, :transformer) if haskey(transformer, "pf_start") f_idx = (l, transformer["f_bus"], transformer["t_bus"]) - JuMP.set_start_value(pt[f_idx], branch["pf_start"]) + set_start_value(pt[f_idx], branch["pf_start"]) end if haskey(transformer, "pt_start") t_idx = (l, transformer["t_bus"], transformer["f_bus"]) - JuMP.set_start_value(pt[t_idx], transformer["pt_start"]) + set_start_value(pt[t_idx], transformer["pt_start"]) end end @@ -531,21 +531,21 @@ function variable_mc_transformer_flow_reactive(pm::_PMs.AbstractPowerModel; nw:: (t,i,j) = arc rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) - JuMP.set_lower_bound.(qt[(t,i,j)], -rate_a_fr) - JuMP.set_upper_bound.(qt[(t,i,j)], rate_a_fr) - JuMP.set_lower_bound.(qt[(t,j,i)], -rate_a_fr) - JuMP.set_upper_bound.(qt[(t,j,i)], rate_a_fr) + set_lower_bound.(qt[(t,i,j)], -rate_a_fr) + set_upper_bound.(qt[(t,i,j)], rate_a_fr) + set_lower_bound.(qt[(t,j,i)], -rate_a_fr) + set_upper_bound.(qt[(t,j,i)], rate_a_fr) end end for (l,transformer) in _PMs.ref(pm, nw, :transformer) if haskey(transformer, "qf_start") f_idx = (l, transformer["f_bus"], transformer["t_bus"]) - JuMP.set_start_value(qt[f_idx], branch["qf_start"]) + set_start_value(qt[f_idx], branch["qf_start"]) end if haskey(transformer, "qt_start") t_idx = (l, transformer["t_bus"], transformer["f_bus"]) - JuMP.set_start_value(qt[t_idx], transformer["qt_start"]) + set_start_value(qt[t_idx], transformer["qt_start"]) end end @@ -568,8 +568,8 @@ function variable_mc_oltc_tap(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bound ) for i in p_oltc_ids) if bounded for tr_id in p_oltc_ids, p in 1:nph - JuMP.set_lower_bound(_PMs.var(pm, nw)[:tap][tr_id][p], _PMs.ref(pm, nw, :transformer, tr_id, "tm_min")[p]) - JuMP.set_upper_bound(_PMs.var(pm, nw)[:tap][tr_id][p], _PMs.ref(pm, nw, :transformer, tr_id, "tm_max")[p]) + set_lower_bound(_PMs.var(pm, nw)[:tap][tr_id][p], _PMs.ref(pm, nw, :transformer, tr_id, "tm_min")[p]) + set_upper_bound(_PMs.var(pm, nw)[:tap][tr_id][p], _PMs.ref(pm, nw, :transformer, tr_id, "tm_max")[p]) end end @@ -805,10 +805,10 @@ function variable_mc_generation_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.c if bounded for (i,gen) in _PMs.ref(pm, nw, :gen) if haskey(gen, "pmin") - JuMP.set_lower_bound.(pg[i], gen["pmin"]) + set_lower_bound.(pg[i], gen["pmin"]) end if haskey(gen, "pmax") - JuMP.set_upper_bound.(pg[i], gen["pmax"]) + set_upper_bound.(pg[i], gen["pmax"]) end end end @@ -832,10 +832,10 @@ function variable_mc_generation_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm if bounded for (i,gen) in _PMs.ref(pm, nw, :gen) if haskey(gen, "qmin") - JuMP.set_lower_bound.(qg[i], gen["qmin"]) + set_lower_bound.(qg[i], gen["qmin"]) end if haskey(gen, "qmax") - JuMP.set_upper_bound.(qg[i], gen["qmax"]) + set_upper_bound.(qg[i], gen["qmax"]) end end end diff --git a/src/form/bf_mx.jl b/src/form/bf_mx.jl index 4290227d1..5ab758489 100644 --- a/src/form/bf_mx.jl +++ b/src/form/bf_mx.jl @@ -224,10 +224,10 @@ function variable_mc_generation_power(pm::SDPUBFKCLMXModel; nw::Int=pm.cnw, boun #overwrite diagonal bounds for (id, gen) in _PMs.ref(pm, nw, :gen) for c in _PMs.conductor_ids(pm, nw) - JuMP.set_lower_bound(Pg[id][c,c], gen["pmin"][c]) - JuMP.set_upper_bound(Pg[id][c,c], gen["pmax"][c]) - JuMP.set_lower_bound(Qg[id][c,c], gen["qmin"][c]) - JuMP.set_upper_bound(Qg[id][c,c], gen["qmax"][c]) + set_lower_bound(Pg[id][c,c], gen["pmin"][c]) + set_upper_bound(Pg[id][c,c], gen["pmax"][c]) + set_lower_bound(Qg[id][c,c], gen["qmin"][c]) + set_upper_bound(Qg[id][c,c], gen["qmax"][c]) end end # save references diff --git a/test/data/opendss/test2_master.dss b/test/data/opendss/test2_master.dss index 5dc5298e1..963dbd3aa 100644 --- a/test/data/opendss/test2_master.dss +++ b/test/data/opendss/test2_master.dss @@ -15,7 +15,7 @@ New Line.L1-2 bus1=b3-1.2 bus2=b4.2 length=0.032175613 units=km Linecode=lc1 New Line.L2 bus1=b5 bus2=b6_check-chars length=0.013516796 units=none linecode=lc/2 New "Line.L3" phases=3 bus1=b7.1.2.3 bus2=b9.1.2.3 linecode=300 normamps=400 emergamps=600 faultrate=0.1 pctperm=20 repair=0 length=2.58 New "Line._L4" phases=3 bus1=b8.1.2.3 bus2=b10.1.2.3 linecode=300 normamps=400 emergamps=600 faultrate=0.1 pctperm=20 repair=0 length=1.73 switch=y -New line.l5 phases=3 bus1=_b2.1.2.3 bus2=b7.1.2.3 linecode=lc8 +New line.l5 phases=3 bus1=_b2.1.2.3.0 bus2=b7.1.2.3.0 linecode=lc8 New line.l6 phases=3 bus1=b1.1.2.3 bus2=b10.1.2.3 linecode=lc9 new line.l7 bus1=_b2 bus2=b10 like=L2 linecode=lc10 diff --git a/test/opf_bf.jl b/test/opf_bf.jl index c0451f064..ecdefb8cb 100644 --- a/test/opf_bf.jl +++ b/test/opf_bf.jl @@ -11,7 +11,7 @@ @test isapprox(result["objective"], 44880; atol = 1e0) # @test isapprox(result["solution"]["bus"]["3"]["vm"], 0.911466*[1,1,1]; atol = 1e-3) vm = calc_vm_w(result, "3") - @test isapprox(vm, 0.910445*[1,1,1]; atol = 1e-3) + @test isapprox(vm, 0.911466*[1,1,1]; atol = 1e-3) end diff --git a/test/runtests.jl b/test/runtests.jl index 2b190e7cf..8cea69752 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,7 +26,7 @@ using LinearAlgebra pms_path = joinpath(dirname(pathof(PowerModels)), "..") pmd_path = joinpath(dirname(pathof(PowerModelsDistribution)), "..") -ipopt_solver = with_optimizer(Ipopt.Optimizer, tol=1e-7, print_level=0) +ipopt_solver = with_optimizer(Ipopt.Optimizer, tol=1e-6, print_level=0) cbc_solver = with_optimizer(Cbc.Optimizer, logLevel=0) scs_solver = with_optimizer(SCS.Optimizer, max_iters=20000, eps=1e-5, alpha=0.4, verbose=0) juniper_solver = with_optimizer(Juniper.Optimizer, nl_solver=with_optimizer(Ipopt.Optimizer, tol=1e-4, print_level=0), mip_solver=cbc_solver, log_levels=[]) From fddf8c00a553a6ae448f7a79f40cf48e0ab00b5f Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Sun, 23 Feb 2020 18:56:33 +0100 Subject: [PATCH 019/224] merged master --- src/PowerModelsDistribution.jl | 10 + src/io/data_model.md | 188 +++++ src/io/data_model_components.jl | 691 +++++++++++++++++ src/io/data_model_test.jl | 161 ++++ src/io/data_model_util.jl | 164 ++++ src/io/opendss_dm.jl | 1231 +++++++++++++++++++++++++++++++ 6 files changed, 2445 insertions(+) create mode 100644 src/io/data_model.md create mode 100644 src/io/data_model_components.jl create mode 100644 src/io/data_model_test.jl create mode 100644 src/io/data_model_util.jl create mode 100644 src/io/opendss_dm.jl diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 80ee7ff68..2b1af1fe1 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -39,12 +39,22 @@ module PowerModelsDistribution include("core/constraint_template.jl") include("core/relaxation_scheme.jl") + include("io/common_dm.jl") + include("io/opendss_dm.jl") + include("io/common.jl") include("io/json.jl") include("io/dss_parse.jl") include("io/dss_structs.jl") include("io/opendss.jl") + #include("io/common_dm.jl") + #include("io/opendss_dm.jl") + include("io/data_model_components.jl") + include("core/data_model_mapping.jl") + include("core/data_model_pu.jl") + include("io/data_model_util.jl") + include("prob/mld.jl") include("prob/opf.jl") include("prob/opf_iv.jl") diff --git a/src/io/data_model.md b/src/io/data_model.md new file mode 100644 index 000000000..9321a962d --- /dev/null +++ b/src/io/data_model.md @@ -0,0 +1,188 @@ + +# Shared patterns +- Each component has a unique (amonst components of the same type) identifier `id`. +- Everything is defined in SI units, except when a base is explicitly mentioned in the description. +- The default sometimes only mentions the default element, not the default value (e.g. `true` and not `fill(fill(true, 3), 3)`) + +# Data model + +## Bus + +The data model below allows us to include buses of arbitrary many terminals (i.e., more than the usual four). This would be useful for +- underground lines with multiple neutrals which are not joined at every bus; +- distribution lines that carry several conventional lines in parallel (see for example the quad circuits in NEVTestCase). + +name | default | type | description +------|----------|-----|----------- +terminals|[1,2,3,4]|Vector +vm_max | / | Vector | maximum conductor-to-ground voltage magnitude +vm_min | / | Vector | minimum conductor-to-ground voltage magnitude +vm_cd_max | / | Vector{Tuple} | e.g. [(1,2,210)] means \|U1-U2\|>210 +vm_cd_min | / | Vector{Tuple} | e.g. [(1,2,230)] means \|U1-U2\|<230 +grounded | [] | Vector | a list of terminals which are grounded +rg | [] | Vector | resistance of each defined grounding +xg | [] | Vector | reactance of each defined grounding + +The tricky part is how to encode bounds for these type of buses. The most general is defining a list of three-tupples. Take for example a typical bus in a three-phase, four-wire network, where `terminals=[a,b,c,n]`. Such a bus might have +- phase-to-neutral bounds `vm_pn_max=250`, `vm_pn_min=210` +- and phase-to-phase bounds `vm_pp_max=440`, `vm_pp_min=360 +`. + +We can then define this equivalently as +- `vm_cd_max = [(a,n,250), (b,n,250), (c,n,250), (a,b,440), (b,c,440), (c,a,440)]` +- `vm_cd_min = [(a,n,210), (b,n,210), (c,n,210), (a,b,360), (b,c,360), (c,a,360)]` + +Since this might be confusing for novice users, we also allow the user to define bounds through the following properties. + + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +phases | [1,2,3] | Vector +neutral | 4 | | maximum conductor-to-ground voltage magnitude +vm_pn_max | / | Real | maximum phase-to-neutral voltage magnitude for all phases +vm_pn_min | / | Real | minimum phase-to-neutral voltage magnitude for all phases +vm_pp_max | / | Real | maximum phase-to-phase voltage magnitude for all phases +vm_pp_min | / | Real | minimum phase-to-phase voltage magnitude for all phases + +## Line + +This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` and `g_to`; when these properties are additionally specified, they overwrite the one supplied through the linecode. + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +f_bus | / | | +f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects +t_bus | / | | +t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects +linecode | / | | a linecode +rs | / | | series resistance matrix, size of n_conductors x n_conductors +xs | / | | series reactance matrix, size of n_conductors x n_conductors +g_fr | / | | from-side conductance +b_fr | / | | from-side susceptance +g_to | / | | to-side conductance +b_to | / | | to-side susceptance +c_rating | / | | symmetrically applicable current rating +s_rating | / | | symmetrically applicable power rating + +## Linecode + +- Should the linecode also include a `c_rating` and/`s_rating`? + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +rs | / | | series resistance matrix +xs | / | | series reactance matrix n_conductors +g_fr | / | | from-side conductance +b_fr | / | | from-side susceptance +g_to | / | | to-side conductance +b_to | / | | to-side susceptance + +## Shunt + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +bus | / | | +connections | / | | +g | / | | conductance, size should be \|connections\|x\|connections\| +b | / | | susceptance, size should be \|connections\|x\|connections\| + +## Capacitor + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +bus | / | | +connections | / | | +qd_ref | / | | conductance, size should be \|connections\|x\|connections\| +vnom | / | | conductance, size should be \|connections\|x\|connections\| + +## Load + +name | default | type | description +------|----------|----|----------- +id | / | | unique identifier +bus | / | | +connections | / | | +configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral +model | / | | indicates the type of voltage-dependency + +### `model=constant_power` + +name | default | type | description +------|----------|----|----------- +pd | / | Vector{Real} | +qd | / | Vector{Real} | + +### `model=constant_current/impedance` + +name | default | type | description +------|----------|----|----------- +pd_ref | / | Vector{Real} | +qd_ref | / | Vector{Real} | +vnom | / | Real | + +### `model=exponential` + +name | default | type | description +------|----------|----|----------- +pd_ref | / | Vector{Real} | +qd_ref | / | Vector{Real} | +vnom | / | Real | +exp_p | / | Vector{Real} | +exp_q | / | Vector{Real} | + +## Generator + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +bus | / | | +connections | / | | +configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral +pg_min | / | | lower bound on active power generation per phase +pg_max | / | | upper bound on active power generation per phase +qg_min | / | | lower bound on reactive power generation per phase +qg_max | / | | upper bound on reactive power generation per phase + +## AL2W Transformer +These are transformers are assymetric (A), lossless (L) and two-winding (2W). Assymetric refers to the fact that the secondary is always has a `wye` configuration, whilst the primary can be `delta`. + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +n_phases | size(rs)[1] | Int>0 | number of phases +f_bus | / | | +f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects +t_bus | / | | +t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects +configuration | / | {wye, delta} | for the from-side; the to-side is always connected in wye +tm_nom | / | Real | nominal tap ratio for the transformer +tm_max | / | Vector | maximum tap ratio for each phase (base=`tm_nom`) +tm_min | / | Vector | minimum tap ratio for each phase (base=`tm_nom`) +tm_set | fill(1.0, `n_phases`) | Vector | set tap ratio for each phase (base=`tm_nom`) +tm_fix | fill(true, `n_phases`) | Vector | indicates for each phase whether the tap ratio is fixed + +TODO: add tm stuff + +## Transformer +These are n-winding, n-phase, lossy transformers. Note that most properties are now Vectors (or Vectors of Vectors), indexed over the windings. + +name | default | type | description +------|----------|-----|----------- +id | / | | unique identifier +n_phases | size(rs)[1] | Int>0 | number of phases +n_windings | size(rs)[1] | Int>0 | number of windings +bus | / | Vector | list of bus for each winding +connections | | Vector{Vector} | list of connection for each winding +configurations | | Vector{{wye, delta}} | list of configuration for each winding +xsc | 0.0 | Vector | list of short-circuit reactances between each pair of windings; enter as a list of the upper-triangle elements +rs | 0.0 | Vector | list of the winding resistance for each winding +tm_nom | / | Vector{Real} | nominal tap ratio for the transformer +tm_max | / | Vector{Vector} | maximum tap ratio for each winding and phase (base=`tm_nom`) +tm_min | / | Vector{Vector} | minimum tap ratio for for each winding and phase (base=`tm_nom`) +tm_set | 1.0 | Vector{Vector} | set tap ratio for each winding and phase (base=`tm_nom`) +tm_fix | true | Vector{Vector} | indicates for each winding and phase whether the tap ratio is fixed diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl new file mode 100644 index 000000000..6d8faecaf --- /dev/null +++ b/src/io/data_model_components.jl @@ -0,0 +1,691 @@ +#TODO +# Can buses in a voltage zone have different terminals? +# Add current/power bounds to data model + + +function copy_kwargs_to_dict_if_present!(dict, kwargs, args) + for arg in args + if haskey(kwargs, arg) + dict[string(arg)] = kwargs[arg] + end + end +end + +function add_kwarg!(dict, kwargs, name, default) + if haskey(kwargs, name) + dict[string(name)] = kwargs[name] + else + dict[string(name)] = default + end +end + +function component_dict_from_list!(list) + dict = Dict{String, Any}() + for comp_dict in list + dict[comp_dict["id"]] = comp_dict + end + return dict +end + +REQUIRED_FIELDS = Dict{Symbol, Any}() +DTYPES = Dict{Symbol, Any}() +CHECKS = Dict{Symbol, Any}() + +function check_dtypes(dict, dtypes, comp_type, id) + for key in keys(dict) + symb = Symbol(key) + if haskey(dtypes, symb) + @assert(isa(dict[key], dtypes[symb]), "$comp_type $id: the property $key should be a $(dtypes[symb]), not a $(typeof(dict[key])).") + else + #@assert(false, "$comp_type $id: the property $key is unknown.") + end + end +end + + +function add!(data_model, comp_type, comp_dict) + @assert(haskey(comp_dict, "id"), "The component does not have an id defined.") + id = comp_dict["id"] + if !haskey(data_model, comp_type) + data_model[comp_type] = Dict{Any, Any}() + else + @assert(!haskey(data_model[comp_type], id), "There is already a $comp_type with id $id.") + end + data_model[comp_type][id] = comp_dict +end + +function _add_unused_kwargs!(comp_dict, kwargs) + for (prop, val) in kwargs + if !haskey(comp_dict, "$prop") + comp_dict["$prop"] = val + end + end +end + +function check_data_model(data) + for component in keys(DTYPES) + if haskey(data, string(component)) + for (id, comp_dict) in data[string(component)] + if haskey(REQUIRED_FIELDS, component) + for field in REQUIRED_FIELDS[component] + @assert(haskey(comp_dict, string(field)), "The property \'$field\' is missing for $component $id.") + end + end + if haskey(DTYPES, component) + check_dtypes(comp_dict, DTYPES[component], component, id) + end + if haskey(CHECKS, component) + CHECKS[component](data, comp_dict) + end + end + end + end +end + + +function create_data_model(; kwargs...) + data_model = Dict{String, Any}("settings"=>Dict{String, Any}()) + + add_kwarg!(data_model["settings"], kwargs, :v_var_scalar, 1E3) + + _add_unused_kwargs!(data_model["settings"], kwargs) + + return data_model +end + +# COMPONENTS +#*################# + +DTYPES[:ev] = Dict() +DTYPES[:storage] = Dict() +DTYPES[:pv] = Dict() +DTYPES[:wind] = Dict() +DTYPES[:switch] = Dict() +DTYPES[:shunt] = Dict() +DTYPES[:autotransformer] = Dict() +DTYPES[:synchronous_generator] = Dict() +DTYPES[:zip_load] = Dict() +DTYPES[:grounding] = Dict( + :bus => Any, + :rg => Real, + :xg => Real, +) +DTYPES[:synchronous_generator] = Dict() +DTYPES[:boundary] = Dict() +DTYPES[:meter] = Dict() + + +function _check_same_size(data, keys; context=missing) + size_comp = size(data[string(keys[1])]) + for key in keys[2:end] + @assert(all(size(data[string(key)]).==size_comp), "$context: the property $key should have the same size as $(keys[1]).") + end +end + + +function _check_has_size(data, keys, size_comp; context=missing, allow_missing=true) + for key in keys + if haskey(data, key) || !allow_missing + @assert(all(size(data[string(key)]).==size_comp), "$context: the property $key should have as size $size_comp.") + end + end +end + +function _check_connectivity(data, comp_dict; context=missing) + + if haskey(comp_dict, "f_bus") + # two-port element + _check_bus_and_terminals(data, comp_dict["f_bus"], comp_dict["f_connections"], context) + _check_bus_and_terminals(data, comp_dict["t_bus"], comp_dict["t_connections"], context) + elseif haskey(comp_dict, "bus") + if isa(comp_dict["bus"], Vector) + for i in 1:length(comp_dict["bus"]) + _check_bus_and_terminals(data, comp_dict["bus"][i], comp_dict["connections"][i], context) + end + else + _check_bus_and_terminals(data, comp_dict["bus"], comp_dict["connections"], context) + end + end +end + + +function _check_bus_and_terminals(data, bus_id, terminals, context=missing) + @assert(haskey(data, "bus") && haskey(data["bus"], bus_id), "$context: the bus $bus_id is not defined.") + bus = data["bus"][bus_id] + for t in terminals + @assert(t in bus["terminals"], "$context: bus $(bus["id"]) does not have terminal \'$t\'.") + end +end + + +function _check_has_keys(comp_dict, keys; context=missing) + for key in keys + @assert(haskey(comp_dict, key), "$context: the property $key is missing.") + end +end + +function _check_configuration_infer_dim(comp_dict; context=missing) + conf = comp_dict["configuration"] + @assert(conf in ["delta", "wye"], "$context: the configuration should be \'delta\' or \'wye\', not \'$conf\'.") + return conf=="wye" ? length(comp_dict["connections"])-1 : length(comp_dict["connections"]) +end + + +# linecode + +DTYPES[:linecode] = Dict( + :id => Any, + :rs => Array{<:Real, 2}, + :xs => Array{<:Real, 2}, + :g_fr => Array{<:Real, 2}, + :g_to => Array{<:Real, 2}, + :b_fr => Array{<:Real, 2}, + :b_to => Array{<:Real, 2}, +) + +REQUIRED_FIELDS[:linecode] = keys(DTYPES[:linecode]) + +CHECKS[:linecode] = function check_linecode(data, linecode) + _check_same_size(linecode, [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to]) +end + +function create_linecode(; kwargs...) + linecode = Dict{String,Any}() + + n_conductors = 0 + for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] + if haskey(kwargs, key) + n_conductors = size(kwargs[key])[1] + end + end + add_kwarg!(linecode, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(linecode, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) + + _add_unused_kwargs!(linecode, kwargs) + + return linecode +end + +# line + +DTYPES[:line] = Dict( + :id => Any, + :status => Int, + :f_bus => AbstractString, + :t_bus => AbstractString, + :f_connections => Vector{<:Int}, + :t_connections => Vector{<:Int}, + :linecode => AbstractString, + :length => Real, + :c_rating =>Vector{<:Real}, + :s_rating =>Vector{<:Real}, + :angmin=>Vector{<:Real}, + :angmax=>Vector{<:Real}, + :rs => Array{<:Real, 2}, + :xs => Array{<:Real, 2}, + :g_fr => Array{<:Real, 2}, + :g_to => Array{<:Real, 2}, + :b_fr => Array{<:Real, 2}, + :b_to => Array{<:Real, 2}, +) + +REQUIRED_FIELDS[:line] = [:id, :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length] + +CHECKS[:line] = function check_line(data, line) + i = line["id"] + + # for now, always require a line code + if haskey(line, "linecode") + # line is defined with a linecode + @assert(haskey(line, "length"), "line $i: a line defined through a linecode, should have a length property.") + + linecode_id = line["linecode"] + @assert(haskey(data, "linecode") && haskey(data["linecode"], "$linecode_id"), "line $i: the linecode $linecode_id is not defined.") + linecode = data["linecode"]["$linecode_id"] + + for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + @assert(!haskey(line, key), "line $i: a line with a linecode, should not specify $key; this is already done by the linecode.") + end + + N = size(linecode["rs"])[1] + @assert(length(line["f_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") + @assert(length(line["t_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") + else + # normal line + @assert(!haskey(line, "length"), "line $i: length only makes sense for linees defined through linecodes.") + for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + @assert(haskey(line, key), "line $i: a line without linecode, should specify $key.") + end + end + + _check_connectivity(data, line, context="line $(line["id"])") +end + + +function create_line(; kwargs...) + line = Dict{String,Any}() + + add_kwarg!(line, kwargs, :status, 1) + add_kwarg!(line, kwargs, :f_connections, collect(1:4)) + add_kwarg!(line, kwargs, :t_connections, collect(1:4)) + + N = length(line["f_connections"]) + add_kwarg!(line, kwargs, :angmin, fill(-60/180*pi, N)) + add_kwarg!(line, kwargs, :angmax, fill( 60/180*pi, N)) + + # if no linecode, then populate loss parameters with zero + if !haskey(kwargs, :linecode) + n_conductors = 0 + for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] + if haskey(kwargs, key) + n_conductors = size(kwargs[key])[1] + end + end + add_kwarg!(line, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) + add_kwarg!(line, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) + end + + _add_unused_kwargs!(line, kwargs) + + return line +end + +# Bus + +DTYPES[:bus] = Dict( + :id => Any, + :status => Int, + :bus_type => Int, + :terminals => Array{<:Any}, + :phases => Array{<:Int}, + :neutral => Union{Int, Missing}, + :grounded => Array{<:Any}, + :rg => Array{<:Real}, + :xg => Array{<:Real}, + :vm_pn_min => Real, + :vm_pn_max => Real, + :vm_pp_min => Real, + :vm_pp_max => Real, + :vm_min => Array{<:Real, 1}, + :vm_max => Array{<:Real, 1}, + :vm_fix => Array{<:Real, 1}, + :va_fix => Array{<:Real, 1}, +) + +REQUIRED_FIELDS[:bus] = [:id, :status, :terminals, :grounded, :rg, :xg] + +CHECKS[:bus] = function check_bus(data, bus) + id = bus["id"] + + _check_same_size(bus, ["grounded", "rg", "xg"], context="bus $id") + + N = length(bus["terminals"]) + _check_has_size(bus, ["vm_max", "vm_min", "vm", "va"], N, context="bus $id") + + if haskey(bus, "neutral") + assert(haskey(bus, "phases"), "bus $id: has a neutral, but no phases.") + end +end + +function create_bus(; kwargs...) + bus = Dict{String,Any}() + + add_kwarg!(bus, kwargs, :status, 1) + add_kwarg!(bus, kwargs, :terminals, collect(1:4)) + add_kwarg!(bus, kwargs, :grounded, []) + add_kwarg!(bus, kwargs, :bus_type, 1) + add_kwarg!(bus, kwargs, :rg, Array{Float64, 1}()) + add_kwarg!(bus, kwargs, :xg, Array{Float64, 1}()) + + _add_unused_kwargs!(bus, kwargs) + + return bus +end + +# Load + +DTYPES[:load] = Dict( + :id => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :configuration => String, + :model => String, + :pd => Array{<:Real, 1}, + :qd => Array{<:Real, 1}, + :pd_ref => Array{<:Real, 1}, + :qd_ref => Array{<:Real, 1}, + :vnom => Array{<:Real, 1}, + :alpha => Array{<:Real, 1}, + :beta => Array{<:Real, 1}, +) + +REQUIRED_FIELDS[:load] = [:id, :status, :bus, :connections, :configuration] + +CHECKS[:load] = function check_load(data, load) + id = load["id"] + + N = _check_configuration_infer_dim(load; context="load $id") + + model = load["model"] + @assert(model in ["constant_power", "constant_impedance", "constant_current", "exponential"]) + if model=="constant_power" + _check_has_keys(load, ["pd", "qd"], context="load $id, $model:") + _check_has_size(load, ["pd", "qd"], N, context="load $id, $model:") + elseif model=="exponential" + _check_has_keys(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], context="load $id, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], N, context="load $id, $model:") + else + _check_has_keys(load, ["pd_ref", "qd_ref", "vnom"], context="load $id, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vnom"], N, context="load $id, $model:") + end + + _check_connectivity(data, load; context="load $id") +end + + +function create_load(; kwargs...) + load = Dict{String,Any}() + + add_kwarg!(load, kwargs, :status, 1) + add_kwarg!(load, kwargs, :configuration, "wye") + add_kwarg!(load, kwargs, :connections, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) + add_kwarg!(load, kwargs, :model, "constant_power") + if load["model"]=="constant_power" + add_kwarg!(load, kwargs, :pd, fill(0.0, 3)) + add_kwarg!(load, kwargs, :qd, fill(0.0, 3)) + else + add_kwarg!(load, kwargs, :pd_ref, fill(0.0, 3)) + add_kwarg!(load, kwargs, :qd_ref, fill(0.0, 3)) + end + + _add_unused_kwargs!(load, kwargs) + + return load +end + +# generator + +DTYPES[:generator] = Dict( + :id => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :configuration => String, + :cost => Vector{<:Real}, + :pg => Array{<:Real, 1}, + :qg => Array{<:Real, 1}, + :pg_min => Array{<:Real, 1}, + :pg_max => Array{<:Real, 1}, + :qg_min => Array{<:Real, 1}, + :qg_max => Array{<:Real, 1}, +) + +REQUIRED_FIELDS[:generator] = [:id, :status, :bus, :connections] + +CHECKS[:generator] = function check_generator(data, generator) + id = generator["id"] + + N = _check_configuration_infer_dim(generator; context="generator $id") + _check_has_size(generator, ["pd", "qd", "pd_min", "pd_max", "qd_min", "qd_max"], N, context="generator $id") + + _check_connectivity(data, generator; context="generator $id") +end + +function create_generator(; kwargs...) + generator = Dict{String,Any}() + + add_kwarg!(generator, kwargs, :status, 1) + add_kwarg!(generator, kwargs, :configuration, "wye") + add_kwarg!(generator, kwargs, :cost, [1.0, 0.0]*1E-3) + add_kwarg!(generator, kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) + + _add_unused_kwargs!(generator, kwargs) + + return generator +end + + +# Transformer, n-windings three-phase lossy + + +DTYPES[:transformer_nw] = Dict( + :id => Any, + :status => Int, + :bus => Array{<:AbstractString, 1}, + :connections => Vector, + :vnom => Array{<:Real, 1}, + :snom => Array{<:Real, 1}, + :configuration => Array{String, 1}, + :polarity => Array{Bool, 1}, + :xsc => Array{<:Real, 1}, + :rs => Array{<:Real, 1}, + :noloadloss => Real, + :imag => Real, + :tm_fix => Array{Array{Bool, 1}, 1}, + :tm => Array{<:Array{<:Real, 1}, 1}, + :tm_min => Array{<:Array{<:Real, 1}, 1}, + :tm_max => Array{<:Array{<:Real, 1}, 1}, + :tm_step => Array{<:Array{<:Real, 1}, 1}, +) + +REQUIRED_FIELDS[:transformer_nw] = keys(DTYPES[:transformer_nw]) + + +CHECKS[:transformer_nw] = function check_transformer_nw(data, trans) + id = trans["id"] + nrw = length(trans["bus"]) + _check_has_size(trans, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_min", "tm_max", "tm_step"], nrw, context="trans $id") + @assert(length(trans["xsc"])==(nrw^2-nrw)/2) + + nphs = [] + for w in 1:nrw + @assert(trans["configuration"][w] in ["wye", "delta"]) + conf = trans["configuration"][w] + conns = trans["connections"][w] + nph = conf=="wye" ? length(conns)-1 : length(conns) + @assert(all(nph.==nphs), "transformer $id: winding $w has a different number of phases than the previous ones.") + push!(nphs, nph) + #TODO check length other properties + end + + _check_connectivity(data, trans; context="transformer_nw $id") +end + + +function create_transformer_nw(; kwargs...) + trans = Dict{String,Any}() + + @assert(haskey(kwargs, :bus), "You have to specify at least the buses.") + n_windings = length(kwargs[:bus]) + add_kwarg!(trans, kwargs, :status, 1) + add_kwarg!(trans, kwargs, :configuration, fill("wye", n_windings)) + add_kwarg!(trans, kwargs, :polarity, fill(true, n_windings)) + add_kwarg!(trans, kwargs, :rs, fill(0.0, n_windings)) + add_kwarg!(trans, kwargs, :xsc, fill(0.0, n_windings^2-n_windings)) + add_kwarg!(trans, kwargs, :noloadloss, 0.0) + add_kwarg!(trans, kwargs, :imag, 0.0) + add_kwarg!(trans, kwargs, :tm, fill(fill(1.0, 3), n_windings)) + add_kwarg!(trans, kwargs, :tm_min, fill(fill(0.9, 3), n_windings)) + add_kwarg!(trans, kwargs, :tm_max, fill(fill(1.1, 3), n_windings)) + add_kwarg!(trans, kwargs, :tm_step, fill(fill(1/32, 3), n_windings)) + add_kwarg!(trans, kwargs, :tm_fix, fill(fill(true, 3), n_windings)) + + _add_unused_kwargs!(trans, kwargs) + + return trans +end + +# +# # Transformer, two-winding three-phase +# +# DTYPES[:transformer_2w_ideal] = Dict( +# :id => Any, +# :f_bus => String, +# :t_bus => String, +# :configuration => String, +# :f_terminals => Array{Int, 1}, +# :t_terminals => Array{Int, 1}, +# :tm_nom => Real, +# :tm_set => Real, +# :tm_min => Real, +# :tm_max => Real, +# :tm_step => Real, +# :tm_fix => Real, +# ) +# +# +# CHECKS[:transformer_2w_ideal] = function check_transformer_2w_ideal(data, trans) +# end +# +# +# function create_transformer_2w_ideal(id, f_bus, t_bus, tm_nom; kwargs...) +# trans = Dict{String,Any}() +# trans["id"] = id +# trans["f_bus"] = f_bus +# trans["t_bus"] = t_bus +# trans["tm_nom"] = tm_nom +# add_kwarg!(trans, kwargs, :configuration, "wye") +# add_kwarg!(trans, kwargs, :f_terminals, trans["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) +# add_kwarg!(trans, kwargs, :t_terminals, [1, 2, 3, 4]) +# add_kwarg!(trans, kwargs, :tm_set, 1.0) +# add_kwarg!(trans, kwargs, :tm_min, 0.9) +# add_kwarg!(trans, kwargs, :tm_max, 1.1) +# add_kwarg!(trans, kwargs, :tm_step, 1/32) +# add_kwarg!(trans, kwargs, :tm_fix, true) +# return trans +# end + + +# Capacitor + +DTYPES[:capacitor] = Dict( + :id => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :configuration => String, + :qd_ref => Array{<:Real, 1}, + :vnom => Real, +) + +REQUIRED_FIELDS[:capacitor] = keys(DTYPES[:capacitor]) + + +CHECKS[:capacitor] = function check_capacitor(data, cap) + id = cap["id"] + N = length(cap["connections"]) + config = cap["configuration"] + if config=="wye" + @assert(length(cap["qd_ref"])==N-1, "capacitor $id: qd_ref should have $(N-1) elements.") + else + @assert(length(cap["qd_ref"])==N, "capacitor $id: qd_ref should have $N elements.") + end + @assert(config in ["delta", "wye", "wye-grounded", "wye-floating"]) + if config=="delta" + @assert(N>=3, "Capacitor $id: delta-connected capacitors should have at least 3 elements.") + end + + _check_connectivity(data, cap; context="capacitor $id") +end + + +function create_capacitor(; kwargs...) + cap = Dict{String,Any}() + + add_kwarg!(cap, kwargs, :status, 1) + add_kwarg!(cap, kwargs, :configuration, "wye") + add_kwarg!(cap, kwargs, :connections, collect(1:4)) + add_kwarg!(cap, kwargs, :qd_ref, fill(0.0, 3)) + + _add_unused_kwargs!(cap, kwargs) + + return cap +end + + +# Shunt + +DTYPES[:shunt] = Dict( + :id => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :g_sh => Array{<:Real, 2}, + :b_sh => Array{<:Real, 2}, +) + +REQUIRED_FIELDS[:shunt] = keys(DTYPES[:shunt]) + + +CHECKS[:shunt] = function check_shunt(data, shunt) + _check_connectivity(data, shunt; context="shunt $id") + +end + + +function create_shunt(; kwargs...) + shunt = Dict{String,Any}() + + N = length(kwargs[:connections]) + + add_kwarg!(shunt, kwargs, :status, 1) + add_kwarg!(shunt, kwargs, :g_sh, fill(0.0, N, N)) + add_kwarg!(shunt, kwargs, :b_sh, fill(0.0, N, N)) + + _add_unused_kwargs!(shunt, kwargs) + + return shunt +end + + +# voltage source + +DTYPES[:voltage_source] = Dict( + :id => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :vm =>Array{<:Real}, + :va =>Array{<:Real}, + :pg_max =>Array{<:Real}, + :pg_min =>Array{<:Real}, + :qg_max =>Array{<:Real}, + :qg_min =>Array{<:Real}, +) + +REQUIRED_FIELDS[:voltage_source] = [:id, :status, :bus, :connections, :vm, :va] + +CHECKS[:voltage_source] = function check_voltage_source(data, vs) + id = vs["id"] + _check_connectivity(data, vs; context="voltage source $id") + N = length(vs["connections"]) + _check_has_size(vs, ["vm", "va", "pg_max", "pg_min", "qg_max", "qg_min"], N, context="voltage source $id") + +end + + +function create_voltage_source(; kwargs...) + vs = Dict{String,Any}() + + add_kwarg!(vs, kwargs, :status, 1) + add_kwarg!(vs, kwargs, :connections, collect(1:3)) + + _add_unused_kwargs!(vs, kwargs) + + return vs +end + + +# create add_comp! methods +for comp in keys(DTYPES) + eval(Meta.parse("add_$(comp)!(data_model; kwargs...) = add!(data_model, \"$comp\", create_$comp(; kwargs...))")) +end diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl new file mode 100644 index 000000000..85fdbb0fd --- /dev/null +++ b/src/io/data_model_test.jl @@ -0,0 +1,161 @@ +using PowerModelsDistribution +import LinearAlgebra + +function make_test_data_model() + + data_model = create_data_model() + + add_linecode!(data_model, id="6_conds", rs=ones(6, 6), xs=ones(6, 6)) + add_linecode!(data_model, id="4_conds", rs=ones(4, 4), xs=ones(4, 4)) + add_linecode!(data_model, id="3_conds", rs=ones(3, 3), xs=ones(3, 3)) + add_linecode!(data_model, id="2_conds", rs=ones(2, 2), xs=ones(2, 2)) + + # 3 phase + 3 neutral conductors + add_line!(data_model, id="1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6)) + add_line!(data_model, id="2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4]) + # 3 phase + 1 neutral conductors + add_line!(data_model, id="3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2) + # 3 phase conductors + add_line!(data_model, id="4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) + # 2 phase + 1 neutral conductors + add_line!(data_model, id="5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4]) + # 1 phase + 1 neutral conductors + add_line!(data_model, id="6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4]) + # 2 phase conductors + add_line!(data_model, id="7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) + for i in 8:1000 + add_line!(data_model, id="$i", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) + end + + add_bus!(data_model, id="1", terminals=collect(1:4)) + add_bus!(data_model, id="2", terminals=collect(1:6)) + add_bus!(data_model, id="3", terminals=collect(1:4)) + add_bus!(data_model, id="4") + add_bus!(data_model, id="5", terminals=collect(1:4)) + add_bus!(data_model, id="6", terminals=[1,3,4]) + add_bus!(data_model, id="7", terminals=[2,4]) + add_bus!(data_model, id="8", terminals=[1,2]) + add_bus!(data_model, id="9", terminals=[1,2,3,4]) + add_bus!(data_model, id="10", terminals=[1,2,3]) + + # + add_load!(data_model, id="1", bus="7", connections=[2,4], pd=[1.0], qd=[1.0]) + add_load!(data_model, id="2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)]) + add_load!(data_model, id="3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230]) + add_load!(data_model, id="4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5]) + add_load!(data_model, id="5", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3)) + add_load!(data_model, id="6", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3)) + add_load!(data_model, id="7", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3)) + add_load!(data_model, id="8", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]) + add_load!(data_model, id="9", bus="5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3)) + + add_generator!(data_model, id="1", bus="1", configuration="wye") + + add_transformer_nw!(data_model, id="1", bus=["5", "9", "10"], connections=[[1,2,3], [1,2,3,4], [1,2,3]], + vnom=[0.230, 0.230, 0.230], snom=[0.230, 0.230, 0.230], + configuration=["delta", "wye", "delta"], + xsc=[0.0, 0.0, 0.0], + rs=[0.0, 0.0, 1.0], + noloadloss=0.05, + imag=0.05, + ) + + add_capacitor!(data_model, id="cap_3ph", bus="3", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3]) + add_capacitor!(data_model, id="cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3]) + add_capacitor!(data_model, id="cap_2ph_yg", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-grounded") + add_capacitor!(data_model, id="cap_2ph_yfl", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-floating") + add_capacitor!(data_model, id="cap_2ph_y", bus="5", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4]) + + return data_model +end + + +function make_3wire_data_model() + + data_model = create_data_model() + + add_linecode!(data_model, id="3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3))) + + add_voltage_source!(data_model, id="source", bus="sourcebus", connections=collect(1:3), + vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], + pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), + rs=ones(3,3)/10 + ) + + # 3 phase conductors + add_line!(data_model, id=:test, f_bus="sourcebus", t_bus="tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) + + add_bus!(data_model, id="sourcebus", terminals=collect(1:3)) + add_bus!(data_model, id="tr_prim", terminals=collect(1:4)) + add_bus!(data_model, id="tr_sec", terminals=collect(1:4)) + #add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) + + # add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], + # [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], + # configuration=["delta", "wye", "delta"], + # xsc=[0.0, 0.0, 0.0], + # rs=[0.0, 0.0, 0.0], + # loadloss=0.00, + # imag=0.00, + # )) + + add_transformer_nw!(data_model, id="1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], + vnom=[0.230, 0.230], snom=[0.230, 0.230], + configuration=["wye", "wye"], + xsc=[0.0], + rs=[0.0, 0.0], + noloadloss=0.00, + imag=0.00, + ) + + + + # + add_load!(data_model, id="1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0]) + + # add!(data_model, "generator", create_generator("1", "source", + # connections=[1, 2, 3, 4], + # pg_min=fill(-100, 3), + # pg_max=fill( 100, 3), + # qg_min=fill(-100, 3), + # qg_max=fill( 100, 3), + # )) + + #add!(data_model, "capacitor", create_capacitor(1, "tr_sec", 0.230*sqrt(3), qd_ref=[1.0, 1.0, 1.0]*1E-3, connections=[1,2,3], configuration="delta")) + + return data_model +end + + +data_model = make_3wire_data_model() +check_data_model(data_model) +data_model +# +data_model_map!(data_model) +#bsh = data_model["shunt"]["_virtual_1"]["b_sh"] +## +data_model_make_pu!(data_model, vbases=Dict("sourcebus"=>0.230)) +# +data_model_index!(data_model) +data_model_make_compatible_v8!(data_model) +## +import PowerModelsDistribution +PMD = PowerModelsDistribution +import PowerModels +PMs = PowerModels +import InfrastructureModels +IM = InfrastructureModels + +import JuMP, Ipopt + +ipopt_solver = JuMP.with_optimizer(Ipopt.Optimizer) +pm = PMs.instantiate_model(data_model, PMs.IVRPowerModel, PMD.build_mc_opf_iv, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) +sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + +solution_unmake_pu!(sol["solution"], data_model) +solution_identify!(sol["solution"], data_model) +solution_unmap!(sol["solution"], data_model) +#vm = sol["solution"]["bus"]["tr_sec"]["vm"] +#va = sol["solution"]["bus"]["tr_sec"]["va"] +#v = vm.*exp.(im*va/180*pi) +sol["solution"] diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl new file mode 100644 index 000000000..7b88765d0 --- /dev/null +++ b/src/io/data_model_util.jl @@ -0,0 +1,164 @@ + +#import PowerModelsDistribution +#get = PowerModelsDistribution.get + +function scale(dict, key, scale) + if haskey(dict, key) + dict[key] *= scale + end +end + + +function add_virtual!(data_model, comp_type, comp) + if !haskey(data_model, comp_type) + data_model[comp_type] = Dict{Any, Any}() + end + comp_dict = data_model[comp_type] + virtual_ids = [parse(Int, x[1]) for x in [match(r"_virtual_([1-9]{1}[0-9]*)", id) for id in keys(comp_dict) if isa(id, AbstractString)] if x!=nothing] + if isempty(virtual_ids) + id = "_virtual_1" + else + id = "_virtual_$(maximum(virtual_ids)+1)" + end + comp["id"] = id + comp_dict[id] = comp + return comp +end + +add_virtual_get_id!(data_model, comp_type, comp) = add_virtual!(data_model, comp_type, comp)["id"] + +function delete_component!(data_model, comp_type, comp::Dict) + delete!(data_model[comp_type], comp["id"]) + if isempty(data_model[comp_type]) + delete!(data_model, comp_type) + end +end + +function delete_component!(data_model, comp_type, id::Any) + delete!(data_model[comp_type], id) + if isempty(data_model[comp_type]) + delete!(data_model, comp_type) + end +end + +function add_mappings!(data_model::Dict{String, Any}, mapping_type::String, mappings::Vector) + if !haskey(data_model, "mappings") + data_model["mappings"] = [] + end + + append!(data_model["mappings"], [(mapping_type, mapping) for mapping in mappings]) +end + + +function data_model_index!(data_model; components=["line", "shunt", "generator", "load", "transformer_2wa"]) + bus_id2ind = Dict() + + for (i, id) in enumerate(keys(data_model["bus"])) + data_model["bus"][id]["index"] = i + bus_id2ind[id] = i + end + data_model["bus"] = Dict{String, Any}(string(bus_id2ind[id])=>bus for (id, bus) in data_model["bus"]) + + for comp_type in components + comp_dict = Dict{String, Any}() + for (i,(id,comp)) in enumerate(data_model[comp_type]) + @assert(!haskey(comp, "index"), "$comp_type $id: component already has an index.") + comp["index"] = i + comp["id"] = id + comp_dict["$i"] = comp + for bus_key in ["f_bus", "t_bus", "bus"] + if haskey(comp, bus_key) + comp[bus_key] = bus_id2ind[comp[bus_key]] + end + end + end + data_model[comp_type] = comp_dict + end + return data_model +end + + +function solution_identify!(solution, data_model; id_prop="id") + for comp_type in keys(solution) + if isa(solution[comp_type], Dict) + comp_dict = Dict{Any, Any}() + for (ind, comp) in solution[comp_type] + id = data_model[comp_type][ind][id_prop] + comp_dict[id] = comp + end + solution[comp_type] = comp_dict + end + end + + return solution +end + +function add_solution!(solution, comp_type, id, data) + if !haskey(solution, comp_type) + solution[comp_type] = Dict() + end + + if !haskey(solution[comp_type], id) + solution[comp_type][id] = Dict{String, Any}() + end + + for (key, prop) in data + solution[comp_type][id][key] = prop + end +end + + +function delete_solution!(solution, comp_type, id, props) + if haskey(solution, comp_type) + if haskey(solution[comp_type], id) + for prop in props + delete!(solution[comp_type][id], prop) + end + end + end +end + + +function delete_solution!(solution, comp_type, id) + if haskey(solution, comp_type) + if haskey(solution[comp_type], id) + delete!(solution[comp_type], id) + end + end +end + +## +function _get_new_ground(terminals) + if isa(terminals, Vector{Int}) + return maximum(terminals)+1 + else + nrs = [parse(Int, x[1]) for x in [match(r"n([1-9]{1}[0-9]*)", string(t)) for t in terminals] if x!=nothing] + new = isempty(nrs) ? 1 : maximum(nrs)+1 + if isa(terminals, Vector{Symbol}) + return Symbol("g$new") + else + return "g$new" + end + end +end + + +function _get_ground!(bus) + # find perfect groundings (true ground) + grounded_perfect = [] + for i in 1:length(bus["grounded"]) + if bus["rg"][i]==0 && bus["xg"][i]==0 + push!(grounded_perfect, bus["grounded"][i]) + end + end + + if !isempty(grounded_perfect) + return grounded_perfect[1] + else + g = _get_new_ground(bus["terminals"]) + push!(bus["terminals"], g) + push!(bus["rg"], 0.0) + push!(bus["xg"], 0.0) + return g + end +end diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl new file mode 100644 index 000000000..564f8b9e2 --- /dev/null +++ b/src/io/opendss_dm.jl @@ -0,0 +1,1231 @@ +# OpenDSS parser +import LinearAlgebra: isdiag, diag, pinv + + +"Structure representing OpenDSS `dss_source_id` giving the type of the component `dss_type`, its name `dss_name`, and the active phases `active_phases`" +struct DSSSourceId + dss_type::AbstractString + dss_name::AbstractString + active_phases::Set{Int} +end + + +"Parses a component's OpenDSS source information into the `dss_source_id` struct" +function _parse_dss_source_id(component::Dict)::DSSSourceId + dss_type, dss_name = split(component["source_id"], '.') + return DSSSourceId(dss_type, dss_name, Set(component["active_phases"])) +end + + +"returns the linecode with name `id`" +function _get_linecode(dss_data::Dict, id::AbstractString) + if haskey(dss_data, "linecode") + for item in dss_data["linecode"] + if item["name"] == id + return item + end + end + end + return Dict{String,Any}() +end + + +""" + _discover_buses(dss_data) + +Discovers all of the buses (not separately defined in OpenDSS), from "lines". +""" +function _discover_buses(dss_data::Dict)::Array + bus_names = [] + buses = [] + for compType in ["line", "transformer", "reactor"] + if haskey(dss_data, compType) + compList = dss_data[compType] + for compObj in compList + if compType == "transformer" + compObj = _create_transformer(compObj["name"]; _to_sym_keys(compObj)...) + for bus in compObj["buses"] + name, nodes = _parse_busname(bus) + if !(name in bus_names) + push!(bus_names, name) + push!(buses, (name, nodes)) + end + end + elseif haskey(compObj, "bus2") + for key in ["bus1", "bus2"] + name, nodes = _parse_busname(compObj[key]) + if !(name in bus_names) + push!(bus_names, name) + push!(buses, (name, nodes)) + end + end + end + end + end + end + if length(buses) == 0 + Memento.error(_LOGGER, "dss_data has no lines!") + else + return buses + end +end + + +""" + _dss2pmd_bus!(pmd_data, dss_data) + +Adds PowerModels-style buses to `pmd_data` from `dss_data`. +""" +function _dss2pmd_bus_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1) + + buses = _discover_buses(dss_data) + for (n, (bus, nodes)) in enumerate(buses) + + @assert(!(length(bus)>=8 && bus[1:8]=="_virtual"), "Bus $bus: identifiers should not start with _virtual.") + + add_bus!(pmd_data, id=bus, status=1, bus_type=1) + end + + # create virtual sourcebus + circuit = _create_vsource(get(dss_data["circuit"][1], "bus1", "sourcebus"), dss_data["circuit"][1]["name"]; _to_sym_keys(dss_data["circuit"][1])...) + + nodes = Array{Bool}([1 1 1 0]) + ph1_ang = circuit["angle"] + vm_pu = circuit["pu"] + vmi = circuit["pu"] - circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) + vma = circuit["pu"] + circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) + + phases = circuit["phases"] + vnom = pmd_data["settings"]["set_vbase_val"] + + vm = fill(vm_pu, 3)*vnom + va = _wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases]) + + add_voltage_source!(pmd_data, id="source", bus="sourcebus", connections=collect(1:phases), + vm=vm, va=va, + rs=circuit["rmatrix"], xs=circuit["xmatrix"], + ) +end + + +""" + find_component(pmd_data, name, compType) + +Returns the component of `compType` with `name` from `data` of type +Dict{String,Array}. +""" +function find_component(data::Dict, name::AbstractString, compType::AbstractString)::Dict + for comp in values(data[compType]) + if comp["name"] == name + return comp + end + end + Memento.warn(_LOGGER, "Could not find $compType \"$name\"") + return Dict{String,Any}() +end + + +""" + find_bus(busname, pmd_data) + +Finds the index number of the bus in existing data from the given `busname`. +""" +function find_bus(busname::AbstractString, pmd_data::Dict) + bus = find_component(pmd_data, busname, "bus") + if haskey(bus, "bus_i") + return bus["bus_i"] + else + Memento.error(_LOGGER, "cannot find connected bus with id \"$busname\"") + end +end + + +""" + _dss2pmd_load!(pmd_data, dss_data, import_all) + +Adds PowerModels-style loads to `pmd_data` from `dss_data`. +""" +function _dss2pmd_load_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool, ground_terminal::Int=4) + + for load in get(dss_data, "load", []) + _apply_like!(load, dss_data, "load") + defaults = _apply_ordered_properties(_create_load(load["bus1"], load["name"]; _to_sym_keys(load)...), load) + + # parse the model + model = defaults["model"] + # some info on OpenDSS load models + ################################## + # Constant can still be scaled by other settings, fixed cannot + # Note that in the current feature set, fixed therefore equals constant + # 1: Constant P and Q, default + if model == 2 + # 2: Constant Z + elseif model == 3 + # 3: Constant P and quadratic Q + Memento.warn(_LOGGER, "$load_name: load model 3 not supported. Treating as model 1.") + model = 1 + elseif model == 4 + # 4: Exponential + Memento.warn(_LOGGER, "$load_name: load model 4 not supported. Treating as model 1.") + model = 1 + elseif model == 5 + # 5: Constant I + #warn(_LOGGER, "$name: load model 5 not supported. Treating as model 1.") + #model = 1 + elseif model == 6 + # 6: Constant P and fixed Q + Memento.warn(_LOGGER, "$load_name: load model 6 identical to model 1 in current feature set. Treating as model 1.") + model = 1 + elseif model == 7 + # 7: Constant P and quadratic Q (i.e., fixed reactance) + Memento.warn(_LOGGER, "$load_name: load model 7 not supported. Treating as model 1.") + model = 1 + elseif model == 8 + # 8: ZIP + Memento.warn(_LOGGER, "$load_name: load model 8 not supported. Treating as model 1.") + model = 1 + end + # save adjusted model type to dict, human-readable + model_int2str = Dict(1=>"constant_power", 2=>"constant_impedance", 5=>"constant_current") + model = model_int2str[model] + + nphases = defaults["phases"] + conf = defaults["conn"] + + + # connections + bus = _parse_busname(defaults["bus1"])[1] + + connections_default = conf=="wye" ? [collect(1:nphases)..., 0] : collect(1:nphases) + connections = _get_conductors_ordered_dm(defaults["bus1"], default=connections_default, check_length=false) + # if wye connected and neutral not specified, append ground + if conf=="wye" && length(connections)==nphases + connections = [connections..., 0] + end + + # now we can create the load; if you do not have the correct model, + # pd/qd fields will be populated by default (should not happen for constant current/impedance) + loadDict = add_load!(pmd_data, id=defaults["name"], model=model, connections=connections, bus=bus, configuration=conf) + + # if the ground is used directly, register load + if 0 in connections + if !haskey(pmd_data["bus"][bus], "awaiting_ground") + pmd_data["bus"][bus]["awaiting_ground"] = [] + end + push!(pmd_data["bus"][bus]["awaiting_ground"], loadDict) + end + + kv = defaults["kv"] + if conf=="wye" && nphases in [2, 3] + kv = kv/sqrt(3) + end + + if model=="constant_power" + loadDict["pd"] = fill(defaults["kw"]/nphases, nphases) + loadDict["qd"] = fill(defaults["kvar"]/nphases, nphases) + else + loadDict["pd_ref"] = fill(defaults["kw"]/nphases, nphases) + loadDict["qd_ref"] = fill(defaults["kvar"]/nphases, nphases) + loadDict["vnom"] = kv + end + + #loadDict["status"] = convert(Int, defaults["enabled"]) + + #loadDict["source_id"] = "load.$load_name" + + used = ["phases", "bus1", "name"] + _PMs._import_remaining!(loadDict, defaults, import_all; exclude=used) + end +end + + +""" + _dss2pmd_shunt!(pmd_data, dss_data, import_all) + +Adds PowerModels-style shunts to `pmd_data` from `dss_data`. +""" +function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + + for shunt in get(dss_data, "capacitor", []) + _apply_like!(shunt, dss_data, "capacitor") + defaults = _apply_ordered_properties(_create_capacitor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) + + nphases = defaults["phases"] + + dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") + conn = dyz_map[defaults["conn"]] + + bus_name = _parse_busname(defaults["bus1"])[1] + bus2_name = _parse_busname(defaults["bus2"])[1] + if bus_name!=bus2_name + Memento.error("Capacitor $(defaults["name"]): bus1 and bus2 should connect to the same bus.") + end + + f_terminals = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) + if conn=="wye" + t_terminals = _get_conductors_ordered_dm(defaults["bus2"], default=fill(0,nphases)) + else + # if delta connected, ignore bus2 and generate t_terminals such that + # it corresponds to a delta winding + t_terminals = [f_terminals[2:end]..., f_terminals[1]] + end + + + # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) + #TODO figure out for more than 3 phases + vnom_ln = defaults["kv"] + if defaults["phases"] in [2,3] + vnom_ln = vnom_ln/sqrt(3) + end + # 'kvar' is specified for all phases at once; we want the per-phase one, in MVar + qnom = (defaults["kvar"]/1E3)/nphases + b = fill(qnom/vnom_ln^2, nphases) + + # convert to a shunt matrix + terminals, B = calc_shunt(f_terminals, t_terminals, b) + + # if one terminal is ground (0), reduce shunt addmittance matrix + terminals, B = ground_shunt(terminals, B, 0) + + shuntDict = add_shunt!(pmd_data, id=defaults["name"], status=convert(Int, defaults["enabled"]), + bus=bus_name, connections=terminals, + g_sh=fill(0.0, size(B)...), b_sh=B + ) + + used = ["bus1", "phases", "name"] + _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) + + pmd_data["shunt"][shuntDict["id"]] = shuntDict + end + + #TODO revisit this in the future + # for shunt in get(dss_data, "reactor", []) + # if !haskey(shunt, "bus2") + # _apply_like!(shunt, dss_data, "reactor") + # defaults = _apply_ordered_properties(_create_reactor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) + # + # shuntDict = Dict{String,Any}() + # + # nconductors = pmd_data["conductors"] + # name, nodes = _parse_busname(defaults["bus1"]) + # + # Zbase = (pmd_data["basekv"] / sqrt(3.0))^2 * nconductors / pmd_data["baseMVA"] # Use single-phase base impedance for each phase + # Gcap = Zbase * sum(defaults["kvar"]) / (nconductors * 1e3 * (pmd_data["basekv"] / sqrt(3.0))^2) + # + # shuntDict["shunt_bus"] = find_bus(name, pmd_data) + # shuntDict["name"] = defaults["name"] + # shuntDict["gs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) # TODO: + # shuntDict["bs"] = _PMs.MultiConductorVector(_parse_array(Gcap, nodes, nconductors)) + # shuntDict["status"] = convert(Int, defaults["enabled"]) + # shuntDict["index"] = length(pmd_data["shunt"]) + 1 + # + # shuntDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + # shuntDict["source_id"] = "reactor.$(defaults["name"])" + # + # used = ["bus1", "phases", "name"] + # _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) + # + # push!(pmd_data["shunt"], shuntDict) + # end + # end +end + + +""" +Given a vector and a list of elements to find, this method will return a list +of the positions of the elements in that vector. +""" +function get_inds(vec::Array{<:Any, 1}, els::Array{<:Any, 1}) + ret = Array{Int, 1}(undef, length(els)) + for (i,f) in enumerate(els) + for (j,l) in enumerate(vec) + if f==l + ret[i] = j + end + end + end + return ret +end + + +""" +Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the +conductors 't_cnds', this method will return a list of conductors 'cnd' and a +matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. +""" +function calc_shunt(f_cnds, t_cnds, y) + cnds = unique([f_cnds..., t_cnds...]) + e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) + Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) + return (cnds, Y) +end + + + +""" +Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this +method will calculate the reduced addmittance matrix if terminal 'ground' is +grounded. +""" +function ground_shunt(cnds, Y, ground) + if ground in cnds + cndsr = setdiff(cnds, ground) + cndsr_inds = get_inds(cnds, cndsr) + Yr = Y[cndsr_inds, cndsr_inds] + return (cndsr, Yr) + else + return cnds, Y + end +end + + +function rm_floating_cnd(cnds, Y, f) + P = setdiff(cnds, f) + f_inds = get_inds(cnds, [f]) + P_inds = get_inds(cnds, P) + Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] + return (P,Yrm) +end + + +""" + _dss2pmd_gen!(pmd_data, dss_data, import_all) + +Adds PowerModels-style generators to `pmd_data` from `dss_data`. +""" +function _dss2pmd_gen_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "gen") + pmd_data["gen"] = [] + end + + # # sourcebus generator (created by circuit) + # circuit = dss_data["circuit"][1] + # defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), "sourcegen"; _to_sym_keys(circuit)...) + # + # genDict = Dict{String,Any}() + # + # nconductors = pmd_data["conductors"] + # name, nodes = _parse_busname(defaults["bus1"]) + # + # genDict["gen_bus"] = find_bus("virtual_sourcebus", pmd_data) + # genDict["name"] = defaults["name"] + # genDict["gen_status"] = convert(Int, defaults["enabled"]) + # + # # TODO: populate with VSOURCE properties + # genDict["pg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) + # genDict["qg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) + # + # genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) + # genDict["qmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) + # + # genDict["pmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) + # genDict["pmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) + # + # genDict["model"] = 2 + # genDict["startup"] = 0.0 + # genDict["shutdown"] = 0.0 + # genDict["ncost"] = 3 + # genDict["cost"] = [0.0, 1.0, 0.0] + # + # genDict["index"] = length(pmd_data["gen"]) + 1 + # + # genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + # genDict["source_id"] = "vsource.$(defaults["name"])" + # + # used = ["name", "phases", "bus1"] + # _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) + # + # push!(pmd_data["gen"], genDict) + + + for gen in get(dss_data, "generator", []) + _apply_like!(gen, dss_data, "generator") + defaults = _apply_ordered_properties(_create_generator(gen["bus1"], gen["name"]; _to_sym_keys(gen)...), gen) + + genDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + genDict["gen_bus"] = find_bus(name, pmd_data) + genDict["name"] = defaults["name"] + genDict["gen_status"] = convert(Int, defaults["enabled"]) + genDict["pg"] = _PMs.MultiConductorVector(_parse_array(defaults["kw"] / (1e3 * nconductors), nodes, nconductors)) + genDict["qg"] = _PMs.MultiConductorVector(_parse_array(defaults["kvar"] / (1e3 * nconductors), nodes, nconductors)) + genDict["vg"] = _PMs.MultiConductorVector(_parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors)) + + genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(defaults["minkvar"] / (1e3 * nconductors), nodes, nconductors)) + genDict["qmax"] = _PMs.MultiConductorVector(_parse_array(defaults["maxkvar"] / (1e3 * nconductors), nodes, nconductors)) + + genDict["apf"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + + genDict["pmax"] = genDict["pg"] # Assumes generator is at rated power + genDict["pmin"] = 0.0 * genDict["pg"] # 0% of pmax + + genDict["pc1"] = genDict["pmax"] + genDict["pc2"] = genDict["pmin"] + genDict["qc1min"] = genDict["qmin"] + genDict["qc1max"] = genDict["qmax"] + genDict["qc2min"] = genDict["qmin"] + genDict["qc2max"] = genDict["qmax"] + + # For distributed generation ramp rates are not usually an issue + # and they are not supported in OpenDSS + genDict["ramp_agc"] = genDict["pmax"] + + genDict["ramp_q"] = _PMs.MultiConductorVector(_parse_array(max.(abs.(genDict["qmin"].values), abs.(genDict["qmax"].values)), nodes, nconductors)) + genDict["ramp_10"] = genDict["pmax"] + genDict["ramp_30"] = genDict["pmax"] + + genDict["control_model"] = defaults["model"] + + # if PV generator mode convert attached bus to PV bus + if genDict["control_model"] == 3 + pmd_data["bus"][genDict["gen_bus"]]["bus_type"] = 2 + end + + genDict["model"] = 2 + genDict["startup"] = 0.0 + genDict["shutdown"] = 0.0 + genDict["ncost"] = 3 + genDict["cost"] = [0.0, 1.0, 0.0] + + genDict["index"] = length(pmd_data["gen"]) + 1 + + genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + genDict["source_id"] = "generator.$(defaults["name"])" + + used = ["name", "phases", "bus1"] + _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) + + push!(pmd_data["gen"], genDict) + end + + for pv in get(dss_data, "pvsystem", []) + Memento.warn(_LOGGER, "Converting PVSystem \"$(pv["name"])\" into generator with limits determined by OpenDSS property 'kVA'") + + _apply_like!(pv, dss_data, "pvsystem") + defaults = _apply_ordered_properties(_create_pvsystem(pv["bus1"], pv["name"]; _to_sym_keys(pv)...), pv) + + pvDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + pvDict["name"] = defaults["name"] + pvDict["gen_bus"] = find_bus(name, pmd_data) + + pvDict["pg"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + pvDict["qg"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + pvDict["vg"] = _PMs.MultiConductorVector(_parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors)) + + pvDict["pmin"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + pvDict["pmax"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + + pvDict["qmin"] = -_PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + pvDict["qmax"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + + pvDict["gen_status"] = convert(Int, defaults["enabled"]) + + pvDict["model"] = 2 + pvDict["startup"] = 0.0 + pvDict["shutdown"] = 0.0 + pvDict["ncost"] = 3 + pvDict["cost"] = [0.0, 1.0, 0.0] + + pvDict["index"] = length(pmd_data["gen"]) + 1 + + pvDict["active_phases"] = [nodes[n] > 0 ? 1 : 0 for n in 1:nconductors] + pvDict["source_id"] = "pvsystem.$(defaults["name"])" + + used = ["name", "phases", "bus1"] + _PMs._import_remaining!(pvDict, defaults, import_all; exclude=used) + + push!(pmd_data["gen"], pvDict) + end +end + + +""" + _dss2pmd_line!(pmd_data, dss_data, import_all) + +Adds PowerModels-style lines to `pmd_data` from `dss_data`. +""" +function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "branch") + pmd_data["line"] = Dict{String, Any}() + end + + #nconductors = pmd_data["conductors"] + + for line in get(dss_data, "line", []) + _apply_like!(line, dss_data, "line") + + if haskey(line, "linecode") + linecode = deepcopy(_get_linecode(dss_data, get(line, "linecode", ""))) + if haskey(linecode, "like") + linecode = merge(find_component(dss_data, linecode["like"], "linecode"), linecode) + end + + linecode["units"] = get(line, "units", "none") == "none" ? "none" : get(linecode, "units", "none") + linecode["circuit_basefreq"] = pmd_data["settings"]["basefreq"] + + linecode = _create_linecode(get(linecode, "name", ""); _to_sym_keys(linecode)...) + delete!(linecode, "name") + else + linecode = Dict{String,Any}() + end + + if haskey(line, "basefreq") && line["basefreq"] != pmd_data["settings"]["basefreq"] + Memento.warn(_LOGGER, "basefreq=$(line["basefreq"]) on line $(line["name"]) does not match circuit basefreq=$(pmd_data["settings"]["basefreq"])") + line["circuit_basefreq"] = pmd_data["settings"]["basefreq"] + end + + defaults = _apply_ordered_properties(_create_line(line["bus1"], line["bus2"], line["name"]; _to_sym_keys(line)...), line; linecode=linecode) + + lineDict = Dict{String,Any}() + + lineDict["id"] = defaults["name"] + lineDict["f_bus"] = _parse_busname(defaults["bus1"])[1] + lineDict["t_bus"] = _parse_busname(defaults["bus2"])[1] + + #lineDict["length"] = defaults["length"] + + nphases = defaults["phases"] + lineDict["n_conductors"] = nphases + + rmatrix = defaults["rmatrix"] + xmatrix = defaults["xmatrix"] + cmatrix = defaults["cmatrix"] + + lineDict["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) + lineDict["t_connections"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) + + lineDict["rs"] = rmatrix * defaults["length"] + lineDict["xs"] = xmatrix * defaults["length"] + + lineDict["g_fr"] = fill(0.0, nphases, nphases) + lineDict["g_to"] = fill(0.0, nphases, nphases) + + lineDict["b_fr"] = (2.0 * pi * pmd_data["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 + lineDict["b_to"] = (2.0 * pi * pmd_data["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 + + #lineDict["c_rating_a"] = fill(defaults["normamps"], nphases) + #lineDict["c_rating_b"] = fill(defaults["emergamps"], nphases) + #lineDict["c_rating_c"] = fill(defaults["emergamps"], nphases) + + lineDict["status"] = convert(Int, defaults["enabled"]) + + #lineDict["source_id"] = "line.$(defaults["name"])" + + #used = ["name", "bus1", "bus2", "rmatrix", "xmatrix"] + #_PMs._import_remaining!(lineDict, defaults, import_all; exclude=used) + + pmd_data["line"][lineDict["id"]] = lineDict + end +end + + +""" + _dss2pmd_transformer!(pmd_data, dss_data, import_all) + +Adds ThreePhasePowerModels-style transformers to `pmd_data` from `dss_data`. +""" +function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "transformer_nw") + pmd_data["transformer_nw"] = Dict{String,Any}() + end + + for transformer in get(dss_data, "transformer", []) + _apply_like!(transformer, dss_data, "transformer") + defaults = _apply_ordered_properties(_create_transformer(transformer["name"]; _to_sym_keys(transformer)...), transformer) + + nphases = defaults["phases"] + nrw = defaults["windings"] + + dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") + confs = [dyz_map[x] for x in defaults["conns"]] + + # test if this transformer conforms with limitations + if nphases<3 && "delta" in confs + Memento.error("Transformers with delta windings should have at least 3 phases to be well-defined.") + end + if nrw>3 + # All of the code is compatible with any number of windings, + # except for the parsing of the loss model (the pair-wise reactance) + Memento.error(_LOGGER, "For now parsing of xscarray is not supported. At most 3 windings are allowed, not $nrw.") + end + + transDict = Dict{String, Any}() + transDict["id"] = defaults["name"] + transDict["bus"] = Array{String, 1}(undef, nrw) + transDict["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) + transDict["tm"] = Array{Array{Real, 1}, 1}(undef, nrw) + transDict["fixed"] = [[true for i in 1:nphases] for j in 1:nrw] + transDict["vnom"] = [defaults["kvs"][w] for w in 1:nrw] + transDict["snom"] = [defaults["kvas"][w] for w in 1:nrw] + transDict["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) + transDict["configuration"] = Array{String, 1}(undef, nrw) + transDict["polarity"] = Array{String, 1}(undef, nrw) + + for w in 1:nrw + transDict["bus"][w] = _parse_busname(defaults["buses"][w])[1] + + conn = dyz_map[defaults["conns"][w]] + transDict["configuration"][w] = conn + + terminals_default = conn=="wye" ? [1:nphases..., 0] : collect(1:nphases) + terminals_w = _get_conductors_ordered_dm(defaults["buses"][w], default=terminals_default) + transDict["connections"][w] = terminals_w + if 0 in terminals_w + bus = transDict["bus"][w] + if !haskey(pmd_data["bus"][bus], "awaiting_ground") + pmd_data["bus"][bus]["awaiting_ground"] = [] + end + push!(pmd_data["bus"][bus]["awaiting_ground"], transDict) + end + transDict["polarity"][w] = "forward" + transDict["tm"][w] = fill(defaults["taps"][w], nphases) + end + + #transDict["source_id"] = "transformer.$(defaults["name"])" + if !isempty(defaults["bank"]) + transDict["bank"] = defaults["bank"] + end + + # loss model (converted to SI units, referred to secondary) + transDict["rs"] = [defaults["%rs"][w]/100 for w in 1:nrw] + transDict["noloadloss"] = defaults["%noloadloss"]/100 + transDict["imag"] = defaults["%imag"]/100 + if nrw==2 + transDict["xsc"] = [defaults["xhl"]]/100 + elseif nrw==3 + transDict["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 + end + + add_virtual!(pmd_data, "transformer_nw", create_transformer_nw(; + Dict(Symbol.(keys(transDict)).=>values(transDict))... + )) + end +end + + +""" + _dss2pmd_reactor!(pmd_data, dss_data, import_all) + +Adds PowerModels-style branch components based on DSS reactors to `pmd_data` from `dss_data` +""" +function _dss2pmd_reactor_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "branch") + pmd_data["branch"] = [] + end + + if haskey(dss_data, "reactor") + Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating like line") + for reactor in dss_data["reactor"] + if haskey(reactor, "bus2") + _apply_like!(reactor, dss_data, "reactor") + defaults = _apply_ordered_properties(_create_reactor(reactor["bus1"], reactor["name"], reactor["bus2"]; _to_sym_keys(reactor)...), reactor) + + reactDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + + f_bus, nodes = _parse_busname(defaults["bus1"]) + t_bus = _parse_busname(defaults["bus2"])[1] + + reactDict["name"] = defaults["name"] + reactDict["f_bus"] = find_bus(f_bus, pmd_data) + reactDict["t_bus"] = find_bus(t_bus, pmd_data) + + reactDict["br_r"] = _PMs.MultiConductorMatrix(_parse_matrix(diagm(0 => fill(0.2, nconductors)), nodes, nconductors)) + reactDict["br_x"] = _PMs.MultiConductorMatrix(_parse_matrix(zeros(nconductors, nconductors), nodes, nconductors)) + + reactDict["g_fr"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + reactDict["g_to"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + reactDict["b_fr"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + reactDict["b_to"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + + for key in ["g_fr", "g_to", "b_fr", "b_to"] + reactDict[key] = _PMs.MultiConductorMatrix(LinearAlgebra.diagm(0=>reactDict[key].values)) + end + + reactDict["c_rating_a"] = _PMs.MultiConductorVector(_parse_array(defaults["normamps"], nodes, nconductors)) + reactDict["c_rating_b"] = _PMs.MultiConductorVector(_parse_array(defaults["emergamps"], nodes, nconductors)) + reactDict["c_rating_c"] = _PMs.MultiConductorVector(_parse_array(defaults["emergamps"], nodes, nconductors)) + + reactDict["tap"] = _PMs.MultiConductorVector(_parse_array(1.0, nodes, nconductors, NaN)) + reactDict["shift"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + + reactDict["br_status"] = convert(Int, defaults["enabled"]) + + reactDict["angmin"] = _PMs.MultiConductorVector(_parse_array(-60.0, nodes, nconductors, -60.0)) + reactDict["angmax"] = _PMs.MultiConductorVector(_parse_array( 60.0, nodes, nconductors, 60.0)) + + reactDict["transformer"] = true + + reactDict["index"] = length(pmd_data["branch"]) + 1 + + nodes = .+([_parse_busname(defaults[n])[2] for n in ["bus1", "bus2"]]...) + reactDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + reactDict["source_id"] = "reactor.$(defaults["name"])" + + used = [] + _PMs._import_remaining!(reactDict, defaults, import_all; exclude=used) + + push!(pmd_data["branch"], reactDict) + end + end + end +end + + +""" + _dss2pmd_pvsystem!(pmd_data, dss_data) + +Adds PowerModels-style pvsystems to `pmd_data` from `dss_data`. +""" +function _dss2pmd_pvsystem_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "pvsystem") + pmd_data["pvsystem"] = [] + end + + for pvsystem in get(dss_data, "pvsystem", []) + _apply_like!(pvsystem, dss_data, "pvsystem") + defaults = _apply_ordered_properties(_create_pvsystem(pvsystem["bus1"], pvsystem["name"]; _to_sym_keys(pvsystem)...), pvsystem) + + pvsystemDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + pvsystemDict["name"] = defaults["name"] + pvsystemDict["pv_bus"] = find_bus(name, pmd_data) + pvsystemDict["p"] = _PMs.MultiConductorVector(_parse_array(defaults["kw"] / 1e3, nodes, nconductors)) + pvsystemDict["q"] = _PMs.MultiConductorVector(_parse_array(defaults["kvar"] / 1e3, nodes, nconductors)) + pvsystemDict["status"] = convert(Int, defaults["enabled"]) + + pvsystemDict["index"] = length(pmd_data["pvsystem"]) + 1 + + pvsystemDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + pvsystemDict["source_id"] = "pvsystem.$(defaults["name"])" + + used = ["phases", "bus1", "name"] + _PMs._import_remaining!(pvsystemDict, defaults, import_all; exclude=used) + + push!(pmd_data["pvsystem"], pvsystemDict) + end +end + + +""" + _dss2pmd_storage!(pmd_data, dss_data, import_all) + +Adds PowerModels-style storage to `pmd_data` from `dss_data` +""" +function _dss2pmd_storage_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) + if !haskey(pmd_data, "storage") + pmd_data["storage"] = [] + end + + for storage in get(dss_data, "storage", []) + _apply_like!(storage, dss_data, "storage") + defaults = _apply_ordered_properties(_create_storage(storage["bus1"], storage["name"]; _to_sym_keys(storage)...), storage) + + storageDict = Dict{String,Any}() + + nconductors = pmd_data["conductors"] + name, nodes = _parse_busname(defaults["bus1"]) + + storageDict["name"] = defaults["name"] + storageDict["storage_bus"] = find_bus(name, pmd_data) + storageDict["energy"] = defaults["kwhstored"] / 1e3 + storageDict["energy_rating"] = defaults["kwhrated"] / 1e3 + storageDict["charge_rating"] = defaults["%charge"] * defaults["kwrated"] / 1e3 / 100.0 + storageDict["discharge_rating"] = defaults["%discharge"] * defaults["kwrated"] / 1e3 / 100.0 + storageDict["charge_efficiency"] = defaults["%effcharge"] / 100.0 + storageDict["discharge_efficiency"] = defaults["%effdischarge"] / 100.0 + storageDict["thermal_rating"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / 1e3 / nconductors, nodes, nconductors)) + storageDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-defaults["kvar"] / 1e3 / nconductors, nodes, nconductors)) + storageDict["qmax"] = _PMs.MultiConductorVector(_parse_array( defaults["kvar"] / 1e3 / nconductors, nodes, nconductors)) + storageDict["r"] = _PMs.MultiConductorVector(_parse_array(defaults["%r"] / 100.0, nodes, nconductors)) + storageDict["x"] = _PMs.MultiConductorVector(_parse_array(defaults["%x"] / 100.0, nodes, nconductors)) + storageDict["p_loss"] = defaults["%idlingkw"] * defaults["kwrated"] / 1e3 + storageDict["q_loss"] = defaults["%idlingkvar"] * defaults["kvar"] / 1e3 + + storageDict["status"] = convert(Int, defaults["enabled"]) + + storageDict["ps"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + storageDict["qs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + + storageDict["index"] = length(pmd_data["storage"]) + 1 + + storageDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + storageDict["source_id"] = "storage.$(defaults["name"])" + + used = ["phases", "bus1", "name"] + _PMs._import_remaining!(storageDict, defaults, import_all; exclude=used) + + push!(pmd_data["storage"], storageDict) + end +end + + +"This function appends a component to a component dictionary of a pmd data model" +function _push_dict_ret_key!(dict::Dict{String, Any}, v::Dict{String, Any}; assume_no_gaps=false) + if isempty(dict) + k = 1 + elseif assume_no_gaps + k = length(keys(dict))+1 + else + k = maximum([parse(Int, x) for x in keys(dict)])+1 + end + + dict[string(k)] = v + v["index"] = k + return k +end + + +""" + _where_is_comp(data, comp_id) + +Finds existing component of id `comp_id` in array of `data` and returns index. +Assumes all components in `data` are unique. +""" +function _where_is_comp(data::Array, comp_id::AbstractString)::Int + for (i, e) in enumerate(data) + if e["name"] == comp_id + return i + end + end + return 0 +end + + +""" + _correct_duplicate_components!(dss_data) + +Finds duplicate components in `dss_data` and merges up, meaning that older +data (lower indices) is always overwritten by newer data (higher indices). +""" +function _correct_duplicate_components!(dss_data::Dict) + out = Dict{String,Array}() + for (k, v) in dss_data + if !(k in ["options"]) + out[k] = [] + for comp in v + if isa(comp, Dict) + idx = _where_is_comp(out[k], comp["name"]) + if idx > 0 + merge!(out[k][idx], comp) + else + push!(out[k], comp) + end + end + end + end + end + merge!(dss_data, out) +end + + +"Creates a virtual branch between the `virtual_sourcebus` and `sourcebus` with the impedance given by `circuit`" +function _create_sourcebus_vbranch_dm!(pmd_data::Dict, circuit::Dict) + #TODO convert to pu + rs = circuit["rmatrix"] + xs = circuit["xmatrix"] + + N = size(rs)[1] + + add_line!(pmd_data, id="_virtual_source_imp", + f_bus="_virtual_sourcebus", t_bus="sourcebus", + f_connections=collect(1:N), t_connections=collect(1:N), + rs=rs, xs=xs + ) + #vbranch = _create_vbranch!(pmd_data, sourcebus, vsourcebus; name="sourcebus_vbranch", br_r=br_r, br_x=br_x) +end + + +"Combines transformers with 'bank' keyword into a single transformer" +function _bank_transformers!(pmd_data::Dict) + transformer_names = Dict(trans["name"] => n for (n, trans) in get(pmd_data, "transformer_comp", Dict())) + bankable_transformers = [trans for trans in values(get(pmd_data, "transformer_comp", Dict())) if haskey(trans, "bank")] + banked_transformers = Dict() + for transformer in bankable_transformers + bank = transformer["bank"] + + if !(bank in keys(banked_transformers)) + n = length(pmd_data["transformer_comp"])+length(banked_transformers)+1 + + banked_transformers[bank] = deepcopy(transformer) + banked_transformers[bank]["name"] = deepcopy(transformer["bank"]) + banked_transformers[bank]["source_id"] = "transformer.$(transformer["bank"])" + banked_transformers[bank]["index"] = n + # set impedances / admittances to zero; only the specified phases should be non-zero + for key in ["rs", "xs", "bsh", "gsh"] + inds = key=="xs" ? keys(banked_transformers[bank][key]) : 1:length(banked_transformers[bank][key]) + for w in inds + banked_transformers[bank][key][w] *= 0 + end + end + delete!(banked_transformers[bank], "bank") + end + + banked_transformer = banked_transformers[bank] + for phase in transformer["active_phases"] + push!(banked_transformer["active_phases"], phase) + for (k, v) in banked_transformer + if isa(v, _PMs.MultiConductorVector) + banked_transformer[k][phase] = deepcopy(transformer[k][phase]) + elseif isa(v, _PMs.MultiConductorMatrix) + banked_transformer[k][phase, :] .= deepcopy(transformer[k][phase, :]) + elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorVector + # most properties are arrays (indexed over the windings) + for w in 1:length(v) + banked_transformer[k][w][phase] = deepcopy(transformer[k][w][phase]) + end + elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorMatrix + # most properties are arrays (indexed over the windings) + for w in 1:length(v) + banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) + end + elseif k=="xs" + # xs is a Dictionary indexed over pairs of windings + for w in keys(v) + banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) + end + end + end + end + end + + for transformer in bankable_transformers + delete!(pmd_data["transformer_comp"], transformer_names[transformer["name"]]) + end + + for transformer in values(banked_transformers) + pmd_data["transformer_comp"]["$(transformer["index"])"] = deepcopy(transformer) + end +end + + +""" + parse_options(options) + +Parses options defined with the `set` command in OpenDSS. +""" +function parse_options(options) + out = Dict{String,Any}() + if haskey(options, "voltagebases") + out["voltagebases"] = _parse_array(Float64, options["voltagebases"]) + end + + if !haskey(options, "defaultbasefreq") + Memento.warn(_LOGGER, "defaultbasefreq is not defined, default for circuit set to 60 Hz") + out["defaultbasefreq"] = 60.0 + else + out["defaultbasefreq"] = parse(Float64, options["defaultbasefreq"]) + end + + return out +end + + +"Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" +function parse_opendss_dm(dss_data::Dict; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict + pmd_data = create_data_model() + + _correct_duplicate_components!(dss_data) + + parse_dss_with_dtypes!(dss_data, ["line", "linecode", "load", "generator", "capacitor", + "reactor", "circuit", "transformer", "pvsystem", + "storage"]) + + if haskey(dss_data, "options") + condensed_opts = [Dict{String,Any}()] + for opt in dss_data["options"] + merge!(condensed_opts[1], opt) + end + dss_data["options"] = condensed_opts + end + + merge!(pmd_data, parse_options(get(dss_data, "options", [Dict{String,Any}()])[1])) + + #pmd_data["per_unit"] = false + #pmd_data["source_type"] = "dss" + #pmd_data["source_version"] = string(VersionNumber("0")) + + if haskey(dss_data, "circuit") + circuit = dss_data["circuit"][1] + defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), circuit["name"]; _to_sym_keys(circuit)...) + + #pmd_data["name"] = defaults["name"] + pmd_data["settings"]["v_var_scalar"] = 1E3 + pmd_data["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))*1E3/pmd_data["settings"]["v_var_scalar"] + pmd_data["settings"]["set_vbase_bus"] = "sourcebus" + + pmd_data["settings"]["set_sbase_val"] = defaults["basemva"]*1E6/pmd_data["settings"]["v_var_scalar"] + pmd_data["settings"]["basefreq"] = pop!(pmd_data, "defaultbasefreq") + #pmd_data["pu"] = defaults["pu"] + #pmd_data["conductors"] = defaults["phases"] + #pmd_data["sourcebus"] = defaults["bus1"] + else + Memento.error(_LOGGER, "Circuit not defined, not a valid circuit!") + end + + _dss2pmd_bus_dm!(pmd_data, dss_data, import_all, vmin, vmax) + _dss2pmd_line_dm!(pmd_data, dss_data, import_all) + _dss2pmd_transformer_dm!(pmd_data, dss_data, import_all) + + + _dss2pmd_load_dm!(pmd_data, dss_data, import_all) + _dss2pmd_shunt_dm!(pmd_data, dss_data, import_all) + + + #_dss2pmd_reactor!(pmd_data, dss_data, import_all) + #_dss2pmd_gen!(pmd_data, dss_data, import_all) + #_dss2pmd_pvsystem!(pmd_data, dss_data, import_all) + #_dss2pmd_storage!(pmd_data, dss_data, import_all) + + #pmd_data["dcline"] = [] + #pmd_data["switch"] = [] + + #InfrastructureModels.arrays_to_dicts!(pmd_data) + + if bank_transformers + _bank_transformers!(pmd_data) + end + + #_create_sourcebus_vbranch_dm!(pmd_data, defaults) + + _discover_terminals!(pmd_data) + + #_adjust_sourcegen_bounds!(pmd_data) + + #pmd_data["files"] = dss_data["filename"] + + return pmd_data +end + +function _discover_terminals!(pmd_data) + terminals = Dict{String, Set{Int}}([(bus["id"], Set{Int}()) for (_,bus) in pmd_data["bus"]]) + + for (_,line) in pmd_data["line"] + push!(terminals[line["f_bus"]], line["f_connections"]...) + push!(terminals[line["t_bus"]], line["t_connections"]...) + end + + if haskey(pmd_data, "transformer_nw3ph_lossy") + for (_,tr) in pmd_data["transformer_nw3ph_lossy"] + for w in tr["n_windings"] + push!(terminals[buses[w]], terminals[w]...) + end + end + end + + for (id, bus) in pmd_data["bus"] + pmd_data["bus"][id]["terminals"] = sort(collect(terminals[id])) + end + + # identify neutrals and propagate along cables + bus_neutral = _find_neutrals(pmd_data) + + for (id,bus) in pmd_data["bus"] + if haskey(bus, "awaiting_ground") || haskey(bus_neutral, id) + # this bus will need a neutral + if haskey(bus_neutral, id) + neutral = bus_neutral[id] + else + neutral = maximum(bus["terminals"])+1 + push!(bus["terminals"], neutral) + end + bus["neutral"] = neutral + if haskey(bus, "awaiting_ground") + bus["grounded"] = [neutral] + bus["rg"] = [0.0] + bus["xg"] = [0.0] + for comp in bus["awaiting_ground"] + if eltype(comp["connections"])<:Array + for w in 1:length(comp["connections"]) + if comp["bus"][w]==id + comp["connections"][w] .+= (comp["connections"][w].==0)*neutral + end + end + else + comp["connections"] .+= (comp["connections"].==0)*neutral + end + end + delete!(bus, "awaiting_ground") + end + end + phases = haskey(bus, "neutral") ? setdiff(bus["terminals"], bus["neutral"]) : bus["terminals"] + bus["phases"] = phases + end +end + +function _find_neutrals(pmd_data) + vertices = [(id, t) for (id, bus) in pmd_data["bus"] for t in bus["terminals"]] + neutrals = [] + edges = Set([((line["f_bus"], line["f_connections"][c]),(line["t_bus"], line["t_connections"][c])) for (id, line) in pmd_data["line"] for c in 1:length(line["f_connections"])]) + + bus_neutrals = [(id,bus["neutral"]) for (id,bus) in pmd_data["bus"] if haskey(bus, "neutral")] + trans_neutrals = [] + for (_, tr) in pmd_data["transformer_nw"] + for w in 1:length(tr["connections"]) + if tr["configuration"][w] == "wye" + @show tr + push!(trans_neutrals, (tr["bus"][w], tr["connections"][w][end])) + end + end + end + load_neutrals = [(load["bus"],load["connections"][end]) for (_,load) in pmd_data["load"] if load["configuration"]=="wye"] + neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) + neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) + stack = deepcopy(neutrals) + while !isempty(stack) + vertex = pop!(stack) + candidates_t = [((f,t), t) for (f,t) in edges if f==vertex] + candidates_f = [((f,t), f) for (f,t) in edges if t==vertex] + for (edge,next) in [candidates_t..., candidates_f...] + delete!(edges, edge) + push!(stack, next) + push!(neutrals, next) + end + end + bus_neutral = Dict{String, Int}() + for (bus,t) in neutrals + bus_neutral[bus] = t + end + return bus_neutral +end + + +"Parses a DSS file into a PowerModels usable format" +function parse_opendss_dm(io::IOStream; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict + dss_data = parse_dss(io) + + return parse_opendss_dm(dss_data; import_all=import_all) +end + + +""" + _get_conductors_ordered(busname; neutral=true) + +Returns an ordered list of defined conductors. If ground=false, will omit any `0` +""" +function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_length=true)::Array + parts = split(busname, '.'; limit=2) + ret = [] + if length(parts)==2 + conds_str = split(parts[2], '.') + ret = [parse(Int, i) for i in conds_str] + else + return default + end + + if check_length && length(default)!=length(ret) + Memento.error("An incorrect number of nodes was specified; |$(parts[2])|!=$(length(default)).") + end + return ret +end From 92ddbd77d51dee90ee024100bbe079e71fe353e9 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Mon, 24 Feb 2020 11:51:28 +0100 Subject: [PATCH 020/224] pf validated --- src/core/data_model_mapping.jl | 589 +++++++++++++++++++++++++++++++++ src/core/data_model_pu.jl | 314 ++++++++++++++++++ src/io/common_dm.jl | 41 +++ src/io/data_model_util.jl | 60 +++- src/io/opendss_dm.jl | 14 +- 5 files changed, 998 insertions(+), 20 deletions(-) create mode 100644 src/core/data_model_mapping.jl create mode 100644 src/core/data_model_pu.jl create mode 100644 src/io/common_dm.jl diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl new file mode 100644 index 000000000..4e8ed2cc8 --- /dev/null +++ b/src/core/data_model_mapping.jl @@ -0,0 +1,589 @@ +import LinearAlgebra + +# MAP DATA MODEL DOWN + +function data_model_map!(data_model) + + !haskey(data_model, "mappings") + + # needs to happen before _expand_linecode, as it might contain a linecode for the internal impedance + add_mappings!(data_model, "decompose_voltage_source", _decompose_voltage_source!(data_model)) + _expand_linecode!(data_model) + # creates shunt of 4x4; disabled for now (incompatible 3-wire kron-reduced) + #add_mappings!(data_model, "load_to_shunt", _load_to_shunt!(data_model)) + add_mappings!(data_model, "capacitor_to_shunt", _capacitor_to_shunt!(data_model)) + add_mappings!(data_model, "decompose_transformer_nw", _decompose_transformer_nw!(data_model)) + add_mappings!(data_model, "_lossy_ground_to_shunt", _lossy_ground_to_shunt!(data_model)) + + # add low level component types if not present yet + for comp_type in ["load", "generator", "bus", "line", "shunt", "transformer_2wa", "storage", "switch"] + if !haskey(data_model, comp_type) + data_model[comp_type] = Dict{String, Any}() + end + end + return data_model +end + + +function _expand_linecode!(data_model) + # expand line codes + for (id, line) in data_model["line"] + if haskey(line, "linecode") + linecode = data_model["linecode"][line["linecode"]] + for key in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + line[key] = line["length"]*linecode[key] + end + delete!(line, "linecode") + delete!(line, "length") + end + end + delete!(data_model, "linecode") +end + + +function _lossy_ground_to_shunt!(data_model) + mappings = [] + + if haskey(data_model, "bus") + for (id, bus) in data_model["bus"] + grounding_lossy_inds = [i for (i,t) in enumerate(bus["grounded"]) if bus["rg"][i]!=0 || bus["xg"][i]!=0] + grounding_lossy = bus["grounded"][grounding_lossy_inds] + grounding_perfect = bus["grounded"][setdiff(1:length(bus["grounded"]), grounding_lossy_inds)] + + if !isempty(grounding_lossy) + zg = bus["rg"][grounding_lossy_inds].+im*bus["xg"][grounding_lossy_inds] + Y_sh = LinearAlgebra.diagm(0=>inv.(zg)) # diagonal matrix, so matrix inverse is element-wise inverse + add_virtual!(data_model, "shunt", create_shunt(bus=bus["id"], connections=grounding_lossy, + g_sh=real.(Y_sh), b_sh=imag.(Y_sh) + )) + end + end + end + return mappings +end + + +function _load_to_shunt!(data_model) + mappings = [] + if haskey(data_model, "load") + for (id, load) in data_model["load"] + if load["model"]=="constant_impedance" + b = load["qd_ref"]./load["vnom"].^2*1E3 + g = load["pd_ref"]./load["vnom"].^2*1E3 + y = b.+im*g + N = length(b) + + if load["configuration"]=="delta" + # create delta transformation matrix Md + Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) + Md[N,1] = -1 + Y = Md'*LinearAlgebra.diagm(0=>y)*Md + + else # load["configuration"]=="wye" + Y_fr = LinearAlgebra.diagm(0=>y) + # B = [[b]; -1'*[b]]*[I -1] + Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) + end + + shunt = add_virtual!(data_model, "shunt", create_shunt(bus=load["bus"], connections=load["connections"], b_sh=imag.(Y), g_sh=real.(Y))) + + delete_component!(data_model, "load", load) + + push!(mappings, Dict( + "load" => load, + "shunt_id" => shunt["id"], + )) + end + end + end + + return mappings +end + + +function _capacitor_to_shunt!(data_model) + mappings = [] + + if haskey(data_model, "capacitor") + for (id, cap) in data_model["capacitor"] + b = cap["qd_ref"]./cap["vnom"]^2*1E3 + N = length(b) + + if cap["configuration"]=="delta" + # create delta transformation matrix Md + Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) + Md[N,1] = -1 + B = Md'*LinearAlgebra.diagm(0=>b)*Md + + elseif cap["configuration"]=="wye-grounded" + B = LinearAlgebra.diagm(0=>b) + + elseif cap["configuration"]=="wye-floating" + # this is a floating wye-segment + # B = [b]*(I-1/(b'*1)*[b';...;b']) + B = LinearAlgebra.diagm(0=>b)*(LinearAlgebra.diagm(0=>ones(N)) - 1/sum(b)*repeat(b',N,1)) + + else # cap["configuration"]=="wye" + B_fr = LinearAlgebra.diagm(0=>b) + # B = [[b]; -1'*[b]]*[I -1] + B = vcat(B_fr, -ones(N)'*B_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) + end + + shunt = create_shunt(NaN, cap["bus"], cap["connections"], b_sh=B) + add_virtual!(data_model, "shunt", shunt) + delete_component!(data_model, "capacitor", cap) + + push!(mappings, Dict( + "capacitor" => cap, + "shunt_id" => shunt["id"], + )) + end + end + + return mappings +end + + +function _decompose_voltage_source!(data_model) + mappings = [] + + if haskey(data_model, "voltage_source") + for (id, vs) in data_model["voltage_source"] + + bus = data_model["bus"][vs["bus"]] + + line_kwargs = Dict(Symbol(prop)=>vs[prop] for prop in ["rs", "xs", "g_fr", "b_fr", "g_to", "b_to", "linecode", "length"] if haskey(vs, prop)) + lossy = !isempty(line_kwargs) + + # if any loss parameters (or linecode) were supplied, then create a line and internal bus + if lossy + sourcebus = add_virtual!(data_model, "bus", create_bus(terminals=deepcopy(vs["connections"]))) + + line = add_virtual!(data_model, "line", create_line(; + f_bus=sourcebus["id"], f_connections=vs["connections"], t_bus=bus["id"], t_connections=vs["connections"], + line_kwargs... + )) + else + sourcebus = bus + end + + ground = _get_ground!(sourcebus) + gen = create_generator(bus=sourcebus["id"], connections=[vs["connections"]..., ground]) + + for prop in ["pg_max", "pg_min", "qg_max", "qg_min"] + if haskey(vs, prop) + gen[prop] = vs[prop] + end + end + + add_virtual!(data_model, "generator", gen) + + conns = vs["connections"] + terminals = bus["terminals"] + + tmp = Dict(enumerate(conns)) + sourcebus["vm"] = sourcebus["vmax"] = sourcebus["vmin"] = [haskey(tmp, t) ? vs["vm"][tmp[t]] : NaN for t in terminals] + sourcebus["va"] = [haskey(tmp, t) ? vs["va"][tmp[t]] : NaN for t in terminals] + sourcebus["bus_type"] = 3 + + delete_component!(data_model, "voltage_source", vs["id"]) + push!(mappings, Dict("voltage_source"=>vs, "gen_id"=>gen["id"], + "vbus_id" => lossy ? sourcebus["id"] : nothing, + "vline_id" => lossy ? line["id"] : nothing, + )) + end + end + + return mappings +end + + +""" + + function decompose_transformer_nw_lossy!(data_model) + +Replaces complex transformers with a composition of ideal transformers and lines +which model losses. New buses (virtual, no physical meaning) are added. +""" +function _decompose_transformer_nw!(data_model) + mappings = [] + + if haskey(data_model, "transformer_nw") + for (tr_id, trans) in data_model["transformer_nw"] + + vnom = trans["vnom"]*data_model["settings"]["v_var_scalar"] + snom = trans["snom"]*data_model["settings"]["v_var_scalar"] + + nrw = length(trans["bus"]) + + # calculate zbase in which the data is specified, and convert to SI + zbase = (vnom.^2)./snom + # x_sc is specified with respect to first winding + x_sc = trans["xsc"].*zbase[1] + # rs is specified with respect to each winding + r_s = trans["rs"].*zbase + + g_sh = (trans["noloadloss"]*snom[1]/3)/vnom[1]^2 + b_sh = (trans["imag"]*snom[1]/3)/vnom[1]^2 + + # data is measured externally, but we now refer it to the internal side + ratios = vnom/1E3 + x_sc = x_sc./ratios[1]^2 + r_s = r_s./ratios.^2 + g_sh = g_sh*ratios[1]^2 + b_sh = b_sh*ratios[1]^2 + + # convert x_sc from list of upper triangle elements to an explicit dict + y_sh = g_sh + im*b_sh + z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) + + vbuses, vlines, trans_t_bus_w = _build_loss_model!(data_model, r_s, z_sc, y_sh) + + trans_ids_w = Array{String, 1}(undef, nrw) + for w in 1:nrw + # 2-WINDING TRANSFORMER + # make virtual bus and mark it for reduction + tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] + trans_2wa = add_virtual!(data_model, "transformer_2wa", Dict( + "f_bus" => trans["bus"][w], + "t_bus" => trans_t_bus_w[w], + "tm_nom" => tm_nom, + "f_connections" => trans["connections"][w], + "t_connections" => collect(1:4), + "configuration" => trans["configuration"][w], + "polarity" => trans["polarity"][w], + "tm" => trans["tm"][w], + "fixed" => trans["fixed"][w], + )) + + for prop in ["tm_min", "tm_max", "tm_step"] + if haskey(trans, prop) + trans_2wa[prop] = trans[prop][w] + end + end + + trans_ids_w[w] = trans_2wa["id"] + end + + delete_component!(data_model, "transformer_nw", trans) + + push!(mappings, Dict( + "trans"=>trans, + "trans_2wa"=>trans_ids_w, + "vlines"=>vlines, + "vbuses"=>vbuses, + )) + end + end + + return mappings +end + + +""" +Converts a set of short-circuit tests to an equivalent reactance network. +Reference: +R. C. Dugan, “A perspective on transformer modeling for distribution system analysis,” +in 2003 IEEE Power Engineering Society General Meeting (IEEE Cat. No.03CH37491), 2003, vol. 1, pp. 114-119 Vol. 1. +""" +function _sc2br_impedance(Zsc) + N = maximum([maximum(k) for k in keys(Zsc)]) + # check whether no keys are missing + # Zsc should contain tupples for upper triangle of NxN + for i in 1:N + for j in i+1:N + if !haskey(Zsc, (i,j)) + if haskey(Zsc, (j,i)) + # Zsc is symmetric; use value of lower triangle if defined + Zsc[(i,j)] = Zsc[(j,i)] + else + Memento.error(_LOGGER, "Short-circuit impedance between winding $i and $j is missing.") + end + end + end + end + # make Zb + Zb = zeros(Complex{Float64}, N-1,N-1) + for i in 1:N-1 + Zb[i,i] = Zsc[(1,i+1)] + end + for i in 1:N-1 + for j in 1:i-1 + Zb[i,j] = (Zb[i,i]+Zb[j,j]-Zsc[(j+1,i+1)])/2 + Zb[j,i] = Zb[i,j] + end + end + # get Ybus + Y = LinearAlgebra.pinv(Zb) + Y = [-Y*ones(N-1) Y] + Y = [-ones(1,N-1)*Y; Y] + # extract elements + Zbr = Dict() + for k in keys(Zsc) + Zbr[k] = (abs(Y[k...])==0) ? Inf : -1/Y[k...] + end + return Zbr +end + + +function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) + # precompute the minimal set of buses and lines + N = length(r_s) + tr_t_bus = collect(1:N) + buses = Set(1:2*N) + edges = [[[i,i+N] for i in 1:N]..., [[i+N,j+N] for (i,j) in keys(zsc)]...] + lines = Dict(enumerate(edges)) + z = Dict(enumerate([r_s..., values(zsc)...])) + shunts = Dict(2=>ysh) + + # remove Inf lines + + for (l,edge) in lines + if real(z[l])==Inf || imag(z[l])==Inf + delete!(lines, l) + delete!(z, l) + end + end + + # merge short circuits + + stack = Set(keys(lines)) + + while !isempty(stack) + l = pop!(stack) + if z[l] == 0 + (i,j) = lines[l] + # remove line + delete!(lines, l) + # remove bus j + delete!(buses, j) + # update lines + for (k,(edge)) in lines + if edge[1]==j + edge[1] = i + end + if edge[2]==j + edge[2] = i + end + if edge[1]==edge[2] + delete!(lines, k) + delete!(stack, k) + end + end + # move shunts + if haskey(shunts, j) + if haskey(shunts, i) + shunts[i] += shunts[j] + else + shunts[i] = shunts[j] + end + end + # update transformer buses + for w in 1:N + if tr_t_bus[w]==j + tr_t_bus[w] = i + end + end + end + end + + bus_ids = Dict() + for bus in buses + bus_ids[bus] = add_virtual_get_id!(data_model, "bus", create_bus(id="")) + end + line_ids = Dict() + for (l,(i,j)) in lines + # merge the shunts into the shunts of the pi model of the line + g_fr = b_fr = g_to = b_to = 0 + if haskey(shunts, i) + g_fr = real(shunts[i]) + b_fr = imag(shunts[i]) + delete!(shunts, i) + end + if haskey(shunts, j) + g_fr = real(shunts[j]) + b_fr = imag(shunts[j]) + delete!(shunts, j) + end + line_ids[l] = add_virtual_get_id!(data_model, "line", Dict( + "status"=>1, + "f_bus"=>bus_ids[i], "t_bus"=>bus_ids[j], + "f_connections"=>collect(1:n_phases), + "t_connections"=>collect(1:n_phases), + "rs"=>LinearAlgebra.diagm(0=>fill(real(z[l]), n_phases)), + "xs"=>LinearAlgebra.diagm(0=>fill(imag(z[l]), n_phases)), + "g_fr"=>LinearAlgebra.diagm(0=>fill(g_fr, n_phases)), + "b_fr"=>LinearAlgebra.diagm(0=>fill(b_fr, n_phases)), + "g_to"=>LinearAlgebra.diagm(0=>fill(g_to, n_phases)), + "b_to"=>LinearAlgebra.diagm(0=>fill(b_to, n_phases)), + )) + end + + return bus_ids, line_ids, [bus_ids[bus] for bus in tr_t_bus] +end + +function _alias!(dict, fr, to) + if haskey(dict, fr) + dict[to] = dict[fr] + end +end + +function _pad_props!(comp, keys, phases_comp, phases_all) + pos = Dict((x,i) for (i,x) in enumerate(phases_all)) + inds = [pos[x] for x in phases_comp] + for prop in keys + if haskey(comp, prop) + if isa(comp[prop], Vector) + tmp = zeros(length(phases_all)) + tmp[inds] = comp[prop] + comp[prop] = tmp + elseif isa(comp[prop], Matrix) + tmp = zeros(length(phases_all), length(phases_all)) + tmp[inds, inds] = comp[prop] + comp[prop] = tmp + else + error("Property is not a vector or matrix!") + end + end + end +end + +function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) + data_model["conductors"] = 3 + data_model["buspairs"] = nothing + for (_, bus) in data_model["bus"] + bus["bus_i"] = bus["index"] + terminals = bus["terminals"] + @assert(all(t in [phases..., neutral] for t in terminals)) + for prop in ["vm", "va", "vmin", "vmax"] + if haskey(bus, prop) + if length(bus[prop])==4 + val = bus[prop] + bus[prop] = val[terminals.!=neutral] + end + end + end + end + + for (_, load) in data_model["load"] + # remove neutral + if load["configuration"]=="wye" + bus = data_model["bus"][string(load["bus"])] + @assert(length(bus["grounded"])==1 && bus["grounded"][1]==load["connections"][end]) + load["connections"] = load["connections"][1:end-1] + _pad_props!(load, ["pd", "qd"], load["connections"], phases) + else + # three-phase loads can only be delta-connected + #@assert(all(load["connections"].==phases)) + end + _alias!(load, "bus", "load_bus") + end + + data_model["gen"] = data_model["generator"] + + # has to be three-phase + for (_, gen) in data_model["gen"] + if gen["configuration"]=="wye" + @assert(all(gen["connections"].==[phases..., neutral])) + else + @assert(all(gen["connections"].==phases)) + end + + _alias!(gen, "status", "gen_status") + _alias!(gen, "bus", "gen_bus") + _alias!(gen, "pg_min", "pmin") + _alias!(gen, "qg_min", "qmin") + _alias!(gen, "pg_max", "pmax") + _alias!(gen, "qg_max", "qmax") + _alias!(gen, "configuration", "conn") + + gen["model"] = 2 + end + + data_model["branch"] = data_model["line"] + for (_, br) in data_model["branch"] + @assert(all(x in phases for x in br["f_connections"])) + @assert(all(x in phases for x in br["t_connections"])) + @assert(all(br["f_connections"].==br["t_connections"])) + + _pad_props!(br, ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to", "s_rating", "c_rating"], br["f_connections"], phases) + + # rename + _alias!(br, "status", "br_status") + _alias!(br, "rs", "br_r") + _alias!(br, "xs", "br_x") + + br["tap"] = 1.0 + br["shift"] = 0 + + if !haskey(br, "angmin") + N = size(br["br_r"])[1] + br["angmin"] = fill(-pi/2, N) + br["angmax"] = fill(pi/2, N) + end + end + + for (_, shunt) in data_model["shunt"] + @assert(all(x in phases for x in shunt["connections"])) + _pad_props!(shunt, ["g_sh", "b_sh"], shunt["connections"], phases) + _alias!(shunt, "bus", "shunt_bus") + _alias!(shunt, "g_sh", "gs") + _alias!(shunt, "b_sh", "bs") + end + + data_model["dcline"] = Dict() + data_model["transformer"] = data_model["transformer_2wa"] + + data_model["per_unit"] = true + data_model["baseMVA"] = data_model["settings"]["sbase"]*data_model["settings"]["v_var_scalar"]/1E6 + data_model["name"] = "IDC" + + + return data_model +end + +# MAP SOLUTION UP + +function solution_unmap!(solution::Dict, data_model::Dict) + for i in length(data_model["mappings"]):-1:1 + (name, data) = data_model["mappings"][i] + + if name=="decompose_transformer_nw" + for bus_id in values(data["vbuses"]) + delete!(solution["bus"], bus_id) + end + + for line_id in values(data["vlines"]) + delete!(solution["branch"], line_id) + end + + pt = [solution["transformer"][tr_id]["pf"] for tr_id in data["trans_2wa"]] + qt = [solution["transformer"][tr_id]["qf"] for tr_id in data["trans_2wa"]] + for tr_id in data["trans_2wa"] + delete!(solution["transformer"], tr_id) + end + + add_solution!(solution, "transformer_nw", data["trans"]["id"], Dict("pt"=>pt, "qt"=>qt)) + elseif name=="capacitor_to_shunt" + # shunt has no solutions defined + delete_solution!(solution, "shunt", data["shunt_id"]) + add_solution!(solution, "capacitor", data["capacitor"]["id"], Dict()) + elseif name=="load_to_shunt" + # shunt has no solutions, but a load should have! + delete!(solution, "shunt", data["shunt_id"]) + add_solution!(solution, "load", data["load"]["id"], Dict()) + elseif name=="decompose_voltage_source" + gen = solution["gen"][data["gen_id"]] + delete_solution!(solution, "gen", data["gen_id"]) + add_solution!(solution, "voltage_source", data["voltage_source"]["id"], Dict("pg"=>gen["pg"], "qg"=>gen["qg"])) + + end + end + + # remove component dicts if empty + for (comp_type, comp_dict) in solution + if isa(comp_dict, Dict) && isempty(comp_dict) + delete!(solution, comp_type) + end + end +end diff --git a/src/core/data_model_pu.jl b/src/core/data_model_pu.jl new file mode 100644 index 000000000..f563b1a67 --- /dev/null +++ b/src/core/data_model_pu.jl @@ -0,0 +1,314 @@ + +function _find_zones(data_model) + unused_line_ids = Set(keys(data_model["line"])) + bus_lines = Dict([(id,Set()) for id in keys(data_model["bus"])]) + for (line_id,line) in data_model["line"] + f_bus = line["f_bus"] + t_bus = line["t_bus"] + push!(bus_lines[f_bus], (line_id,t_bus)) + push!(bus_lines[t_bus], (line_id,f_bus)) + end + zones = [] + buses = Set(keys(data_model["bus"])) + while !isempty(buses) + stack = [pop!(buses)] + zone = Set() + while !isempty(stack) + bus = pop!(stack) + delete!(buses, bus) + push!(zone, bus) + for (line_id,bus_to) in bus_lines[bus] + if line_id in unused_line_ids && bus_to in buses + delete!(unused_line_ids, line_id) + push!(stack, bus_to) + end + end + end + append!(zones, [zone]) + end + zones = Dict(enumerate(zones)) + + return zones +end + + +function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) + # find zones of buses connected by lines + zones = _find_zones(data_model) + bus_to_zone = Dict([(bus,zone) for (zone, buses) in zones for bus in buses]) + + # assign specified vbase to corresponding zones + zone_vbase = Dict{Int, Union{Missing,Real}}([(zone,missing) for zone in keys(zones)]) + for (bus,vbase) in vbase_sources + if !ismissing(zone_vbase[bus_to_zone[bus]]) + Memento.warn("You supplied multiple voltage bases for the same zone; ignoring all but the last one.") + end + zone_vbase[bus_to_zone[bus]] = vbase + end + + # transformers form the edges between these zones + zone_edges = Dict([(zone,[]) for zone in keys(zones)]) + edges = Set() + for (i,(_,trans)) in enumerate(data_model["transformer_2wa"]) + push!(edges,i) + f_zone = bus_to_zone[trans["f_bus"]] + t_zone = bus_to_zone[trans["t_bus"]] + tm_nom = trans["configuration"]=="delta" ? trans["tm_nom"]/sqrt(3) : trans["tm_nom"] + push!(zone_edges[f_zone], (i, t_zone, 1/tm_nom)) + push!(zone_edges[t_zone], (i, f_zone, tm_nom)) + end + + # initialize the stack with all specified zones + stack = [zone for (zone,vbase) in zone_vbase if !ismissing(vbase)] + + while !isempty(stack) + + zone = pop!(stack) + + for (edge_id, zone_to, scale) in zone_edges[zone] + delete!(edges, edge_id) + + if ismissing(zone_vbase[zone_to]) + zone_vbase[zone_to] = zone_vbase[zone]*scale + push!(stack, zone_to) + end + end + end + + bus_vbase = Dict([(bus,zone_vbase[zone]) for (bus,zone) in bus_to_zone]) + line_vbase = Dict([(id, bus_vbase[line["f_bus"]]) for (id,line) in data_model["line"]]) + return (bus_vbase, line_vbase) +end + + +function data_model_make_pu!(data_model; sbase=missing, vbases=missing) + v_var_scalar = data_model["settings"]["v_var_scalar"] + + if haskey(data_model["settings"], "sbase") + sbase_old = data_model["settings"]["sbase"] + else + sbase_old = 1 + end + + if ismissing(sbase) + if haskey(data_model["settings"], "set_sbase_val") + sbase = data_model["settings"]["set_sbase_val"] + else + sbase = 1 + end + end + + # automatically find a good vbase + if ismissing(vbases) + if haskey(data_model["settings"], "set_vbase_val") && haskey(data_model["settings"], "set_vbase_bus") + vbases = Dict(data_model["settings"]["set_vbase_bus"]=>data_model["settings"]["set_vbase_val"]) + else + buses_type_3 = [(id, sum(bus["vm"])/length(bus["vm"])) for (id,bus) in data_model["bus"] if haskey(bus, "bus_type") && bus["bus_type"]==3] + if !isempty(buses_type_3) + vbases = Dict([buses_type_3[1]]) + else + Memento.error("Please specify vbases manually; cannot make an educated guess for this data model.") + end + end + end + + bus_vbase, line_vbase = _calc_vbase(data_model, vbases) + + for (id, bus) in data_model["bus"] + _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, v_var_scalar) + end + + for (id, line) in data_model["line"] + vbase = line_vbase[id] + _rebase_pu_line!(line, line_vbase[id], sbase, sbase_old, v_var_scalar) + end + + for (id, shunt) in data_model["shunt"] + _rebase_pu_shunt!(shunt, bus_vbase[shunt["bus"]], sbase, sbase_old, v_var_scalar) + end + + for (id, load) in data_model["load"] + _rebase_pu_load!(load, bus_vbase[load["bus"]], sbase, sbase_old, v_var_scalar) + end + + for (id, gen) in data_model["generator"] + _rebase_pu_generator!(gen, bus_vbase[gen["bus"]], sbase, sbase_old, v_var_scalar) + end + + for (id, trans) in data_model["transformer_2wa"] + # voltage base across transformer does not have to be consistent with the ratio! + f_vbase = bus_vbase[trans["f_bus"]] + t_vbase = bus_vbase[trans["t_bus"]] + _rebase_pu_transformer_2w_ideal!(trans, f_vbase, t_vbase, sbase_old, sbase, v_var_scalar) + end + + data_model["settings"]["sbase"] = sbase + + return data_model +end + + +function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) + + # if not in p.u., these are normalized with respect to vnom + prop_vnom = ["vm", "vmax", "vmin", "vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] + + if !haskey(bus, "vbase") + + # if haskey(bus, "vnom") + # vnom = bus["vnom"] + # _scale_props!(bus, ["vnom"], 1/vbase) + # end + _scale_props!(bus, prop_vnom, 1/vbase) + + z_old = 1.0 + else + vbase_old = bus["vbase"] + _scale_props!(bus, [prop_vnom..., "vnom"], vbase_old/vbase) + + z_old = vbase_old^2*sbase_old*v_var_scalar + end + + # rebase grounding resistance + z_new = vbase^2/sbase*v_var_scalar + z_scale = z_old/z_new + _scale_props!(bus, ["rg", "xg"], z_scale) + + # save new vbase + bus["vbase"] = vbase +end + + +function _rebase_pu_line!(line, vbase, sbase, sbase_old, v_var_scalar) + + if !haskey(line, "vbase") + z_old = 1 + else + z_old = vbase_old^2/sbase_old*v_var_scalar + end + + z_new = vbase^2/sbase*v_var_scalar + z_scale = z_old/z_new + y_scale = 1/z_scale + + _scale_props!(line, ["rs", "xs"], z_scale) + _scale_props!(line, ["b_fr", "g_fr", "b_to", "g_to"], y_scale) + + # save new vbase + line["vbase"] = vbase +end + + +function _rebase_pu_shunt!(shunt, vbase, sbase, sbase_old, v_var_scalar) + + if !haskey(shunt, "vbase") + z_old = 1 + else + z_old = vbase_old^2/sbase_old*v_var_scalar + end + + # rebase grounding resistance + z_new = vbase^2/sbase*v_var_scalar + z_scale = z_old/z_new + y_scale = 1/z_scale + scale(shunt, "g_sh", y_scale) + scale(shunt, "b_sh", y_scale) + + # save new vbase + shunt["vbase"] = vbase +end + + +function _rebase_pu_load!(load, vbase, sbase, sbase_old, v_var_scalar) + + if !haskey(load, "vbase") + vbase_old = 1 + sbase_old = 1 + else + vbase_old = load["vbase"] + end + + vbase_old = get(load, "vbase", 1.0) + vbase_scale = vbase_old/vbase + scale(load, "vnom", vbase_scale) + + sbase_scale = sbase_old/sbase + scale(load, "pd", sbase_scale) + scale(load, "qd", sbase_scale) + + # save new vbase + load["vbase"] = vbase +end + + +function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar) + vbase_old = get(gen, "vbase", 1.0/v_var_scalar) + vbase_scale = vbase_old/vbase + sbase_scale = sbase_old/sbase + + for key in ["pd_set", "qd_set", "pd_min", "qd_min", "pd_max", "qd_max"] + scale(gen, key, sbase_scale) + end + + scale(gen, "cost", 1/sbase_scale) + + # save new vbase + gen["vbase"] = vbase +end + + +function _rebase_pu_transformer_2w_ideal!(trans, f_vbase_new, t_vbase_new, sbase_old, sbase_new, v_var_scalar) + f_vbase_old = get(trans, "f_vbase", 1.0) + t_vbase_old = get(trans, "t_vbase", 1.0) + f_vbase_scale = f_vbase_old/f_vbase_new + t_vbase_scale = t_vbase_old/t_vbase_new + + scale(trans, "tm_nom", f_vbase_scale/t_vbase_scale) + + # save new vbase + trans["f_vbase"] = f_vbase_new + trans["t_vbase"] = t_vbase_new +end + + +function _scale_props!(comp::Dict{String, Any}, prop_names::Array{String, 1}, scale::Real) + for name in prop_names + if haskey(comp, name) + comp[name] *= scale + end + end +end + +#data_model_user = make_test_data_model() +#data_model_base = map_down_data_model(data_model_user) +#bus_vbase, line_vbase = get_vbase(data_model_base, Dict("1"=>230.0)) + +#make_pu!(data_model_base) + +function add_big_M!(data_model; kwargs...) + big_M = Dict{String, Any}() + + big_M["v_phase_pu_min"] = add_kwarg!(big_M, kwargs, :v_phase_pu_min, 0.5) + big_M["v_phase_pu_max"] = add_kwarg!(big_M, kwargs, :v_phase_pu_max, 2.0) + big_M["v_neutral_pu_min"] = add_kwarg!(big_M, kwargs, :v_neutral_pu_min, 0.0) + big_M["v_neutral_pu_max"] = add_kwarg!(big_M, kwargs, :v_neutral_pu_max, 0.5) + + data_model["big_M"] = big_M +end + +function solution_unmake_pu!(solution, data_model) + sbase = data_model["sbase"] + for (comp_type, comp_dict) in [(x,y) for (x,y) in solution if isa(y, Dict)] + for (id, comp) in comp_dict + for (prop, val) in comp + if any([occursin(x, prop) for x in ["p", "q"]]) + comp[prop] = val*sbase + elseif any([occursin(x, prop) for x in ["vm", "vr", "vi"]]) + comp[prop] = val*data_model[comp_type][id]["vbase"] + end + end + end + end + + return solution +end diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl new file mode 100644 index 000000000..65393f9c2 --- /dev/null +++ b/src/io/common_dm.jl @@ -0,0 +1,41 @@ +""" + parse_file(io) + +Parses the IOStream of a file into a Three-Phase PowerModels data structure. +""" +function parse_file_dm(io::IO; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, filetype::AbstractString="json", bank_transformers::Bool=true) + if filetype == "m" + pmd_data = PowerModelsDistribution.parse_matlab(io) + elseif filetype == "dss" + Memento.warn(_LOGGER, "Not all OpenDSS features are supported, currently only minimal support for lines, loads, generators, and capacitors as shunts. Transformers and reactors as transformer branches are included, but value translation is not fully supported.") + pmd_data = PowerModelsDistribution.parse_opendss_dm(io; import_all=import_all, vmin=vmin, vmax=vmax, bank_transformers=bank_transformers) + elseif filetype == "json" + pmd_data = PowerModels.parse_json(io; validate=false) + else + Memento.error(_LOGGER, "only .m and .dss files are supported") + end + + #correct_network_data!(pmd_data) + + return pmd_data +end + + +"" +function parse_file_dm(file::String; kwargs...) + pmd_data = open(file) do io + parse_file_dm(io; filetype=split(lowercase(file), '.')[end], kwargs...) + end + + # to get the old indexing + pmd_data_old = open(file) do io + parse_file(io; filetype=split(lowercase(file), '.')[end], kwargs...) + end + index_presets = Dict(comp_type=>Dict(comp["name"]=>comp["index"] for (id, comp) in pmd_data_old[comp_type]) for comp_type in ["bus"]) + + data_model_map!(pmd_data) + data_model_make_pu!(pmd_data) + data_model_index!(pmd_data; index_presets=index_presets) + data_model_make_compatible_v8!(pmd_data) + return pmd_data +end diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl index 7b88765d0..a3ae0381c 100644 --- a/src/io/data_model_util.jl +++ b/src/io/data_model_util.jl @@ -50,30 +50,62 @@ function add_mappings!(data_model::Dict{String, Any}, mapping_type::String, mapp end -function data_model_index!(data_model; components=["line", "shunt", "generator", "load", "transformer_2wa"]) - bus_id2ind = Dict() - - for (i, id) in enumerate(keys(data_model["bus"])) - data_model["bus"][id]["index"] = i - bus_id2ind[id] = i +function _get_next_index(last_index, presets) + new_index = last_index+1 + while new_index in presets + new_index += 1 end - data_model["bus"] = Dict{String, Any}(string(bus_id2ind[id])=>bus for (id, bus) in data_model["bus"]) + return new_index +end + +function data_model_index!(data_model; components=["bus", "line", "shunt", "generator", "load", "transformer_2wa"], index_presets=Dict()) + comp_id2ind = Dict() + + # bus should be the first component, because we want to for comp_type in components comp_dict = Dict{String, Any}() - for (i,(id,comp)) in enumerate(data_model[comp_type]) - @assert(!haskey(comp, "index"), "$comp_type $id: component already has an index.") - comp["index"] = i - comp["id"] = id - comp_dict["$i"] = comp + + if !haskey(index_presets, comp_type) + for (i,(id,comp)) in enumerate(data_model[comp_type]) + @assert(!haskey(comp, "index"), "$comp_type $id: component already has an index.") + comp["index"] = i + comp["id"] = id + comp_dict["$i"] = comp + end + else + last_index = 0 + + for (id, comp) in data_model[comp_type] + @assert(!haskey(comp, "index"), "$comp_type $id: component already has an index.") + if haskey(index_presets[comp_type], id) + println("$comp_type: $id found, ->$(index_presets[comp_type][id])") + comp["index"] = index_presets[comp_type][id] + else + comp["index"] = _get_next_index(last_index, values(index_presets[comp_type])) + last_index = comp["index"] + end + + comp["id"] = id + comp_dict["$(comp["index"])"] = comp + end + end + + data_model[comp_type] = comp_dict + comp_id2ind[comp_type] = Dict(comp["id"]=>comp["index"] for comp in values(comp_dict)) + end + + # update bus references + for comp_type in components + for (_, comp) in data_model[comp_type] for bus_key in ["f_bus", "t_bus", "bus"] if haskey(comp, bus_key) - comp[bus_key] = bus_id2ind[comp[bus_key]] + comp[bus_key] = comp_id2ind["bus"][comp[bus_key]] end end end - data_model[comp_type] = comp_dict end + return data_model end diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 564f8b9e2..fdc5679aa 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -581,7 +581,7 @@ function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) line["circuit_basefreq"] = pmd_data["settings"]["basefreq"] end - defaults = _apply_ordered_properties(_create_line(line["bus1"], line["bus2"], line["name"]; _to_sym_keys(line)...), line; linecode=linecode) + defaults = _apply_ordered_properties(_create_line(line["bus1"], line["bus2"], line["name"]; _to_sym_keys(line)...), line; code_dict=linecode) lineDict = Dict{String,Any}() @@ -591,12 +591,15 @@ function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) #lineDict["length"] = defaults["length"] - nphases = defaults["phases"] + #TODO nphases not being read correctly from linecode; infer indirectly instead + nphases = size(defaults["rmatrix"])[1] lineDict["n_conductors"] = nphases - rmatrix = defaults["rmatrix"] - xmatrix = defaults["xmatrix"] - cmatrix = defaults["cmatrix"] + #TODO fix this in a cleaner way + # ensure that this actually is a matrix and not a vector for 1x1 data + rmatrix = reshape(defaults["rmatrix"], nphases, nphases) + xmatrix = reshape(defaults["xmatrix"], nphases, nphases) + cmatrix = reshape(defaults["cmatrix"], nphases, nphases) lineDict["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) lineDict["t_connections"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) @@ -1174,7 +1177,6 @@ function _find_neutrals(pmd_data) for (_, tr) in pmd_data["transformer_nw"] for w in 1:length(tr["connections"]) if tr["configuration"][w] == "wye" - @show tr push!(trans_neutrals, (tr["bus"][w], tr["connections"][w][end])) end end From 7da85b89af5490a0edf3fad0190303793686b3d8 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 24 Feb 2020 11:07:49 -0700 Subject: [PATCH 021/224] DOC: Data model documentation formatting --- src/io/data_model.md | 253 ++++++++++++++++++++++--------------------- 1 file changed, 129 insertions(+), 124 deletions(-) diff --git a/src/io/data_model.md b/src/io/data_model.md index 9321a962d..41d18a199 100644 --- a/src/io/data_model.md +++ b/src/io/data_model.md @@ -1,5 +1,6 @@ # Shared patterns + - Each component has a unique (amonst components of the same type) identifier `id`. - Everything is defined in SI units, except when a base is explicitly mentioned in the description. - The default sometimes only mentions the default element, not the default value (e.g. `true` and not `fill(fill(true, 3), 3)`) @@ -9,180 +10,184 @@ ## Bus The data model below allows us to include buses of arbitrary many terminals (i.e., more than the usual four). This would be useful for + - underground lines with multiple neutrals which are not joined at every bus; - distribution lines that carry several conventional lines in parallel (see for example the quad circuits in NEVTestCase). -name | default | type | description -------|----------|-----|----------- -terminals|[1,2,3,4]|Vector -vm_max | / | Vector | maximum conductor-to-ground voltage magnitude -vm_min | / | Vector | minimum conductor-to-ground voltage magnitude -vm_cd_max | / | Vector{Tuple} | e.g. [(1,2,210)] means \|U1-U2\|>210 -vm_cd_min | / | Vector{Tuple} | e.g. [(1,2,230)] means \|U1-U2\|<230 -grounded | [] | Vector | a list of terminals which are grounded -rg | [] | Vector | resistance of each defined grounding -xg | [] | Vector | reactance of each defined grounding +| name | default | type | description | +| --------- | --------- | ------------- | --------------------------------------------- | +| terminals | [1,2,3,4] | Vector | +| vm_max | / | Vector | maximum conductor-to-ground voltage magnitude | +| vm_min | / | Vector | minimum conductor-to-ground voltage magnitude | +| vm_cd_max | / | Vector{Tuple} | e.g. [(1,2,210)] means \|U1-U2\|>210 | +| vm_cd_min | / | Vector{Tuple} | e.g. [(1,2,230)] means \|U1-U2\|<230 | +| grounded | [] | Vector | a list of terminals which are grounded | +| rg | [] | Vector | resistance of each defined grounding | +| xg | [] | Vector | reactance of each defined grounding | The tricky part is how to encode bounds for these type of buses. The most general is defining a list of three-tupples. Take for example a typical bus in a three-phase, four-wire network, where `terminals=[a,b,c,n]`. Such a bus might have + - phase-to-neutral bounds `vm_pn_max=250`, `vm_pn_min=210` -- and phase-to-phase bounds `vm_pp_max=440`, `vm_pp_min=360 -`. +- and phase-to-phase bounds `vm_pp_max=440`, `vm_pp_min=360`. We can then define this equivalently as + - `vm_cd_max = [(a,n,250), (b,n,250), (c,n,250), (a,b,440), (b,c,440), (c,a,440)]` - `vm_cd_min = [(a,n,210), (b,n,210), (c,n,210), (a,b,360), (b,c,360), (c,a,360)]` Since this might be confusing for novice users, we also allow the user to define bounds through the following properties. -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -phases | [1,2,3] | Vector -neutral | 4 | | maximum conductor-to-ground voltage magnitude -vm_pn_max | / | Real | maximum phase-to-neutral voltage magnitude for all phases -vm_pn_min | / | Real | minimum phase-to-neutral voltage magnitude for all phases -vm_pp_max | / | Real | maximum phase-to-phase voltage magnitude for all phases -vm_pp_min | / | Real | minimum phase-to-phase voltage magnitude for all phases +| name | default | type | description | +| --------- | ------- | ------ | --------------------------------------------------------- | +| id | / | | unique identifier | +| phases | [1,2,3] | Vector | +| neutral | 4 | | maximum conductor-to-ground voltage magnitude | +| vm_pn_max | / | Real | maximum phase-to-neutral voltage magnitude for all phases | +| vm_pn_min | / | Real | minimum phase-to-neutral voltage magnitude for all phases | +| vm_pp_max | / | Real | maximum phase-to-phase voltage magnitude for all phases | +| vm_pp_min | / | Real | minimum phase-to-phase voltage magnitude for all phases | ## Line This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` and `g_to`; when these properties are additionally specified, they overwrite the one supplied through the linecode. -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -f_bus | / | | -f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects -t_bus | / | | -t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects -linecode | / | | a linecode -rs | / | | series resistance matrix, size of n_conductors x n_conductors -xs | / | | series reactance matrix, size of n_conductors x n_conductors -g_fr | / | | from-side conductance -b_fr | / | | from-side susceptance -g_to | / | | to-side conductance -b_to | / | | to-side susceptance -c_rating | / | | symmetrically applicable current rating -s_rating | / | | symmetrically applicable power rating +| name | default | type | description | +| ------------- | ------- | ---- | ------------------------------------------------------------------------ | +| id | / | | unique identifier | +| f_bus | / | | +| f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | +| t_bus | / | | +| t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | +| linecode | / | | a linecode | +| rs | / | | series resistance matrix, size of n_conductors x n_conductors | +| xs | / | | series reactance matrix, size of n_conductors x n_conductors | +| g_fr | / | | from-side conductance | +| b_fr | / | | from-side susceptance | +| g_to | / | | to-side conductance | +| b_to | / | | to-side susceptance | +| c_rating | / | | symmetrically applicable current rating | +| s_rating | / | | symmetrically applicable power rating | ## Linecode - Should the linecode also include a `c_rating` and/`s_rating`? -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -rs | / | | series resistance matrix -xs | / | | series reactance matrix n_conductors -g_fr | / | | from-side conductance -b_fr | / | | from-side susceptance -g_to | / | | to-side conductance -b_to | / | | to-side susceptance +| name | default | type | description | +| ---- | ------- | ---- | ------------------------------------ | +| id | / | | unique identifier | +| rs | / | | series resistance matrix | +| xs | / | | series reactance matrix n_conductors | +| g_fr | / | | from-side conductance | +| b_fr | / | | from-side susceptance | +| g_to | / | | to-side conductance | +| b_to | / | | to-side susceptance | ## Shunt -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -bus | / | | -connections | / | | -g | / | | conductance, size should be \|connections\|x\|connections\| -b | / | | susceptance, size should be \|connections\|x\|connections\| +| name | default | type | description | +| ----------- | ------- | ---- | ---------------------------------------------------------- | +| id | / | | unique identifier | +| bus | / | | +| connections | / | | +| g | / | | conductance, size should be \|connections\|x\|connections\ | +| b | / | | susceptance, size should be \|connections\|x\|connections\ | ## Capacitor -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -bus | / | | -connections | / | | -qd_ref | / | | conductance, size should be \|connections\|x\|connections\| -vnom | / | | conductance, size should be \|connections\|x\|connections\| +| name | default | type | description | +| ----------- | ------- | ---- | ---------------------------------------------------------- | +| id | / | | unique identifier | +| bus | / | | +| connections | / | | +| qd_ref | / | | conductance, size should be \|connections\|x\|connections\ | +| vnom | / | | conductance, size should be \|connections\|x\|connections\ | ## Load -name | default | type | description -------|----------|----|----------- -id | / | | unique identifier -bus | / | | -connections | / | | -configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral -model | / | | indicates the type of voltage-dependency +| name | default | type | description | +| ------------- | ------- | ------------ | --------------------------------------------------------------- | +| id | / | | unique identifier | +| bus | / | | +| connections | / | | +| configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | +| model | / | | indicates the type of voltage-dependency | ### `model=constant_power` -name | default | type | description -------|----------|----|----------- -pd | / | Vector{Real} | -qd | / | Vector{Real} | +| name | default | type | description | +| ---- | ------- | ------------ | ----------- | +| pd | / | Vector{Real} | +| qd | / | Vector{Real} | ### `model=constant_current/impedance` -name | default | type | description -------|----------|----|----------- -pd_ref | / | Vector{Real} | -qd_ref | / | Vector{Real} | -vnom | / | Real | +| name | default | type | description | +| ------ | ------- | ------------ | ----------- | +| pd_ref | / | Vector{Real} | +| qd_ref | / | Vector{Real} | +| vnom | / | Real | ### `model=exponential` -name | default | type | description -------|----------|----|----------- -pd_ref | / | Vector{Real} | -qd_ref | / | Vector{Real} | -vnom | / | Real | -exp_p | / | Vector{Real} | -exp_q | / | Vector{Real} | +| name | default | type | description | +| ------ | ------- | ------------ | ----------- | +| pd_ref | / | Vector{Real} | +| qd_ref | / | Vector{Real} | +| vnom | / | Real | +| exp_p | / | Vector{Real} | +| exp_q | / | Vector{Real} | ## Generator -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -bus | / | | -connections | / | | -configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral -pg_min | / | | lower bound on active power generation per phase -pg_max | / | | upper bound on active power generation per phase -qg_min | / | | lower bound on reactive power generation per phase -qg_max | / | | upper bound on reactive power generation per phase - -## AL2W Transformer +| name | default | type | description | +| ------------- | ------- | ------------ | --------------------------------------------------------------- | +| id | / | | unique identifier | +| bus | / | | +| connections | / | | +| configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | +| pg_min | / | | lower bound on active power generation per phase | +| pg_max | / | | upper bound on active power generation per phase | +| qg_min | / | | lower bound on reactive power generation per phase | +| qg_max | / | | upper bound on reactive power generation per phase | + +## Assymetric, Lossless, Two-Winding (AL2W) Transformer + These are transformers are assymetric (A), lossless (L) and two-winding (2W). Assymetric refers to the fact that the secondary is always has a `wye` configuration, whilst the primary can be `delta`. -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -n_phases | size(rs)[1] | Int>0 | number of phases -f_bus | / | | -f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects -t_bus | / | | -t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects -configuration | / | {wye, delta} | for the from-side; the to-side is always connected in wye -tm_nom | / | Real | nominal tap ratio for the transformer -tm_max | / | Vector | maximum tap ratio for each phase (base=`tm_nom`) -tm_min | / | Vector | minimum tap ratio for each phase (base=`tm_nom`) -tm_set | fill(1.0, `n_phases`) | Vector | set tap ratio for each phase (base=`tm_nom`) -tm_fix | fill(true, `n_phases`) | Vector | indicates for each phase whether the tap ratio is fixed +| name | default | type | description | +| ------------- | ---------------------- | ------------ | ------------------------------------------------------------------------ | +| id | / | | unique identifier | +| n_phases | size(rs)[1] | Int>0 | number of phases | +| f_bus | / | | +| f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | +| t_bus | / | | +| t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | +| configuration | / | {wye, delta} | for the from-side; the to-side is always connected in wye | +| tm_nom | / | Real | nominal tap ratio for the transformer | +| tm_max | / | Vector | maximum tap ratio for each phase (base=`tm_nom`) | +| tm_min | / | Vector | minimum tap ratio for each phase (base=`tm_nom`) | +| tm_set | fill(1.0, `n_phases`) | Vector | set tap ratio for each phase (base=`tm_nom`) | +| tm_fix | fill(true, `n_phases`) | Vector | indicates for each phase whether the tap ratio is fixed | TODO: add tm stuff ## Transformer + These are n-winding, n-phase, lossy transformers. Note that most properties are now Vectors (or Vectors of Vectors), indexed over the windings. -name | default | type | description -------|----------|-----|----------- -id | / | | unique identifier -n_phases | size(rs)[1] | Int>0 | number of phases -n_windings | size(rs)[1] | Int>0 | number of windings -bus | / | Vector | list of bus for each winding -connections | | Vector{Vector} | list of connection for each winding -configurations | | Vector{{wye, delta}} | list of configuration for each winding -xsc | 0.0 | Vector | list of short-circuit reactances between each pair of windings; enter as a list of the upper-triangle elements -rs | 0.0 | Vector | list of the winding resistance for each winding -tm_nom | / | Vector{Real} | nominal tap ratio for the transformer -tm_max | / | Vector{Vector} | maximum tap ratio for each winding and phase (base=`tm_nom`) -tm_min | / | Vector{Vector} | minimum tap ratio for for each winding and phase (base=`tm_nom`) -tm_set | 1.0 | Vector{Vector} | set tap ratio for each winding and phase (base=`tm_nom`) -tm_fix | true | Vector{Vector} | indicates for each winding and phase whether the tap ratio is fixed +| name | default | type | description | +| -------------- | ----------- | -------------------- | -------------------------------------------------------------------------------------------------------------- | +| id | / | | unique identifier | +| n_phases | size(rs)[1] | Int>0 | number of phases | +| n_windings | size(rs)[1] | Int>0 | number of windings | +| bus | / | Vector | list of bus for each winding | +| connections | | Vector{Vector} | list of connection for each winding | +| configurations | | Vector{{wye, delta}} | list of configuration for each winding | +| xsc | 0.0 | Vector | list of short-circuit reactances between each pair of windings; enter as a list of the upper-triangle elements | +| rs | 0.0 | Vector | list of the winding resistance for each winding | +| tm_nom | / | Vector{Real} | nominal tap ratio for the transformer | +| tm_max | / | Vector{Vector} | maximum tap ratio for each winding and phase (base=`tm_nom`) | +| tm_min | / | Vector{Vector} | minimum tap ratio for for each winding and phase (base=`tm_nom`) | +| tm_set | 1.0 | Vector{Vector} | set tap ratio for each winding and phase (base=`tm_nom`) | +| tm_fix | true | Vector{Vector} | indicates for each winding and phase whether the tap ratio is fixed | From 3945ede12f4bf1becfc578faddb731a8e5940c1d Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 20 Feb 2020 12:24:20 -0700 Subject: [PATCH 022/224] ADD: Buscoord parsing into PMD data structure (#249) * ADD: Buscoord parsing into PMD data structure Adds automatic parsing of buscoords (if present) into the PMD data structure, under the fields `lon` and `lat`. Adds unit test using existing opendss test files and updates changelog. Removes `buscoords.dat` from test data files, it does not seem to be used. Closes #245 * FIX: Backwards compatibility with Julia 1.0 `range(start, stop; step=)` is not available in Julia 1.0 --- CHANGELOG.md | 1 + src/io/dss_parse.jl | 7 +++---- src/io/opendss.jl | 12 ++++++++++++ test/data/opendss/buscoords.dat | 0 test/opendss.jl | 4 ++++ 5 files changed, 20 insertions(+), 4 deletions(-) delete mode 100644 test/data/opendss/buscoords.dat diff --git a/CHANGELOG.md b/CHANGELOG.md index 825bf62f5..3e24da5e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## staged +- Add automatic parsing of lon,lat from buscoords file into PMD data structure (#245, #249) - Updates virtual_sourcebus, which is intended to represent a voltage source, to have a fixed voltage magnitude (#246,#248) - Add parsing of series data files into array fields in OpenDSS parser - Add LoadShape parsing to OpenDSS parser (#247) diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 97c034ae7..f90b40963 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -642,10 +642,9 @@ function _parse_buscoords(file::AbstractString)::Array coordArray = [] for line in lines bus, x, y = split(line, regex; limit=3) - push!(coordArray, Dict{String,Any}("bus"=>strip(bus, [',']), - "name"=>strip(bus, [',']), - "x"=>parse(Float64, strip(x, [','])), - "y"=>parse(Float64, strip(y, [',', '\r'])))) + push!(coordArray, Dict{String,Any}("name"=>lowercase(strip(bus, [','])), + "x"=>parse(Float64, strip(x, [','])), + "y"=>parse(Float64, strip(y, [',', '\r'])))) end return coordArray end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 9313edb71..b6172165e 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -1698,6 +1698,16 @@ function _bank_transformers!(pmd_data::Dict) end +"Parses buscoords [lon,lat] (if present) into their respective buses" +function _dss2pmd_buscoords!(pmd_data::Dict, dss_data::Dict) + for bc in get(dss_data, "buscoords", []) + bus = pmd_data["bus"]["$(find_bus(bc["name"], pmd_data))"] + bus["lon"] = bc["x"] + bus["lat"] = bc["y"] + end +end + + """ parse_options(options) @@ -1774,6 +1784,8 @@ function parse_opendss(dss_data::Dict; import_all::Bool=false, vmin::Float64=0.9 InfrastructureModels.arrays_to_dicts!(pmd_data) + _dss2pmd_buscoords!(pmd_data, dss_data) + if bank_transformers _bank_transformers!(pmd_data) end diff --git a/test/data/opendss/buscoords.dat b/test/data/opendss/buscoords.dat deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/opendss.jl b/test/opendss.jl index bd0fe37cf..8ed735d83 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -133,6 +133,10 @@ xmatrix=PMD._parse_matrix(Float64, "[1.0000 |0.500000 0.50000 |0.500000 0.50000 1.000000 ]") * 3 cmatrix = PMD._parse_matrix(Float64, "[8.0000 |-2.00000 9.000000 |-1.75000 -2.50000 8.00000 ]") / 3 + @testset "buscoords automatic parsing" begin + @test all(haskey(bus, "lon") && haskey(bus, "lat") for bus in values(pmd["bus"]) if "bus_i" in 1:10) + end + @testset "opendss parse generic parser verification" begin dss = PMD.parse_dss("../test/data/opendss/test2_master.dss") From 083b021ccfa2075f240ef5ff726266ba9040cd40 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 25 Feb 2020 16:44:52 +0100 Subject: [PATCH 023/224] transformer fixing --- src/core/constraint_template.jl | 2 +- src/io/data_model_util.jl | 1 - src/io/opendss.jl | 3 ++- src/io/opendss_dm.jl | 21 ++++++++++++--------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 01a1fbbfb..c41a5a58f 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -144,7 +144,7 @@ function constraint_mc_trans(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw #TODO change data model # there is redundancy in specifying polarity seperately on from and to side #TODO change this once migrated to new data model - pol = haskey(trans, "poalrity") ? trans["polarity"] : trans["config_fr"]["polarity"] + pol = trans["polarity"] if config=="wye" constraint_mc_trans_yy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) diff --git a/src/io/data_model_util.jl b/src/io/data_model_util.jl index a3ae0381c..a3ad4d150 100644 --- a/src/io/data_model_util.jl +++ b/src/io/data_model_util.jl @@ -79,7 +79,6 @@ function data_model_index!(data_model; components=["bus", "line", "shunt", "gene for (id, comp) in data_model[comp_type] @assert(!haskey(comp, "index"), "$comp_type $id: component already has an index.") if haskey(index_presets[comp_type], id) - println("$comp_type: $id found, ->$(index_presets[comp_type][id])") comp["index"] = index_presets[comp_type][id] else comp["index"] = _get_next_index(last_index, values(index_presets[comp_type])) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index b6172165e..3927d3915 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -1146,12 +1146,13 @@ function _decompose_transformers!(pmd_data; import_all::Bool=false) trans_dict["config_fr"] = trans["config"][w] trans_dict["config_to"] = Dict( "type"=>"wye", - "polarity"=>'+', + "polarity"=>1, "cnd"=>[1, 2, 3], "grounded"=>true, "vm_nom"=>1.0 ) # temporary fix for prop renaming + trans_dict["polarity"] = trans_dict["config_fr"]["polarity"] trans_dict["configuration"] = trans_dict["config_fr"]["type"] trans_dict["f_connections"] = trans_dict["config_fr"]["cnd"] trans_dict["t_connections"] = trans_dict["config_to"]["cnd"] diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index fdc5679aa..1d34e0f2e 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -669,7 +669,7 @@ function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bo transDict["snom"] = [defaults["kvas"][w] for w in 1:nrw] transDict["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) transDict["configuration"] = Array{String, 1}(undef, nrw) - transDict["polarity"] = Array{String, 1}(undef, nrw) + transDict["polarity"] = Array{Int, 1}(undef, nrw) for w in 1:nrw transDict["bus"][w] = _parse_busname(defaults["buses"][w])[1] @@ -687,7 +687,7 @@ function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bo end push!(pmd_data["bus"][bus]["awaiting_ground"], transDict) end - transDict["polarity"][w] = "forward" + transDict["polarity"][w] = 1 transDict["tm"][w] = fill(defaults["taps"][w], nphases) end @@ -1115,14 +1115,16 @@ function _discover_terminals!(pmd_data) terminals = Dict{String, Set{Int}}([(bus["id"], Set{Int}()) for (_,bus) in pmd_data["bus"]]) for (_,line) in pmd_data["line"] - push!(terminals[line["f_bus"]], line["f_connections"]...) - push!(terminals[line["t_bus"]], line["t_connections"]...) + # ignore 0 terminal + push!(terminals[line["f_bus"]], setdiff(line["f_connections"], [0])...) + push!(terminals[line["t_bus"]], setdiff(line["t_connections"], [0])...) end - if haskey(pmd_data, "transformer_nw3ph_lossy") - for (_,tr) in pmd_data["transformer_nw3ph_lossy"] - for w in tr["n_windings"] - push!(terminals[buses[w]], terminals[w]...) + if haskey(pmd_data, "transformer_nw") + for (_,tr) in pmd_data["transformer_nw"] + for w in 1:length(tr["bus"]) + # ignore 0 terminal + push!(terminals[tr["bus"][w]], setdiff(tr["connections"][w], [0])...) end end end @@ -1158,8 +1160,9 @@ function _discover_terminals!(pmd_data) else comp["connections"] .+= (comp["connections"].==0)*neutral end + @show comp["connections"] end - delete!(bus, "awaiting_ground") + #delete!(bus, "awaiting_ground") end end phases = haskey(bus, "neutral") ? setdiff(bus["terminals"], bus["neutral"]) : bus["terminals"] From e2f9e51e7b5724e95f1f4c2a8e20336cc6088cf7 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 25 Feb 2020 13:49:01 -0700 Subject: [PATCH 024/224] DOC: formatting --- src/io/data_model.md | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/io/data_model.md b/src/io/data_model.md index 41d18a199..751cfe81d 100644 --- a/src/io/data_model.md +++ b/src/io/data_model.md @@ -16,7 +16,7 @@ The data model below allows us to include buses of arbitrary many terminals (i.e | name | default | type | description | | --------- | --------- | ------------- | --------------------------------------------- | -| terminals | [1,2,3,4] | Vector | +| terminals | [1,2,3,4] | Vector | | | vm_max | / | Vector | maximum conductor-to-ground voltage magnitude | | vm_min | / | Vector | minimum conductor-to-ground voltage magnitude | | vm_cd_max | / | Vector{Tuple} | e.g. [(1,2,210)] means \|U1-U2\|>210 | @@ -41,7 +41,7 @@ Since this might be confusing for novice users, we also allow the user to define | name | default | type | description | | --------- | ------- | ------ | --------------------------------------------------------- | | id | / | | unique identifier | -| phases | [1,2,3] | Vector | +| phases | [1,2,3] | Vector | | | neutral | 4 | | maximum conductor-to-ground voltage magnitude | | vm_pn_max | / | Real | maximum phase-to-neutral voltage magnitude for all phases | | vm_pn_min | / | Real | minimum phase-to-neutral voltage magnitude for all phases | @@ -55,9 +55,9 @@ This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` | name | default | type | description | | ------------- | ------- | ---- | ------------------------------------------------------------------------ | | id | / | | unique identifier | -| f_bus | / | | +| f_bus | / | | | | f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | -| t_bus | / | | +| t_bus | / | | | | t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | | linecode | / | | a linecode | | rs | / | | series resistance matrix, size of n_conductors x n_conductors | @@ -88,8 +88,8 @@ This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` | name | default | type | description | | ----------- | ------- | ---- | ---------------------------------------------------------- | | id | / | | unique identifier | -| bus | / | | -| connections | / | | +| bus | / | | | +| connections | / | | | | g | / | | conductance, size should be \|connections\|x\|connections\ | | b | / | | susceptance, size should be \|connections\|x\|connections\ | @@ -98,8 +98,8 @@ This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` | name | default | type | description | | ----------- | ------- | ---- | ---------------------------------------------------------- | | id | / | | unique identifier | -| bus | / | | -| connections | / | | +| bus | / | | | +| connections | / | | | | qd_ref | / | | conductance, size should be \|connections\|x\|connections\ | | vnom | / | | conductance, size should be \|connections\|x\|connections\ | @@ -108,8 +108,8 @@ This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` | name | default | type | description | | ------------- | ------- | ------------ | --------------------------------------------------------------- | | id | / | | unique identifier | -| bus | / | | -| connections | / | | +| bus | / | | | +| connections | / | | | | configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | | model | / | | indicates the type of voltage-dependency | @@ -117,34 +117,34 @@ This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` | name | default | type | description | | ---- | ------- | ------------ | ----------- | -| pd | / | Vector{Real} | -| qd | / | Vector{Real} | +| pd | / | Vector{Real} | | +| qd | / | Vector{Real} | | ### `model=constant_current/impedance` | name | default | type | description | | ------ | ------- | ------------ | ----------- | -| pd_ref | / | Vector{Real} | -| qd_ref | / | Vector{Real} | -| vnom | / | Real | +| pd_ref | / | Vector{Real} | | +| qd_ref | / | Vector{Real} | | +| vnom | / | Real | | ### `model=exponential` | name | default | type | description | | ------ | ------- | ------------ | ----------- | -| pd_ref | / | Vector{Real} | -| qd_ref | / | Vector{Real} | -| vnom | / | Real | -| exp_p | / | Vector{Real} | -| exp_q | / | Vector{Real} | +| pd_ref | / | Vector{Real} | | +| qd_ref | / | Vector{Real} | | +| vnom | / | Real | | +| exp_p | / | Vector{Real} | | +| exp_q | / | Vector{Real} | | ## Generator | name | default | type | description | | ------------- | ------- | ------------ | --------------------------------------------------------------- | | id | / | | unique identifier | -| bus | / | | -| connections | / | | +| bus | / | | | +| connections | / | | | | configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | | pg_min | / | | lower bound on active power generation per phase | | pg_max | / | | upper bound on active power generation per phase | @@ -159,9 +159,9 @@ These are transformers are assymetric (A), lossless (L) and two-winding (2W). As | ------------- | ---------------------- | ------------ | ------------------------------------------------------------------------ | | id | / | | unique identifier | | n_phases | size(rs)[1] | Int>0 | number of phases | -| f_bus | / | | +| f_bus | / | | | | f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | -| t_bus | / | | +| t_bus | / | | | | t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | | configuration | / | {wye, delta} | for the from-side; the to-side is always connected in wye | | tm_nom | / | Real | nominal tap ratio for the transformer | From 86311cc7e8f31dbda1c8aed9b9c5ffcf3a0fd085 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 26 Feb 2020 16:24:31 -0700 Subject: [PATCH 025/224] DSS parser refactor --- src/io/dss_parse.jl | 435 ++++++++++++++++++++++---------------------- 1 file changed, 222 insertions(+), 213 deletions(-) diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index f90b40963..c1b64fb5d 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -4,18 +4,17 @@ function _sqr(x::Float64) end -_double_operators = Dict("+" => +, "-" => -, "*" => *, "/" => /, "^" => ^, - "atan2" => (x, y) -> rad2deg(atan2(y, x))) +_double_operators = Dict("+" => +, "-" => -, "*" => *, "/" => /, "^" => ^, "atan2" => (x, y) -> rad2deg(atan2(y, x))) _single_operators = Dict("sqr" => _sqr, "sqrt" => sqrt, "inv" => inv, "ln" => log, - "exp" => exp, "log10" => log10, "sin" => sind, "cos" => cosd, - "tan" => tand, "asin" => asind, "acos" => acosd, "atan" => atand) + "exp" => exp, "log10" => log10, "sin" => sind, "cos" => cosd, + "tan" => tand, "asin" => asind, "acos" => acosd, "atan" => atand) -array_delimiters = ['\"', '\'', '[', '{', '(', ']', '}', ')'] +const _array_delimiters = ['\"', '\'', '[', '{', '(', ']', '}', ')'] "parses Reverse Polish Notation `expr`" function _parse_rpn(expr::AbstractString, dtype::Type=Float64) - clean_expr = strip(expr, array_delimiters) + clean_expr = strip(expr, _array_delimiters) if occursin("rollup", clean_expr) || occursin("rolldn", clean_expr) || occursin("swap", clean_expr) Memento.warn(_LOGGER, "_parse_rpn does not support \"rollup\", \"rolldn\", or \"swap\", leaving as String") @@ -58,10 +57,10 @@ end "detects if `expr` is Reverse Polish Notation expression" function _isa_rpn(expr::AbstractString)::Bool - expr = split(strip(expr, array_delimiters)) - opkeys = keys(merge(_double_operators, _single_operators)) + expr = split(strip(expr, _array_delimiters)) + op_keys = keys(merge(_double_operators, _single_operators)) for item in expr - if item in opkeys + if item in op_keys return true end end @@ -93,11 +92,11 @@ end """ - _get_prop_name(ctype) + _get_prop_name(obj_type) -Returns the property names in order for a given component type `ctype`. +Returns the property names in order for a given component type `obj_type`. """ -function _get_prop_name(ctype::AbstractString)::Array +function _get_prop_name(obj_type::AbstractString)::Array linecode = ["nphases", "r1", "x1", "r0", "x0", "c1", "c0", "units", "rmatrix", "xmatrix", "cmatrix", "basefreq", "normamps", "emergamps", "faultrate", "pctperm", "repair", "kron", @@ -229,7 +228,7 @@ function _get_prop_name(ctype::AbstractString)::Array "vmaxpu", "yearly", "daily", "duty", "tyearly", "tduty", "class", "usermodel", "userdata", "debugtrace", "spectrum"] - ctypes = Dict{String, Array}("linecode" => linecode, + obj_types = Dict{String, Array}("linecode" => linecode, "linegeometry" => linegeometry, "linespacing" =>linespacing, "loadshape" => loadshape, @@ -259,11 +258,7 @@ function _get_prop_name(ctype::AbstractString)::Array "circuit" => vsource ) - try - return ctypes[ctype] - catch e - throw(e) - end + return obj_types[obj_type] end @@ -276,7 +271,7 @@ brackets, rows are separated by "|", and columns are separated by spaces. """ function _parse_matrix(dtype::Type, data::AbstractString)::Array rows = [] - for line in split(strip(data, array_delimiters), '|') + for line in split(strip(data, _array_delimiters), '|') cols = [] for item in split(line) push!(cols, parse(dtype, item)) @@ -369,7 +364,7 @@ function _parse_array(dtype::Type, data::AbstractString) end if _isa_rpn(data) - matches = collect((m.match for m = eachmatch(Regex(string("[",join(array_delimiters, '\\'),"]")), data, overlap=false))) + matches = collect((m.match for m = eachmatch(Regex(string("[",join(_array_delimiters, '\\'),"]")), data, overlap=false))) if length(matches) == 2 if dtype == String return data @@ -381,7 +376,7 @@ function _parse_array(dtype::Type, data::AbstractString) elements = _parse_properties(data[2:end-1]) end else - elements = split(strip(data, array_delimiters), split_char) + elements = split(strip(data, _array_delimiters), split_char) elements = [strip(el) for el in elements if strip(el) != ""] end @@ -558,51 +553,51 @@ function _parse_mult(mult_string::AbstractString; path::AbstractString="", npts: end file_key = [prop for prop in keys(props) if endswith(prop, "file")][1] - fullpath = path == "" ? props[file_key] : join([path, props[file_key]], '/') + full_path = path == "" ? props[file_key] : join([path, props[file_key]], '/') type = file_key == "file" ? "mult" : file_key - return "($(join(_parse_loadshape_file(fullpath, type, npts; header=get(props, "header", false), column=parse(Int, get(props, "column", "1"))), ",")))" + return "($(join(_parse_loadshape_file(full_path, type, npts; header=get(props, "header", false), column=parse(Int, get(props, "column", "1"))), ",")))" end end "parses loadshape component" -function _parse_loadshape!(curCompDict::Dict{String,Any}; path::AbstractString="") - if any(parse.(Float64, [get(curCompDict, "interval", "1.0"), get(curCompDict, "minterval", "60.0"), get(curCompDict, "sinterval", "3600.0")]) .<= 0.0) +function _parse_loadshape!(current_obj::Dict{String,Any}; path::AbstractString="") + if any(parse.(Float64, [get(current_obj, "interval", "1.0"), get(current_obj, "minterval", "60.0"), get(current_obj, "sinterval", "3600.0")]) .<= 0.0) interval = true else interval = false end - npts = parse(Int, get(curCompDict, "npts", "1")) + npts = parse(Int, get(current_obj, "npts", "1")) - for prop in curCompDict["prop_order"] + for prop in current_obj["prop_order"] if prop in ["pmult", "qmult"] - curCompDict[prop] = _parse_mult(curCompDict[prop]; path=path, npts=npts) + current_obj[prop] = _parse_mult(current_obj[prop]; path=path, npts=npts) elseif prop in ["csvfile", "pqcsvfile", "sngfile", "dblfile"] - fullpath = path == "" ? curCompDict[prop] : join([path, curCompDict[prop]], '/') - data = _parse_loadshape_file(fullpath, prop, parse(Int, get(curCompDict, "npts", "1")); interval=interval, header=false) + full_path = path == "" ? current_obj[prop] : join([path, current_obj[prop]], '/') + data = _parse_loadshape_file(full_path, prop, parse(Int, get(current_obj, "npts", "1")); interval=interval, header=false) if prop == "pqcsvfile" if interval - curCompDict["hour"], curCompDict["pmult"], curCompDict["qmult"] = data + current_obj["hour"], current_obj["pmult"], current_obj["qmult"] = data else - curCompDict["pmult"], curCompDict["qmult"] = data + current_obj["pmult"], current_obj["qmult"] = data end else if interval - curCompDict["hour"], curCompDict["pmult"] = data + current_obj["hour"], current_obj["pmult"] = data else - curCompDict["pmult"] = data + current_obj["pmult"] = data end end end end for prop in ["pmult", "qmult", "hour"] - if haskey(curCompDict, prop) && isa(curCompDict[prop], Array) - curCompDict[prop] = "($(join(curCompDict[prop], ",")))" - elseif haskey(curCompDict, prop) && isa(curCompDict[prop], String) && !_isa_array(curCompDict[prop]) - curCompDict[prop] = "($(curCompDict[prop]))" + if haskey(current_obj, prop) && isa(current_obj[prop], Array) + current_obj[prop] = "($(join(current_obj[prop], ",")))" + elseif haskey(current_obj, prop) && isa(current_obj[prop], String) && !_isa_array(current_obj[prop]) + current_obj[prop] = "($(current_obj[prop]))" end end end @@ -630,7 +625,7 @@ Parses a Bus Coordinate `file`, in either "dat" or "csv" formats, where in "dat", columns are separated by spaces, and in "csv" by commas. File expected to contain "bus,x,y" on each line. """ -function _parse_buscoords(file::AbstractString)::Array +function _parse_buscoords(file::AbstractString)::Dict{String,Any} file_str = read(open(file), String) regex = r",\s*" if endswith(lowercase(file), "csv") || endswith(lowercase(file), "dss") @@ -639,14 +634,13 @@ function _parse_buscoords(file::AbstractString)::Array lines = _strip_lines(split(file_str, '\n')) - coordArray = [] + buscoords = Dict{String,Dict{String,Any}}() for line in lines bus, x, y = split(line, regex; limit=3) - push!(coordArray, Dict{String,Any}("name"=>lowercase(strip(bus, [','])), - "x"=>parse(Float64, strip(x, [','])), - "y"=>parse(Float64, strip(y, [',', '\r'])))) + buscoords[lowercase(strip(bus, [',']))] = Dict{String,Any}("x"=>parse(Float64, strip(x, [','])), "y"=>parse(Float64, strip(y, [',', '\r']))) end - return coordArray + + return buscoords end @@ -657,148 +651,154 @@ Parses a string of `properties` of a component type, character by character into an array with each element containing (if present) the property name, "=", and the property value. """ -function _parse_properties(properties::AbstractString)::Array - propsOut = [] - endArray = true - endProp = false - endEquality = false - endSQuot = true - endDQuot = true - str_out = "" +function _parse_properties(properties::AbstractString)::Array{<:Any, 1} + parsed_properties = [] + end_array = true + end_property = false + end_equality = false + end_single_quote = true + end_double_quote = true + string_out = "" properties = replace(properties, r"\s*=\s*" => "=") - nchars = length(properties) + num_chars = length(properties) for (n, char) in enumerate(properties) - sstr_out = split(str_out, "=") - if length(sstr_out) == 2 && sstr_out[2] != "" - endEquality = true - elseif !occursin("=", str_out) && (char == ' ' || n == nchars) - endEquality = true - elseif occursin("=", str_out) && (char == ' ' || n == nchars) && endArray - endEquality = true + substring_out = split(string_out, "=") + if length(substring_out) == 2 && substring_out[2] != "" + end_equality = true + elseif !occursin("=", string_out) && (char == ' ' || n == num_chars) + end_equality = true + elseif occursin("=", string_out) && (char == ' ' || n == num_chars) && end_array + end_equality = true else - endEquality = false + end_equality = false end if char == ' ' - endProp = true + end_property = true else - endProp = false + end_property = false end if char in ['[', '(', '{'] - endArray = false + end_array = false elseif char in [']', ')', '}'] - endArray = true + end_array = true end if char == '\"' - endDQuot = !endDQuot + end_double_quote = !end_double_quote elseif char == '\'' - endSQuot = !endSQuot + end_single_quote = !end_single_quote end - if char != ' ' || !endArray || !endDQuot || !endSQuot - str_out = string(str_out, char) + if char != ' ' || !end_array || !end_double_quote || !end_single_quote + string_out = string(string_out, char) end - if str_out != "" && endArray && endProp && endEquality && endDQuot && endSQuot || n == nchars - push!(propsOut, str_out) - str_out = "" + if string_out != "" && end_array && end_property && end_equality && end_double_quote && end_single_quote || n == num_chars + push!(parsed_properties, string_out) + string_out = "" end end - return propsOut + return parsed_properties end """ - _add_component!(dss_data, ctype_name, compDict) + _add_component!(data_dss, obj_type_name, object) -Adds a component of type `ctype_name` with properties given by `compDict` to -the existing `dss_data` structure. If a component of the same type has already -been added to `dss_data`, the new component is appeneded to the existing array +Adds a component of type `obj_type_name` with properties given by `object` to +the existing `data_dss` structure. If a component of the same type has already +been added to `data_dss`, the new component is appeneded to the existing array of components of that type, otherwise a new array is created. """ -function _add_component!(dss_data::Dict, ctype_name::AbstractString, compDict::Dict) - ctype = split(ctype_name, '.'; limit=2)[1] - if haskey(dss_data, ctype) - push!(dss_data[ctype], compDict) +function _add_component!(data_dss::Dict, obj_type_name::AbstractString, object::Dict) + obj_type = split(obj_type_name, '.'; limit=2)[1] + if obj_type == "circuit" + if haskey(data_dss, "circuit") + Memento.error(_LOGGER, "Cannot have two circuits, invalid dss") + else + data_dss[obj_type] = object + end + elseif haskey(data_dss, obj_type) + data_dss[obj_type][object["name"]] = object else - dss_data[ctype] = [compDict] + data_dss[obj_type] = Dict{String,Any}(object["name"] => object) end end """ - _add_property(compDict, key, value) + _add_property(object, key, value) -Adds a property to an existing component properties dictionary `compDict` given +Adds a property to an existing component properties dictionary `object` given the `key` and `value` of the property. If a property of the same name already -exists inside `compDict`, the original value is converted to an array, and the +exists inside `object`, the original value is converted to an array, and the new value is appended to the end. """ -function _add_property(compDict::Dict, key::AbstractString, value::Any)::Dict - if !haskey(compDict, "prop_order") - compDict["prop_order"] = Array{String,1}(["name"]) +function _add_property(object::Dict, key::AbstractString, value::Any)::Dict + if !haskey(object, "prop_order") + object["prop_order"] = Array{String,1}(["name"]) end - cur_wdg = "wdg" in compDict["prop_order"] ? string(filter(p->occursin("wdg", p), compDict["prop_order"])[end][end]) : "" - cur_wdg = cur_wdg == "g" ? "" : cur_wdg + current_wdg = "wdg" in object["prop_order"] ? string(filter(p->occursin("wdg", p), object["prop_order"])[end][end]) : "" + current_wdg = current_wdg == "g" ? "" : current_wdg if key in ["wdg", "bus", "conn", "kv", "kva", "tap", "%r", "rneut", "xneut"] - key = join(filter(p->!isempty(p), [key, cur_wdg]), "_") + key = join(filter(p->!isempty(p), [key, current_wdg]), "_") end - if haskey(compDict, lowercase(key)) + if haskey(object, lowercase(key)) rmatch = match(r"_(\d+)$", key) if typeof(rmatch) != Nothing - endNum = parse(Int, rmatch.captures[1]) + 1 - key = replace(key, r"_(\d+)$" => "_$endNum") + end_num = parse(Int, rmatch.captures[1]) + 1 + key = replace(key, r"_(\d+)$" => "_$end_num") else key = string(key, "_2") end end - compDict[lowercase(key)] = value - push!(compDict["prop_order"], lowercase(key)) + object[lowercase(key)] = value + push!(object["prop_order"], lowercase(key)) - return compDict + return object end """ - _parse_component(component, properies, compDict=Dict{String,Any}()) + _parse_component(component, properies, object=Dict{String,Any}()) -Parses a `component` with `properties` into a `compDict`. If `compDict` is not +Parses a `component` with `properties` into a `object`. If `object` is not defined, an empty dictionary will be used. Assumes that unnamed properties are given in order, but named properties can be given anywhere. """ -function _parse_component(component::AbstractString, properties::AbstractString, compDict::Dict=Dict{String,Any}(); path::AbstractString="") +function _parse_component(component::AbstractString, properties::AbstractString, object::Dict{String,<:Any}=Dict{String,Any}(); path::AbstractString="") Memento.debug(_LOGGER, "Properties: $properties") - ctype, name = split(component, '.'; limit=2) + obj_type, name = split(component, '.'; limit=2) - if !haskey(compDict, "name") - compDict["name"] = name + if !haskey(object, "name") + object["name"] = name end - propArray = _parse_properties(properties) - Memento.debug(_LOGGER, "propArray: $propArray") + property_array = _parse_properties(properties) + Memento.debug(_LOGGER, "property_array: $property_array") - propNames = _get_prop_name(ctype) - propIdx = 1 + property_names = _get_prop_name(obj_type) + property_idx = 1 - for (n, property) in enumerate(propArray) + for (n, property) in enumerate(property_array) if property == "" continue elseif !occursin("=", property) - property = join([propNames[propIdx], property], '=') - propIdx += 1 + property = join([property_names[property_idx], property], '=') + property_idx += 1 else - if ctype == "loadshape" && startswith(property, "mult") + if obj_type == "loadshape" && startswith(property, "mult") property = replace(property, "mult" => "pmult") - elseif ctype == "transformer" + elseif obj_type == "transformer" prop_name, _ = split(property,'=') if prop_name == "ppm" property = replace(property, prop_name => "ppm_antifloat") @@ -811,9 +811,9 @@ function _parse_component(component::AbstractString, properties::AbstractString, end end - propIdxs = findall(e->e==split(property,'=')[1], propNames) - if length(propIdxs) > 0 - propIdx = findall(e->e==split(property,'=')[1], propNames)[1] + 1 + property_idxs = findall(e->e==split(property,'=')[1], property_names) + if length(property_idxs) > 0 + property_idx = findall(e->e==split(property,'=')[1], property_names)[1] + 1 end end @@ -823,10 +823,10 @@ function _parse_component(component::AbstractString, properties::AbstractString, value = _parse_mult(value; path=path) end - _add_property(compDict, key, value) + _add_property(object, key, value) end - return compDict + return object end @@ -837,7 +837,7 @@ Merges two (partially) parsed OpenDSS files to the same dictionary `dss_prime`. Used in cases where files are referenced via the "compile" or "redirect" OpenDSS commands inside the originating file. """ -function _merge_dss!(dss_prime::Dict{String,Array}, dss_to_add::Dict{String,Array}) +function _merge_dss!(dss_prime::Dict{String,<:Any}, dss_to_add::Dict{String,<:Any}) for (k, v) in dss_to_add if k in keys(dss_prime) append!(dss_prime[k], v) @@ -849,16 +849,16 @@ end """ - _parse_line(elements, curCompDict=Dict{String,Any}()) + _parse_line(elements, current_obj=Dict{String,Any}()) Parses an already separated line given by `elements` (an array) of an OpenDSS -file into `curCompDict`. If not defined, `curCompDict` is an empty dictionary. +file into `current_obj`. If not defined, `current_obj` is an empty dictionary. """ -function _parse_line(elements::Array, curCompDict::Dict=Dict{String,Any}(); path::AbstractString="") - curCtypeName = strip(elements[2], ['\"', '\'']) - if startswith(curCtypeName, "object") - curCtypeName = split(curCtypeName, '=')[2] - curCompDict["name"] = split(curCtypeName, '.')[2] +function _parse_line(elements::Array, current_obj::Dict=Dict{String,Any}(); path::AbstractString="") + current_obj_type = strip(elements[2], ['\"', '\'']) + if startswith(current_obj_type, "object") + current_obj_type = split(current_obj_type, '=')[2] + current_obj["name"] = split(current_obj_type, '.')[2] else if length(elements) != 3 properties = "" @@ -866,10 +866,10 @@ function _parse_line(elements::Array, curCompDict::Dict=Dict{String,Any}(); path properties = elements[3] end - curCompDict = _parse_component(curCtypeName, properties; path=path) + current_obj = _parse_component(current_obj_type, properties; path=path) end - return curCtypeName, curCompDict + return current_obj_type, current_obj end @@ -880,21 +880,20 @@ end """ - _assign_property!(dss_data, cType, cName, propName, propValue) + _assign_property!(data_dss, obj_type, obj_name, property_name, property_value) -Assigns a property with name `propName` and value `propValue` to the component -of type `cType` named `cName` in `dss_data`. +Assigns a property with name `property_name` and value `property_value` to the component +of type `obj_type` named `obj_name` in `data_dss`. """ -function _assign_property!(dss_data::Dict, cType::AbstractString, cName::AbstractString, - propName::AbstractString, propValue::Any) - if haskey(dss_data, cType) - for obj in dss_data[cType] - if obj["name"] == cName - obj[propName] = propValue +function _assign_property!(data_dss::Dict, obj_type::AbstractString, obj_name::AbstractString, property_name::AbstractString, property_value::Any) + if haskey(data_dss, obj_type) + for obj in data_dss[obj_type] + if obj["name"] == obj_name + obj[property_name] = property_value end end else - Memento.warn(_LOGGER, "Cannot find $cType object $cName.") + Memento.warn(_LOGGER, "Cannot find $obj_type object $obj_name.") end end @@ -907,17 +906,17 @@ supports components and options, but not commands, e.g. "plot" or "solve". Will also parse files defined inside of the originating DSS file via the "compile", "redirect" or "buscoords" commands. """ -function parse_dss(io::IOStream)::Dict +function parse_dss(io::IOStream)::Dict{String,Any} filename = match(r"^$", io.name).captures[1] Memento.info(_LOGGER, "Calling parse_dss on $filename") - currentFile = split(filename, "/")[end] + current_file = split(filename, "/")[end] path = join(split(filename, '/')[1:end-1], '/') - dss_data = Dict{String,Array}() + data_dss = Dict{String,Any}() - dss_data["filename"] = [currentFile] + data_dss["filename"] = [string(current_file)] - curCompDict = Dict{String,Any}() - curCtypeName = "" + current_obj = Dict{String,Any}() + current_obj_type = "" lines = readlines(io) @@ -930,93 +929,94 @@ function parse_dss(io::IOStream)::Dict line = _strip_comments(line) if startswith(strip(line), '~') - curCompDict = _parse_component(curCtypeName, strip(strip(lowercase(line)), '~'), curCompDict) + current_obj = _parse_component(current_obj_type, strip(strip(lowercase(line)), '~'), current_obj) if n < nlines && startswith(strip(stripped_lines[n + 1]), '~') continue else - _add_component!(dss_data, curCtypeName, curCompDict) + _add_component!(data_dss, current_obj_type, current_obj) end else - curCompDict = Dict{String,Any}() + current_obj = Dict{String,Any}() line_elements = split(line, r"\s+"; limit=3) cmd = lowercase(line_elements[1]) if cmd == "clear" - Memento.info(_LOGGER, "`dss_data` has been reset with the \"clear\" command.") - dss_data = Dict{String,Array}("filename"=>dss_data["filename"]) + Memento.info(_LOGGER, "`data_dss` has been reset with the \"clear\" command.") + data_dss = Dict{String,Any}("filename"=>data_dss["filename"]) continue elseif cmd == "redirect" file = line_elements[2] - fullpath = path == "" ? file : join([path, file], '/') + full_path = path == "" ? file : join([path, file], '/') Memento.info(_LOGGER, "Redirecting to file \"$file\"") - _merge_dss!(dss_data, parse_dss(fullpath)) + _merge_dss!(data_dss, parse_dss(full_path)) continue elseif cmd == "compile" file = split(strip(line_elements[2], ['(',')']), '\\')[end] - fullpath = path == "" ? file : join([path, file], '/') + full_path = path == "" ? file : join([path, file], '/') Memento.info(_LOGGER, "Compiling file \"$file\"") - _merge_dss!(dss_data, parse_dss(fullpath)) + _merge_dss!(data_dss, parse_dss(full_path)) continue elseif cmd == "set" Memento.debug(_LOGGER, "set command: $line_elements") + properties = _parse_properties(join(line_elements[2:end], " ")) if length(line_elements) == 2 property, value = split(lowercase(line_elements[2]), '='; limit=2) else - property, value = lowercase(line_elements[2]), strip(strip(lowercase(line_elements[3]), '=')) + property, value = split(lowercase(join(line_elements[2:end], " ")), '='; limit=2) end - if !haskey(dss_data, "options") - dss_data["options"] = [Dict{String,Any}()] + if !haskey(data_dss, "options") + data_dss["options"] = Dict{String,Any}() end - dss_data["options"][1]["$(property)"] = value + data_dss["options"]["$(property)"] = value continue elseif cmd == "buscoords" file = line_elements[2] - fullpath = path == "" ? file : join([path, file], '/') - Memento.debug(_LOGGER, "Buscoords path: $fullpath") - dss_data["buscoords"] = _parse_buscoords(fullpath) + full_path = path == "" ? file : join([path, file], '/') + Memento.debug(_LOGGER, "Buscoords path: $full_path") + data_dss["buscoords"] = _parse_buscoords(full_path) elseif cmd == "new" - curCtypeName, curCompDict = _parse_line([lowercase(line_element) for line_element in line_elements]; path=path) + current_obj_type, current_obj = _parse_line([lowercase(line_element) for line_element in line_elements]; path=path) - if startswith(curCtypeName, "loadshape") - _parse_loadshape!(curCompDict; path=path) + if startswith(current_obj_type, "loadshape") + _parse_loadshape!(current_obj; path=path) end else try - cType, cName, props = split(lowercase(line), '.'; limit=3) - propsOut = _parse_properties(props) + obj_type, obj_name, props = split(lowercase(line), '.'; limit=3) + parsed_properties = _parse_properties(props) wdg = "" - for prop in propsOut - propName, propValue = split(prop, '=') - if cType == "transformer" - wdg = propName == "wdg" && propValue != "1" ? propValue : propName == "wdg" && propValue == "1" ? "" : wdg + for prop in parsed_properties + property_name, property_value = split(prop, '=') + if obj_type == "transformer" + wdg = property_name == "wdg" && property_value != "1" ? property_value : property_name == "wdg" && property_value == "1" ? "" : wdg - if propName in ["wdg", "bus", "conn", "kv", "kva", "tap", "%r", "rneut", "xneut"] - propName = join(filter(p->!isempty(p), [propName, wdg]), "_") + if property_name in ["wdg", "bus", "conn", "kv", "kva", "tap", "%r", "rneut", "xneut"] + property_name = join(filter(p->!isempty(p), [property_name, wdg]), "_") end - _assign_property!(dss_data, cType, cName, propName, propValue) + _assign_property!(data_dss, obj_type, obj_name, property_name, property_value) else - _assign_property!(dss_data, cType, cName, propName, propValue) + _assign_property!(data_dss, obj_type, obj_name, property_name, property_value) end end catch - Memento.warn(_LOGGER, "Command \"$cmd\" on line $real_line_num in \"$currentFile\" is not supported, skipping.") + Memento.warn(_LOGGER, "Command \"$cmd\" on line $real_line_num in \"$current_file\" is not supported, skipping.") end end - Memento.debug(_LOGGER, "size curCompDict: $(length(curCompDict))") + Memento.debug(_LOGGER, "size current_obj: $(length(current_obj))") if n < nlines && startswith(strip(stripped_lines[n + 1]), '~') continue - elseif length(curCompDict) > 0 - _add_component!(dss_data, curCtypeName, curCompDict) + elseif length(current_obj) > 0 + _add_component!(data_dss, current_obj_type, current_obj) else continue end @@ -1024,16 +1024,16 @@ function parse_dss(io::IOStream)::Dict end Memento.info(_LOGGER, "Done parsing $filename") - return dss_data + return data_dss end "" function parse_dss(filename::AbstractString)::Dict - dss_data = open(filename) do io + data_dss = open(filename) do io parse_dss(io) end - return dss_data + return data_dss end @@ -1072,37 +1072,47 @@ end """ - parse_dss_with_dtypes!(dss_data, toParse) + parse_dss_with_dtypes!(data_dss, to_parse) -Parses the data in keys defined by `toParse` in `dss_data` using types given by +Parses the data in keys defined by `to_parse` in `data_dss` using types given by the default properties from the `get_prop_default` function. """ -function parse_dss_with_dtypes!(dss_data::Dict, toParse::Array{String}=[]) - for compType in toParse - if haskey(dss_data, compType) - Memento.debug(_LOGGER, "type: $compType") - for item in dss_data[compType] - dtypes = _get_dtypes(compType) - for (k, v) in item - if haskey(dtypes, k) - Memento.debug(_LOGGER, "key: $k") - if isa(v, Array) - arrout = [] - for el in v - if isa(v, AbstractString) - push!(arrout, _parse_element_with_dtype(dtypes[k], el)) - else - push!(arrout, el) - end - end - item[k] = arrout - elseif isa(v, AbstractString) - item[k] = _parse_element_with_dtype(dtypes[k], v) - else - Memento.error(_LOGGER, "dtype unknown $compType, $k, $v") - end +function parse_dss_with_dtypes!(data_dss::Dict{String,<:Any}, to_parse::Array{String}=[]) + for obj_type in to_parse + if haskey(data_dss, obj_type) + Memento.debug(_LOGGER, "type: $obj_type") + dtypes = _get_dtypes(obj_type) + if obj_type == "circuit" + _parse_obj_dtypes!(obj_type, data_dss[obj_type], dtypes) + else + for object in values(data_dss[obj_type]) + _parse_obj_dtypes!(obj_type, object, dtypes) + end + end + end + end +end + + +"" +function _parse_obj_dtypes!(obj_type, object, dtypes) + for (k, v) in object + if haskey(dtypes, k) + Memento.debug(_LOGGER, "key: $k") + if isa(v, Array) + arrout = [] + for el in v + if isa(v, AbstractString) + push!(arrout, _parse_element_with_dtype(dtypes[k], el)) + else + push!(arrout, el) end end + object[k] = arrout + elseif isa(v, AbstractString) + object[k] = _parse_element_with_dtype(dtypes[k], v) + else + Memento.error(_LOGGER, "dtype unknown $obj_type, $k, $v") end end end @@ -1148,11 +1158,11 @@ function _get_conductors_ordered(busname::AbstractString; neutral::Bool=true, nc parts = split(busname, '.'; limit=2) ret = [] if length(parts)==2 - conds_str = split(parts[2], '.') + conductors_string = split(parts[2], '.') if neutral - ret = [parse(Int, i) for i in conds_str] + ret = [parse(Int, i) for i in conductors_string] else - ret = [parse(Int, i) for i in conds_str if i != "0"] + ret = [parse(Int, i) for i in conductors_string if i != "0"] end else ret = collect(1:nconductors) @@ -1169,7 +1179,7 @@ end "" -function _apply_ordered_properties(defaults::Dict{String,Any}, raw_dss::Dict{String,Any}; code_dict::Dict{String,Any}=Dict{String,Any}()) +function _apply_ordered_properties(defaults::Dict{String,<:Any}, raw_dss::Dict{String,<:Any}; code_dict::Dict{String,<:Any}=Dict{String,Any}()) _defaults = deepcopy(defaults) for prop in filter(p->p!="like", raw_dss["prop_order"]) @@ -1185,8 +1195,7 @@ end "applies `like` to component" -function _apply_like!(raw_dss, dss_data, comp_type) - default_exclusions = +function _apply_like!(raw_dss, data_dss, comp_type) links = ["like"] if any(link in raw_dss["prop_order"] for link in links) new_prop_order = [] @@ -1200,7 +1209,7 @@ function _apply_like!(raw_dss, dss_data, comp_type) end if prop in links - linked_dss = find_component(dss_data, raw_dss[prop], comp_type) + linked_dss = find_component(data_dss, raw_dss[prop], comp_type) if isempty(linked_dss) Memento.warn(_LOGGER, "$comp_type.$(raw_dss["name"]): $prop=$(raw_dss[prop]) cannot be found") else @@ -1211,7 +1220,7 @@ function _apply_like!(raw_dss, dss_data, comp_type) push!(new_prop_order, linked_prop) if linked_prop in links - _apply_like!(linked_dss, dss_data, comp_type) + _apply_like!(linked_dss, data_dss, comp_type) else raw_dss[linked_prop] = deepcopy(linked_dss[linked_prop]) end From ec24bb3d61ae76101bd4d0d724bdb1dcb6a4792c Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 26 Feb 2020 16:25:48 -0700 Subject: [PATCH 026/224] solution_unmake_pu! -> solution_make_si! --- src/core/data_model_pu.jl | 26 +++++++++++++++----------- src/io/data_model_test.jl | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/core/data_model_pu.jl b/src/core/data_model_pu.jl index f563b1a67..8c882c22e 100644 --- a/src/core/data_model_pu.jl +++ b/src/core/data_model_pu.jl @@ -1,4 +1,5 @@ +"finds voltage zones" function _find_zones(data_model) unused_line_ids = Set(keys(data_model["line"])) bus_lines = Dict([(id,Set()) for id in keys(data_model["bus"])]) @@ -32,6 +33,7 @@ function _find_zones(data_model) end +"calculates voltage bases for each voltage zone" function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) # find zones of buses connected by lines zones = _find_zones(data_model) @@ -81,6 +83,7 @@ function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) end +"converts to per unit from SI" function data_model_make_pu!(data_model; sbase=missing, vbases=missing) v_var_scalar = data_model["settings"]["v_var_scalar"] @@ -148,8 +151,8 @@ function data_model_make_pu!(data_model; sbase=missing, vbases=missing) end +"per-unit conversion for buses" function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) - # if not in p.u., these are normalized with respect to vnom prop_vnom = ["vm", "vmax", "vmin", "vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] @@ -179,8 +182,8 @@ function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) end +"per-unit conversion for lines" function _rebase_pu_line!(line, vbase, sbase, sbase_old, v_var_scalar) - if !haskey(line, "vbase") z_old = 1 else @@ -199,8 +202,8 @@ function _rebase_pu_line!(line, vbase, sbase, sbase_old, v_var_scalar) end +"per-unit conversion for shunts" function _rebase_pu_shunt!(shunt, vbase, sbase, sbase_old, v_var_scalar) - if !haskey(shunt, "vbase") z_old = 1 else @@ -219,8 +222,8 @@ function _rebase_pu_shunt!(shunt, vbase, sbase, sbase_old, v_var_scalar) end +"per-unit conversion for loads" function _rebase_pu_load!(load, vbase, sbase, sbase_old, v_var_scalar) - if !haskey(load, "vbase") vbase_old = 1 sbase_old = 1 @@ -241,6 +244,7 @@ function _rebase_pu_load!(load, vbase, sbase, sbase_old, v_var_scalar) end +"per-unit conversion for generators" function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar) vbase_old = get(gen, "vbase", 1.0/v_var_scalar) vbase_scale = vbase_old/vbase @@ -257,6 +261,7 @@ function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar) end +"per-unit conversion for ideal 2-winding transformers" function _rebase_pu_transformer_2w_ideal!(trans, f_vbase_new, t_vbase_new, sbase_old, sbase_new, v_var_scalar) f_vbase_old = get(trans, "f_vbase", 1.0) t_vbase_old = get(trans, "t_vbase", 1.0) @@ -271,6 +276,7 @@ function _rebase_pu_transformer_2w_ideal!(trans, f_vbase_new, t_vbase_new, sbase end +"helper function to apply a scale factor to given properties" function _scale_props!(comp::Dict{String, Any}, prop_names::Array{String, 1}, scale::Real) for name in prop_names if haskey(comp, name) @@ -279,12 +285,8 @@ function _scale_props!(comp::Dict{String, Any}, prop_names::Array{String, 1}, sc end end -#data_model_user = make_test_data_model() -#data_model_base = map_down_data_model(data_model_user) -#bus_vbase, line_vbase = get_vbase(data_model_base, Dict("1"=>230.0)) - -#make_pu!(data_model_base) +"" function add_big_M!(data_model; kwargs...) big_M = Dict{String, Any}() @@ -296,8 +298,10 @@ function add_big_M!(data_model; kwargs...) data_model["big_M"] = big_M end -function solution_unmake_pu!(solution, data_model) - sbase = data_model["sbase"] + +"" +function solution_make_si!(solution, data_model) + sbase = data_model["settings"]["sbase"] for (comp_type, comp_dict) in [(x,y) for (x,y) in solution if isa(y, Dict)] for (id, comp) in comp_dict for (prop, val) in comp diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl index 85fdbb0fd..a394d7d95 100644 --- a/src/io/data_model_test.jl +++ b/src/io/data_model_test.jl @@ -152,7 +152,7 @@ ipopt_solver = JuMP.with_optimizer(Ipopt.Optimizer) pm = PMs.instantiate_model(data_model, PMs.IVRPowerModel, PMD.build_mc_opf_iv, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) -solution_unmake_pu!(sol["solution"], data_model) +solution_make_si!(sol["solution"], data_model) solution_identify!(sol["solution"], data_model) solution_unmap!(sol["solution"], data_model) #vm = sol["solution"]["bus"]["tr_sec"]["vm"] From b335d1d5bd98ea120215808a44f19e88758c7d32 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 26 Feb 2020 16:26:15 -0700 Subject: [PATCH 027/224] Minor mapping changes --- src/core/data_model_mapping.jl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index 4e8ed2cc8..672b0bc9a 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -21,6 +21,9 @@ function data_model_map!(data_model) data_model[comp_type] = Dict{String, Any}() end end + + data_model["model"] = "mathematical" + return data_model end @@ -545,9 +548,7 @@ end # MAP SOLUTION UP function solution_unmap!(solution::Dict, data_model::Dict) - for i in length(data_model["mappings"]):-1:1 - (name, data) = data_model["mappings"][i] - + for (name, data) in reverse(data_model["mappings"]) if name=="decompose_transformer_nw" for bus_id in values(data["vbuses"]) delete!(solution["bus"], bus_id) @@ -587,3 +588,10 @@ function solution_unmap!(solution::Dict, data_model::Dict) end end end + + +function transform_solution!(solution, data_model) + solution_make_si!(solution, data_model) + solution_identify!(solution, data_model) + solution_unmap!(solution, data_model) +end From b2efa2d03100c4eebfdf48ef762e0560feade366 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 26 Feb 2020 16:26:44 -0700 Subject: [PATCH 028/224] parse_file_dm rewrite --- src/io/common_dm.jl | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl index 65393f9c2..e0d42c0ef 100644 --- a/src/io/common_dm.jl +++ b/src/io/common_dm.jl @@ -3,12 +3,12 @@ Parses the IOStream of a file into a Three-Phase PowerModels data structure. """ -function parse_file_dm(io::IO; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, filetype::AbstractString="json", bank_transformers::Bool=true) +function parse_file_dm(io::IO; import_all::Bool=false, filetype::AbstractString="json", bank_transformers::Bool=true) if filetype == "m" pmd_data = PowerModelsDistribution.parse_matlab(io) elseif filetype == "dss" Memento.warn(_LOGGER, "Not all OpenDSS features are supported, currently only minimal support for lines, loads, generators, and capacitors as shunts. Transformers and reactors as transformer branches are included, but value translation is not fully supported.") - pmd_data = PowerModelsDistribution.parse_opendss_dm(io; import_all=import_all, vmin=vmin, vmax=vmax, bank_transformers=bank_transformers) + pmd_data = PowerModelsDistribution.parse_opendss_dm(io; import_all=import_all, bank_transformers=bank_transformers) elseif filetype == "json" pmd_data = PowerModels.parse_json(io; validate=false) else @@ -22,20 +22,36 @@ end "" -function parse_file_dm(file::String; kwargs...) +function parse_file_dm(file::String; model::String="engineering", kwargs...) pmd_data = open(file) do io parse_file_dm(io; filetype=split(lowercase(file), '.')[end], kwargs...) end - # to get the old indexing - pmd_data_old = open(file) do io - parse_file(io; filetype=split(lowercase(file), '.')[end], kwargs...) + if model == "mathematical" + # to get the old indexing + pmd_data_old = open(file) do io + parse_file(io; filetype=split(lowercase(file), '.')[end], kwargs...) + end + index_presets = Dict(comp_type=>Dict(comp["name"]=>comp["index"] for (id, comp) in pmd_data_old[comp_type]) for comp_type in ["bus"]) + + transform_model!(pmd_data; index_presets=index_presets) end - index_presets = Dict(comp_type=>Dict(comp["name"]=>comp["index"] for (id, comp) in pmd_data_old[comp_type]) for comp_type in ["bus"]) - data_model_map!(pmd_data) - data_model_make_pu!(pmd_data) - data_model_index!(pmd_data; index_presets=index_presets) - data_model_make_compatible_v8!(pmd_data) return pmd_data end + + +"transforms model between engineering (high-level) and mathematical (low-level) models" +function transform_data_model!(data::Dict{String,Any}; index_presets::Dict{String,<:Any}=Dict{String,Any}()) + if get(data, "model", "mathematical") == "engineering" + data_model_map!(data) + data_model_make_pu!(data) + data_model_index!(data, index_presets=index_presets) + data_model_make_compatible_v8!(data) + else + if haskey(data, "") + else + Memento.warn(_LOGGER, "Cannot transform mathematical model to engineering model, no mapping information available") + end + end +end From 4242af9d3242672c06fe302e83934353dcf764a6 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 26 Feb 2020 16:28:05 -0700 Subject: [PATCH 029/224] refactor opendss_dm --- src/io/opendss_dm.jl | 1012 ++++++++++++++++++------------------------ 1 file changed, 437 insertions(+), 575 deletions(-) diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 1d34e0f2e..6ce7f572c 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -2,58 +2,36 @@ import LinearAlgebra: isdiag, diag, pinv -"Structure representing OpenDSS `dss_source_id` giving the type of the component `dss_type`, its name `dss_name`, and the active phases `active_phases`" -struct DSSSourceId - dss_type::AbstractString - dss_name::AbstractString - active_phases::Set{Int} -end - - -"Parses a component's OpenDSS source information into the `dss_source_id` struct" -function _parse_dss_source_id(component::Dict)::DSSSourceId - dss_type, dss_name = split(component["source_id"], '.') - return DSSSourceId(dss_type, dss_name, Set(component["active_phases"])) -end - - -"returns the linecode with name `id`" -function _get_linecode(dss_data::Dict, id::AbstractString) - if haskey(dss_data, "linecode") - for item in dss_data["linecode"] - if item["name"] == id - return item - end - end - end - return Dict{String,Any}() -end +const _exclude_duplicate_check = ["options", "filename", "circuit"] +const _dss_edge_components = ["line", "transformer", "reactor"] +const _dss_supported_components = ["line", "linecode", "load", "generator", "capacitor", "reactor", "circuit", "transformer", "pvsystem", "storage", "loadshape"] +const _dss_option_dtypes = Dict{String,Type}("defaultbasefreq" => Float64, "voltagebases" => Float64) """ - _discover_buses(dss_data) + _discover_buses(data_dss) Discovers all of the buses (not separately defined in OpenDSS), from "lines". """ -function _discover_buses(dss_data::Dict)::Array +function _discover_buses(data_dss::Dict{String,<:Any})::Array bus_names = [] buses = [] - for compType in ["line", "transformer", "reactor"] - if haskey(dss_data, compType) - compList = dss_data[compType] - for compObj in compList - if compType == "transformer" - compObj = _create_transformer(compObj["name"]; _to_sym_keys(compObj)...) - for bus in compObj["buses"] + for obj_type in _dss_edge_components + if haskey(data_dss, obj_type) + dss_objs = data_dss[obj_type] + for dss_obj in values(dss_objs) + if obj_type == "transformer" + dss_obj = _create_transformer(dss_obj["name"]; _to_sym_keys(dss_obj)...) + for bus in dss_obj["buses"] name, nodes = _parse_busname(bus) if !(name in bus_names) push!(bus_names, name) push!(buses, (name, nodes)) end end - elseif haskey(compObj, "bus2") + elseif haskey(dss_obj, "bus2") for key in ["bus1", "bus2"] - name, nodes = _parse_busname(compObj[key]) + name, nodes = _parse_busname(dss_obj[key]) if !(name in bus_names) push!(bus_names, name) push!(buses, (name, nodes)) @@ -64,7 +42,7 @@ function _discover_buses(dss_data::Dict)::Array end end if length(buses) == 0 - Memento.error(_LOGGER, "dss_data has no lines!") + Memento.error(_LOGGER, "data_dss has no edge components!") else return buses end @@ -72,84 +50,56 @@ end """ - _dss2pmd_bus!(pmd_data, dss_data) + _dss2pmd_bus!(data_eng, data_dss) -Adds PowerModels-style buses to `pmd_data` from `dss_data`. +Adds PowerModels-style buses to `data_eng` from `data_dss`. """ -function _dss2pmd_bus_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1) +function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) - buses = _discover_buses(dss_data) + buses = _discover_buses(data_dss) for (n, (bus, nodes)) in enumerate(buses) @assert(!(length(bus)>=8 && bus[1:8]=="_virtual"), "Bus $bus: identifiers should not start with _virtual.") - add_bus!(pmd_data, id=bus, status=1, bus_type=1) + add_bus!(data_eng, id=bus, status=1, bus_type=1) end +end + + +"" +function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) # create virtual sourcebus - circuit = _create_vsource(get(dss_data["circuit"][1], "bus1", "sourcebus"), dss_data["circuit"][1]["name"]; _to_sym_keys(dss_data["circuit"][1])...) + circuit = _create_vsource(get(data_dss["circuit"], "bus1", "sourcebus"), data_dss["circuit"]["name"]; _to_sym_keys(data_dss["circuit"])...) nodes = Array{Bool}([1 1 1 0]) ph1_ang = circuit["angle"] vm_pu = circuit["pu"] - vmi = circuit["pu"] - circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) - vma = circuit["pu"] + circuit["pu"] / (circuit["mvasc3"] / circuit["basemva"]) phases = circuit["phases"] - vnom = pmd_data["settings"]["set_vbase_val"] + vnom = data_eng["settings"]["set_vbase_val"] vm = fill(vm_pu, 3)*vnom va = _wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases]) - add_voltage_source!(pmd_data, id="source", bus="sourcebus", connections=collect(1:phases), - vm=vm, va=va, - rs=circuit["rmatrix"], xs=circuit["xmatrix"], - ) + add_voltage_source!(data_eng, id="source", bus="sourcebus", connections=collect(1:phases), vm=vm, va=va, rs=circuit["rmatrix"], xs=circuit["xmatrix"]) end -""" - find_component(pmd_data, name, compType) - -Returns the component of `compType` with `name` from `data` of type -Dict{String,Array}. -""" -function find_component(data::Dict, name::AbstractString, compType::AbstractString)::Dict - for comp in values(data[compType]) - if comp["name"] == name - return comp - end - end - Memento.warn(_LOGGER, "Could not find $compType \"$name\"") - return Dict{String,Any}() -end - - -""" - find_bus(busname, pmd_data) - -Finds the index number of the bus in existing data from the given `busname`. -""" -function find_bus(busname::AbstractString, pmd_data::Dict) - bus = find_component(pmd_data, busname, "bus") - if haskey(bus, "bus_i") - return bus["bus_i"] - else - Memento.error(_LOGGER, "cannot find connected bus with id \"$busname\"") - end +"" +function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) end """ - _dss2pmd_load!(pmd_data, dss_data, import_all) + _dss2pmd_load!(data_eng, data_dss, import_all) -Adds PowerModels-style loads to `pmd_data` from `dss_data`. +Adds PowerModels-style loads to `data_eng` from `data_dss`. """ -function _dss2pmd_load_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool, ground_terminal::Int=4) - - for load in get(dss_data, "load", []) - _apply_like!(load, dss_data, "load") - defaults = _apply_ordered_properties(_create_load(load["bus1"], load["name"]; _to_sym_keys(load)...), load) +function _dss2eng_load!(data_eng::Dict, data_dss::Dict, import_all::Bool, ground_terminal::Int=4) + for (name, dss_obj) in get(data_dss, "load", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "load") + defaults = _apply_ordered_properties(_create_load(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) # parse the model model = defaults["model"] @@ -205,14 +155,14 @@ function _dss2pmd_load_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool, gro # now we can create the load; if you do not have the correct model, # pd/qd fields will be populated by default (should not happen for constant current/impedance) - loadDict = add_load!(pmd_data, id=defaults["name"], model=model, connections=connections, bus=bus, configuration=conf) + eng_obj = add_load!(data_eng, id=name, model=model, connections=connections, bus=bus, configuration=conf) # if the ground is used directly, register load if 0 in connections - if !haskey(pmd_data["bus"][bus], "awaiting_ground") - pmd_data["bus"][bus]["awaiting_ground"] = [] + if !haskey(data_eng["bus"][bus], "awaiting_ground") + data_eng["bus"][bus]["awaiting_ground"] = [] end - push!(pmd_data["bus"][bus]["awaiting_ground"], loadDict) + push!(data_eng["bus"][bus]["awaiting_ground"], eng_obj) end kv = defaults["kv"] @@ -221,34 +171,29 @@ function _dss2pmd_load_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool, gro end if model=="constant_power" - loadDict["pd"] = fill(defaults["kw"]/nphases, nphases) - loadDict["qd"] = fill(defaults["kvar"]/nphases, nphases) + eng_obj["pd"] = fill(defaults["kw"]/nphases, nphases) + eng_obj["qd"] = fill(defaults["kvar"]/nphases, nphases) else - loadDict["pd_ref"] = fill(defaults["kw"]/nphases, nphases) - loadDict["qd_ref"] = fill(defaults["kvar"]/nphases, nphases) - loadDict["vnom"] = kv + eng_obj["pd_ref"] = fill(defaults["kw"]/nphases, nphases) + eng_obj["qd_ref"] = fill(defaults["kvar"]/nphases, nphases) + eng_obj["vnom"] = kv end - #loadDict["status"] = convert(Int, defaults["enabled"]) - - #loadDict["source_id"] = "load.$load_name" + eng_obj["status"] = convert(Int, defaults["enabled"]) + eng_obj["source_id"] = "load.$name" - used = ["phases", "bus1", "name"] - _PMs._import_remaining!(loadDict, defaults, import_all; exclude=used) + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end end end -""" - _dss2pmd_shunt!(pmd_data, dss_data, import_all) - -Adds PowerModels-style shunts to `pmd_data` from `dss_data`. -""" -function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - - for shunt in get(dss_data, "capacitor", []) - _apply_like!(shunt, dss_data, "capacitor") - defaults = _apply_ordered_properties(_create_capacitor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) +"Adds capacitors to `data_eng` from `data_dss`" +function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + for (name, dss_obj) in get(data_dss, "capacitor", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "capacitor") + defaults = _apply_ordered_properties(_create_capacitor(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) nphases = defaults["phases"] @@ -258,7 +203,7 @@ function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) bus_name = _parse_busname(defaults["bus1"])[1] bus2_name = _parse_busname(defaults["bus2"])[1] if bus_name!=bus2_name - Memento.error("Capacitor $(defaults["name"]): bus1 and bus2 should connect to the same bus.") + Memento.error("Capacitor $(name): bus1 and bus2 should connect to the same bus.") end f_terminals = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) @@ -285,47 +230,58 @@ function _dss2pmd_shunt_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) terminals, B = calc_shunt(f_terminals, t_terminals, b) # if one terminal is ground (0), reduce shunt addmittance matrix - terminals, B = ground_shunt(terminals, B, 0) + terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) + + eng_obj = add_shunt!(data_eng, id=name, status=convert(Int, defaults["enabled"]), bus=bus_name, connections=terminals, g_sh=fill(0.0, size(B)...), b_sh=B) - shuntDict = add_shunt!(pmd_data, id=defaults["name"], status=convert(Int, defaults["enabled"]), - bus=bus_name, connections=terminals, - g_sh=fill(0.0, size(B)...), b_sh=B - ) + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end - used = ["bus1", "phases", "name"] - _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) + if !haskey(data_eng, "capacitor") + data_eng["capacitor"] = Dict{String,Any}() + end - pmd_data["shunt"][shuntDict["id"]] = shuntDict + data_eng["capacitor"][name] = eng_obj end +end + +"Adds shunt reactors to `data_eng` from `data_dss`" +function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) #TODO revisit this in the future - # for shunt in get(dss_data, "reactor", []) - # if !haskey(shunt, "bus2") - # _apply_like!(shunt, dss_data, "reactor") - # defaults = _apply_ordered_properties(_create_reactor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) - # - # shuntDict = Dict{String,Any}() - # - # nconductors = pmd_data["conductors"] + # for (name, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) + # if !haskey(dss_obj, "bus2") + # _apply_like!(dss_obj, data_dss, "reactor") + # defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + + # eng_obj = Dict{String,Any}() + + # nphases = defaults["phases"] # name, nodes = _parse_busname(defaults["bus1"]) - # - # Zbase = (pmd_data["basekv"] / sqrt(3.0))^2 * nconductors / pmd_data["baseMVA"] # Use single-phase base impedance for each phase - # Gcap = Zbase * sum(defaults["kvar"]) / (nconductors * 1e3 * (pmd_data["basekv"] / sqrt(3.0))^2) - # - # shuntDict["shunt_bus"] = find_bus(name, pmd_data) - # shuntDict["name"] = defaults["name"] - # shuntDict["gs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) # TODO: - # shuntDict["bs"] = _PMs.MultiConductorVector(_parse_array(Gcap, nodes, nconductors)) - # shuntDict["status"] = convert(Int, defaults["enabled"]) - # shuntDict["index"] = length(pmd_data["shunt"]) + 1 - # - # shuntDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - # shuntDict["source_id"] = "reactor.$(defaults["name"])" - # - # used = ["bus1", "phases", "name"] - # _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) - # - # push!(pmd_data["shunt"], shuntDict) + + # Zbase = (data_eng["basekv"] / sqrt(3.0))^2 * nphases / data_eng["baseMVA"] # Use single-phase base impedance for each phase + # Gcap = Zbase * sum(defaults["kvar"]) / (nphases * 1e3 * (data_eng["basekv"] / sqrt(3.0))^2) + + # eng_obj["shunt_bus"] = find_bus(name, data_eng) + # eng_obj["name"] = name + # eng_obj["gs"] = _parse_array(0.0, nodes, nphases) # TODO: + # eng_obj["bs"] = _parse_array(Gcap, nodes, nconductors) + # eng_obj["status"] = convert(Int, defaults["enabled"]) + # eng_obj["index"] = length(data_eng["shunt"]) + 1 + + # eng_obj["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + # eng_obj["source_id"] = "reactor.$(name)" + + # if import_all + # _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + # end + + # if !haskey(data_eng, "shunt_reactor") + # data_eng["shunt_reactor"] = Dict{String,Any}() + # end + + # data_eng["shunt_reactor"][name] = eng_obj # end # end end @@ -335,7 +291,7 @@ end Given a vector and a list of elements to find, this method will return a list of the positions of the elements in that vector. """ -function get_inds(vec::Array{<:Any, 1}, els::Array{<:Any, 1}) +function _get_idxs(vec::Array{<:Any, 1}, els::Array{<:Any, 1}) ret = Array{Int, 1}(undef, length(els)) for (i,f) in enumerate(els) for (j,l) in enumerate(vec) @@ -367,10 +323,10 @@ Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this method will calculate the reduced addmittance matrix if terminal 'ground' is grounded. """ -function ground_shunt(cnds, Y, ground) +function _calc_ground_shunt_admittance_matrix(cnds, Y, ground) if ground in cnds cndsr = setdiff(cnds, ground) - cndsr_inds = get_inds(cnds, cndsr) + cndsr_inds = _get_idxs(cnds, cndsr) Yr = Y[cndsr_inds, cndsr_inds] return (cndsr, Yr) else @@ -379,269 +335,176 @@ function ground_shunt(cnds, Y, ground) end -function rm_floating_cnd(cnds, Y, f) +function _rm_floating_cnd(cnds, Y, f) P = setdiff(cnds, f) - f_inds = get_inds(cnds, [f]) - P_inds = get_inds(cnds, P) + f_inds = _get_idxs(cnds, [f]) + P_inds = _get_idxs(cnds, P) Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] return (P,Yrm) end -""" - _dss2pmd_gen!(pmd_data, dss_data, import_all) - -Adds PowerModels-style generators to `pmd_data` from `dss_data`. -""" -function _dss2pmd_gen_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "gen") - pmd_data["gen"] = [] - end +"Adds generators to `data_eng` from `data_dss`" +function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + for (name, dss_obj) in get(data_dss, "generator", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "generator") + defaults = _apply_ordered_properties(_create_generator(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) - # # sourcebus generator (created by circuit) - # circuit = dss_data["circuit"][1] - # defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), "sourcegen"; _to_sym_keys(circuit)...) - # - # genDict = Dict{String,Any}() - # - # nconductors = pmd_data["conductors"] - # name, nodes = _parse_busname(defaults["bus1"]) - # - # genDict["gen_bus"] = find_bus("virtual_sourcebus", pmd_data) - # genDict["name"] = defaults["name"] - # genDict["gen_status"] = convert(Int, defaults["enabled"]) - # - # # TODO: populate with VSOURCE properties - # genDict["pg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) - # genDict["qg"] = _PMs.MultiConductorVector(_parse_array( 0.0, nodes, nconductors)) - # - # genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) - # genDict["qmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) - # - # genDict["pmin"] = _PMs.MultiConductorVector(_parse_array(-NaN, nodes, nconductors)) - # genDict["pmax"] = _PMs.MultiConductorVector(_parse_array( NaN, nodes, nconductors)) - # - # genDict["model"] = 2 - # genDict["startup"] = 0.0 - # genDict["shutdown"] = 0.0 - # genDict["ncost"] = 3 - # genDict["cost"] = [0.0, 1.0, 0.0] - # - # genDict["index"] = length(pmd_data["gen"]) + 1 - # - # genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - # genDict["source_id"] = "vsource.$(defaults["name"])" - # - # used = ["name", "phases", "bus1"] - # _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) - # - # push!(pmd_data["gen"], genDict) - - - for gen in get(dss_data, "generator", []) - _apply_like!(gen, dss_data, "generator") - defaults = _apply_ordered_properties(_create_generator(gen["bus1"], gen["name"]; _to_sym_keys(gen)...), gen) - - genDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - genDict["gen_bus"] = find_bus(name, pmd_data) - genDict["name"] = defaults["name"] - genDict["gen_status"] = convert(Int, defaults["enabled"]) - genDict["pg"] = _PMs.MultiConductorVector(_parse_array(defaults["kw"] / (1e3 * nconductors), nodes, nconductors)) - genDict["qg"] = _PMs.MultiConductorVector(_parse_array(defaults["kvar"] / (1e3 * nconductors), nodes, nconductors)) - genDict["vg"] = _PMs.MultiConductorVector(_parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors)) - - genDict["qmin"] = _PMs.MultiConductorVector(_parse_array(defaults["minkvar"] / (1e3 * nconductors), nodes, nconductors)) - genDict["qmax"] = _PMs.MultiConductorVector(_parse_array(defaults["maxkvar"] / (1e3 * nconductors), nodes, nconductors)) - - genDict["apf"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - - genDict["pmax"] = genDict["pg"] # Assumes generator is at rated power - genDict["pmin"] = 0.0 * genDict["pg"] # 0% of pmax - - genDict["pc1"] = genDict["pmax"] - genDict["pc2"] = genDict["pmin"] - genDict["qc1min"] = genDict["qmin"] - genDict["qc1max"] = genDict["qmax"] - genDict["qc2min"] = genDict["qmin"] - genDict["qc2max"] = genDict["qmax"] - - # For distributed generation ramp rates are not usually an issue - # and they are not supported in OpenDSS - genDict["ramp_agc"] = genDict["pmax"] - - genDict["ramp_q"] = _PMs.MultiConductorVector(_parse_array(max.(abs.(genDict["qmin"].values), abs.(genDict["qmax"].values)), nodes, nconductors)) - genDict["ramp_10"] = genDict["pmax"] - genDict["ramp_30"] = genDict["pmax"] - - genDict["control_model"] = defaults["model"] + eng_obj = Dict{String,Any}() - # if PV generator mode convert attached bus to PV bus - if genDict["control_model"] == 3 - pmd_data["bus"][genDict["gen_bus"]]["bus_type"] = 2 - end + eng_obj["phases"] = defaults["phases"] - genDict["model"] = 2 - genDict["startup"] = 0.0 - genDict["shutdown"] = 0.0 - genDict["ncost"] = 3 - genDict["cost"] = [0.0, 1.0, 0.0] + eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] - genDict["index"] = length(pmd_data["gen"]) + 1 + eng_obj["pg"] = defaults["kw"] + eng_obj["qg"] = defaults["kvar"] + eng_obj["vg"] = defaults["kv"] - genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - genDict["source_id"] = "generator.$(defaults["name"])" + eng_obj["control_model"] = defaults["model"] - used = ["name", "phases", "bus1"] - _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) + # if PV generator mode convert attached bus to PV bus + # if eng_obj["control_model"] == 3 + # data_eng["bus"][eng_obj["bus"]]["bus_type"] = 2 + # end - push!(pmd_data["gen"], genDict) - end + eng_obj["model"] = 2 + eng_obj["startup"] = 0.0 + eng_obj["shutdown"] = 0.0 + eng_obj["ncost"] = 3 + eng_obj["cost"] = [0.0, 1.0, 0.0] - for pv in get(dss_data, "pvsystem", []) - Memento.warn(_LOGGER, "Converting PVSystem \"$(pv["name"])\" into generator with limits determined by OpenDSS property 'kVA'") + eng_obj["status"] = convert(Int, defaults["enabled"]) - _apply_like!(pv, dss_data, "pvsystem") - defaults = _apply_ordered_properties(_create_pvsystem(pv["bus1"], pv["name"]; _to_sym_keys(pv)...), pv) + eng_obj["source_id"] = "generator.$(name)" - pvDict = Dict{String,Any}() + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) + if !haskey(data_eng, "generator") + data_eng["generator"] = Dict{String,Any}() + end - pvDict["name"] = defaults["name"] - pvDict["gen_bus"] = find_bus(name, pmd_data) + data_eng["generator"][name] = eng_obj + end +end - pvDict["pg"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) - pvDict["qg"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) - pvDict["vg"] = _PMs.MultiConductorVector(_parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors)) - pvDict["pmin"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - pvDict["pmax"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) +function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + for (name, dss_obj) in get(data_dss, "linecode", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "linecode") - pvDict["qmin"] = -_PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) - pvDict["qmax"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors)) + dss_obj["units"] = get(dss_obj, "units", "none") + dss_obj["circuit_basefreq"] = data_eng["settings"]["basefreq"] - pvDict["gen_status"] = convert(Int, defaults["enabled"]) + defaults = _apply_ordered_properties(_create_linecode(name; _to_sym_keys(dss_obj)...), dss_obj) - pvDict["model"] = 2 - pvDict["startup"] = 0.0 - pvDict["shutdown"] = 0.0 - pvDict["ncost"] = 3 - pvDict["cost"] = [0.0, 1.0, 0.0] + eng_obj = Dict{String,Any}() - pvDict["index"] = length(pmd_data["gen"]) + 1 + nphases = size(defaults["rmatrix"])[1] - pvDict["active_phases"] = [nodes[n] > 0 ? 1 : 0 for n in 1:nconductors] - pvDict["source_id"] = "pvsystem.$(defaults["name"])" + eng_obj["rmatrix"] = reshape(defaults["rmatrix"], nphases, nphases) + eng_obj["xmatrix"] = reshape(defaults["xmatrix"], nphases, nphases) + eng_obj["cmatrix"] = reshape(defaults["cmatrix"], nphases, nphases) - used = ["name", "phases", "bus1"] - _PMs._import_remaining!(pvDict, defaults, import_all; exclude=used) + if !haskey(data_eng, "linecode") + data_eng["linecode"] = Dict{String,Any}() + end - push!(pmd_data["gen"], pvDict) + data_eng["linecode"][name] = eng_obj end end """ - _dss2pmd_line!(pmd_data, dss_data, import_all) + _dss2pmd_line!(data_eng, data_dss, import_all) -Adds PowerModels-style lines to `pmd_data` from `dss_data`. +Adds PowerModels-style lines to `data_eng` from `data_dss`. """ -function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "branch") - pmd_data["line"] = Dict{String, Any}() - end +function _dss2eng_line!(data_eng::Dict, data_dss::Dict, import_all::Bool) + for (name, dss_obj) in get(data_dss, "line", Dict()) + _apply_like!(dss_obj, data_dss, "line") - #nconductors = pmd_data["conductors"] + if haskey(dss_obj, "basefreq") && dss_obj["basefreq"] != data_eng["settings"]["basefreq"] + Memento.warn(_LOGGER, "basefreq=$(dss_obj["basefreq"]) on line $(dss_obj["name"]) does not match circuit basefreq=$(data_eng["settings"]["basefreq"])") + dss_obj["circuit_basefreq"] = data_eng["settings"]["basefreq"] + end - for line in get(dss_data, "line", []) - _apply_like!(line, dss_data, "line") + defaults = _apply_ordered_properties(_create_line(dss_obj["bus1"], dss_obj["bus2"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) - if haskey(line, "linecode") - linecode = deepcopy(_get_linecode(dss_data, get(line, "linecode", ""))) - if haskey(linecode, "like") - linecode = merge(find_component(dss_data, linecode["like"], "linecode"), linecode) - end + eng_obj = Dict{String,Any}() - linecode["units"] = get(line, "units", "none") == "none" ? "none" : get(linecode, "units", "none") - linecode["circuit_basefreq"] = pmd_data["settings"]["basefreq"] + eng_obj["f_bus"] = _parse_busname(defaults["bus1"])[1] + eng_obj["t_bus"] = _parse_busname(defaults["bus2"])[1] - linecode = _create_linecode(get(linecode, "name", ""); _to_sym_keys(linecode)...) - delete!(linecode, "name") - else - linecode = Dict{String,Any}() - end + eng_obj["length"] = defaults["length"] - if haskey(line, "basefreq") && line["basefreq"] != pmd_data["settings"]["basefreq"] - Memento.warn(_LOGGER, "basefreq=$(line["basefreq"]) on line $(line["name"]) does not match circuit basefreq=$(pmd_data["settings"]["basefreq"])") - line["circuit_basefreq"] = pmd_data["settings"]["basefreq"] - end - - defaults = _apply_ordered_properties(_create_line(line["bus1"], line["bus2"], line["name"]; _to_sym_keys(line)...), line; code_dict=linecode) + # TODO nphases not being read correctly from linecode; infer indirectly instead + nphases = size(defaults["rmatrix"])[1] + eng_obj["n_conductors"] = nphases - lineDict = Dict{String,Any}() + if haskey(dss_obj, "linecode") + eng_obj["linecode"] = dss_obj["linecode"] + end - lineDict["id"] = defaults["name"] - lineDict["f_bus"] = _parse_busname(defaults["bus1"])[1] - lineDict["t_bus"] = _parse_busname(defaults["bus2"])[1] + for key in ["rmatrix", "xmatrix", "cmatrix"] + if haskey(dss_obj, key) + eng_obj[key] = reshape(defaults[key], nphases, nphases) + end + end - #lineDict["length"] = defaults["length"] + eng_obj["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) + eng_obj["t_connections"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) - #TODO nphases not being read correctly from linecode; infer indirectly instead - nphases = size(defaults["rmatrix"])[1] - lineDict["n_conductors"] = nphases - #TODO fix this in a cleaner way - # ensure that this actually is a matrix and not a vector for 1x1 data - rmatrix = reshape(defaults["rmatrix"], nphases, nphases) - xmatrix = reshape(defaults["xmatrix"], nphases, nphases) - cmatrix = reshape(defaults["cmatrix"], nphases, nphases) + # TODO calculate these in the conversion/mapping to mathematical model + # eng_obj["rs"] = rmatrix * defaults["length"] + # eng_obj["xs"] = xmatrix * defaults["length"] - lineDict["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) - lineDict["t_connections"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) + # eng_obj["g_fr"] = fill(0.0, nphases, nphases) + # eng_obj["g_to"] = fill(0.0, nphases, nphases) - lineDict["rs"] = rmatrix * defaults["length"] - lineDict["xs"] = xmatrix * defaults["length"] + # eng_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 + # eng_obj["b_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 - lineDict["g_fr"] = fill(0.0, nphases, nphases) - lineDict["g_to"] = fill(0.0, nphases, nphases) + eng_obj["status"] = convert(Int, defaults["enabled"]) - lineDict["b_fr"] = (2.0 * pi * pmd_data["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 - lineDict["b_to"] = (2.0 * pi * pmd_data["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 + eng_obj["source_id"] = "line.$(name)" - #lineDict["c_rating_a"] = fill(defaults["normamps"], nphases) - #lineDict["c_rating_b"] = fill(defaults["emergamps"], nphases) - #lineDict["c_rating_c"] = fill(defaults["emergamps"], nphases) - - lineDict["status"] = convert(Int, defaults["enabled"]) + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end - #lineDict["source_id"] = "line.$(defaults["name"])" + if defaults["switch"] + if !haskey(data_eng, "switch") + data_eng["switch"] = Dict{String,Any}() + end - #used = ["name", "bus1", "bus2", "rmatrix", "xmatrix"] - #_PMs._import_remaining!(lineDict, defaults, import_all; exclude=used) + data_eng["switch"][name] = eng_obj + else + if !haskey(data_eng, "line") + data_eng["line"] = Dict{String, Any}() + end - pmd_data["line"][lineDict["id"]] = lineDict + data_eng["line"][name] = eng_obj + end end end """ - _dss2pmd_transformer!(pmd_data, dss_data, import_all) + _dss2pmd_transformer!(data_eng, data_dss, import_all) -Adds ThreePhasePowerModels-style transformers to `pmd_data` from `dss_data`. +Adds PMD-style transformers to `data_eng` from `data_dss`. """ -function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "transformer_nw") - pmd_data["transformer_nw"] = Dict{String,Any}() +function _dss2eng_transformer!(data_eng::Dict, data_dss::Dict, import_all::Bool) + if !haskey(data_eng, "transformer_nw") + data_eng["transformer_nw"] = Dict{String,Any}() end - for transformer in get(dss_data, "transformer", []) - _apply_like!(transformer, dss_data, "transformer") - defaults = _apply_ordered_properties(_create_transformer(transformer["name"]; _to_sym_keys(transformer)...), transformer) + for (name, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "transformer") + defaults = _apply_ordered_properties(_create_transformer(dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) nphases = defaults["phases"] nrw = defaults["windings"] @@ -659,124 +522,124 @@ function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bo Memento.error(_LOGGER, "For now parsing of xscarray is not supported. At most 3 windings are allowed, not $nrw.") end - transDict = Dict{String, Any}() - transDict["id"] = defaults["name"] - transDict["bus"] = Array{String, 1}(undef, nrw) - transDict["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) - transDict["tm"] = Array{Array{Real, 1}, 1}(undef, nrw) - transDict["fixed"] = [[true for i in 1:nphases] for j in 1:nrw] - transDict["vnom"] = [defaults["kvs"][w] for w in 1:nrw] - transDict["snom"] = [defaults["kvas"][w] for w in 1:nrw] - transDict["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) - transDict["configuration"] = Array{String, 1}(undef, nrw) - transDict["polarity"] = Array{Int, 1}(undef, nrw) + eng_obj = Dict{String, Any}() + eng_obj["bus"] = Array{String, 1}(undef, nrw) + eng_obj["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) + eng_obj["tm"] = Array{Array{Real, 1}, 1}(undef, nrw) + eng_obj["fixed"] = [[true for i in 1:nphases] for j in 1:nrw] + eng_obj["vnom"] = [defaults["kvs"][w] for w in 1:nrw] + eng_obj["snom"] = [defaults["kvas"][w] for w in 1:nrw] + eng_obj["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) + eng_obj["configuration"] = Array{String, 1}(undef, nrw) + eng_obj["polarity"] = Array{Int, 1}(undef, nrw) for w in 1:nrw - transDict["bus"][w] = _parse_busname(defaults["buses"][w])[1] + eng_obj["bus"][w] = _parse_busname(defaults["buses"][w])[1] conn = dyz_map[defaults["conns"][w]] - transDict["configuration"][w] = conn + eng_obj["configuration"][w] = conn terminals_default = conn=="wye" ? [1:nphases..., 0] : collect(1:nphases) terminals_w = _get_conductors_ordered_dm(defaults["buses"][w], default=terminals_default) - transDict["connections"][w] = terminals_w + eng_obj["connections"][w] = terminals_w if 0 in terminals_w - bus = transDict["bus"][w] - if !haskey(pmd_data["bus"][bus], "awaiting_ground") - pmd_data["bus"][bus]["awaiting_ground"] = [] + bus = eng_obj["bus"][w] + if !haskey(data_eng["bus"][bus], "awaiting_ground") + data_eng["bus"][bus]["awaiting_ground"] = [] end - push!(pmd_data["bus"][bus]["awaiting_ground"], transDict) + push!(data_eng["bus"][bus]["awaiting_ground"], eng_obj) end - transDict["polarity"][w] = 1 - transDict["tm"][w] = fill(defaults["taps"][w], nphases) + eng_obj["polarity"][w] = 1 + eng_obj["tm"][w] = fill(defaults["taps"][w], nphases) end - #transDict["source_id"] = "transformer.$(defaults["name"])" + #eng_obj["source_id"] = "transformer.$(name)" if !isempty(defaults["bank"]) - transDict["bank"] = defaults["bank"] + eng_obj["bank"] = defaults["bank"] end # loss model (converted to SI units, referred to secondary) - transDict["rs"] = [defaults["%rs"][w]/100 for w in 1:nrw] - transDict["noloadloss"] = defaults["%noloadloss"]/100 - transDict["imag"] = defaults["%imag"]/100 + eng_obj["rs"] = [defaults["%rs"][w]/100 for w in 1:nrw] + eng_obj["noloadloss"] = defaults["%noloadloss"]/100 + eng_obj["imag"] = defaults["%imag"]/100 if nrw==2 - transDict["xsc"] = [defaults["xhl"]]/100 + eng_obj["xsc"] = [defaults["xhl"]]/100 elseif nrw==3 - transDict["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 + eng_obj["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 end - add_virtual!(pmd_data, "transformer_nw", create_transformer_nw(; - Dict(Symbol.(keys(transDict)).=>values(transDict))... + add_virtual!(data_eng, "transformer_nw", create_transformer_nw(; + Dict(Symbol.(keys(eng_obj)).=>values(eng_obj))... )) end end """ - _dss2pmd_reactor!(pmd_data, dss_data, import_all) + _dss2pmd_reactor!(data_eng, data_dss, import_all) -Adds PowerModels-style branch components based on DSS reactors to `pmd_data` from `dss_data` +Adds PowerModels-style branch components based on DSS reactors to `data_eng` from `data_dss` """ -function _dss2pmd_reactor_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "branch") - pmd_data["branch"] = [] +function _dss2eng_reactor!(data_eng::Dict, data_dss::Dict, import_all::Bool) + if !haskey(data_eng, "branch") + data_eng["branch"] = [] end - if haskey(dss_data, "reactor") + if haskey(data_dss, "reactor") Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating like line") - for reactor in dss_data["reactor"] + for (name, reactor) in data_dss["reactor"] if haskey(reactor, "bus2") - _apply_like!(reactor, dss_data, "reactor") + _apply_like!(reactor, data_dss, "reactor") defaults = _apply_ordered_properties(_create_reactor(reactor["bus1"], reactor["name"], reactor["bus2"]; _to_sym_keys(reactor)...), reactor) - reactDict = Dict{String,Any}() + eng_obj = Dict{String,Any}() - nconductors = pmd_data["conductors"] + nconductors = data_eng["conductors"] f_bus, nodes = _parse_busname(defaults["bus1"]) t_bus = _parse_busname(defaults["bus2"])[1] - reactDict["name"] = defaults["name"] - reactDict["f_bus"] = find_bus(f_bus, pmd_data) - reactDict["t_bus"] = find_bus(t_bus, pmd_data) + eng_obj["name"] = name + eng_obj["f_bus"] = find_bus(f_bus, data_eng) + eng_obj["t_bus"] = find_bus(t_bus, data_eng) - reactDict["br_r"] = _PMs.MultiConductorMatrix(_parse_matrix(diagm(0 => fill(0.2, nconductors)), nodes, nconductors)) - reactDict["br_x"] = _PMs.MultiConductorMatrix(_parse_matrix(zeros(nconductors, nconductors), nodes, nconductors)) + eng_obj["br_r"] = _PMs.MultiConductorMatrix(_parse_matrix(diagm(0 => fill(0.2, nconductors)), nodes, nconductors)) + eng_obj["br_x"] = _PMs.MultiConductorMatrix(_parse_matrix(zeros(nconductors, nconductors), nodes, nconductors)) - reactDict["g_fr"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - reactDict["g_to"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - reactDict["b_fr"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - reactDict["b_to"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + eng_obj["g_fr"] = _parse_array(0.0, nodes, nconductors) + eng_obj["g_to"] = _parse_array(0.0, nodes, nconductors) + eng_obj["b_fr"] = _parse_array(0.0, nodes, nconductors) + eng_obj["b_to"] = _parse_array(0.0, nodes, nconductors) for key in ["g_fr", "g_to", "b_fr", "b_to"] - reactDict[key] = _PMs.MultiConductorMatrix(LinearAlgebra.diagm(0=>reactDict[key].values)) + eng_obj[key] = _PMs.MultiConductorMatrix(LinearAlgebra.diagm(0=>eng_obj[key].values)) end - reactDict["c_rating_a"] = _PMs.MultiConductorVector(_parse_array(defaults["normamps"], nodes, nconductors)) - reactDict["c_rating_b"] = _PMs.MultiConductorVector(_parse_array(defaults["emergamps"], nodes, nconductors)) - reactDict["c_rating_c"] = _PMs.MultiConductorVector(_parse_array(defaults["emergamps"], nodes, nconductors)) + eng_obj["c_rating_a"] = _parse_array(defaults["normamps"], nodes, nconductors) + eng_obj["c_rating_b"] = _parse_array(defaults["emergamps"], nodes, nconductors) + eng_obj["c_rating_c"] = _parse_array(defaults["emergamps"], nodes, nconductors) - reactDict["tap"] = _PMs.MultiConductorVector(_parse_array(1.0, nodes, nconductors, NaN)) - reactDict["shift"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + eng_obj["tap"] = _parse_array(1.0, nodes, nconductors, NaN) + eng_obj["shift"] = _parse_array(0.0, nodes, nconductors) - reactDict["br_status"] = convert(Int, defaults["enabled"]) + eng_obj["br_status"] = convert(Int, defaults["enabled"]) - reactDict["angmin"] = _PMs.MultiConductorVector(_parse_array(-60.0, nodes, nconductors, -60.0)) - reactDict["angmax"] = _PMs.MultiConductorVector(_parse_array( 60.0, nodes, nconductors, 60.0)) + eng_obj["angmin"] = _parse_array(-60.0, nodes, nconductors, -60.0) + eng_obj["angmax"] = _parse_array( 60.0, nodes, nconductors, 60.0) - reactDict["transformer"] = true + eng_obj["transformer"] = true - reactDict["index"] = length(pmd_data["branch"]) + 1 + eng_obj["index"] = length(data_eng["branch"]) + 1 nodes = .+([_parse_busname(defaults[n])[2] for n in ["bus1", "bus2"]]...) - reactDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - reactDict["source_id"] = "reactor.$(defaults["name"])" + eng_obj["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] + eng_obj["source_id"] = "reactor.$(name)" - used = [] - _PMs._import_remaining!(reactDict, defaults, import_all; exclude=used) + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end - push!(pmd_data["branch"], reactDict) + push!(data_eng["branch"], eng_obj) end end end @@ -784,92 +647,100 @@ end """ - _dss2pmd_pvsystem!(pmd_data, dss_data) + _dss2pmd_pvsystem!(data_eng, data_dss) -Adds PowerModels-style pvsystems to `pmd_data` from `dss_data`. +Adds PowerModels-style pvsystems to `data_eng` from `data_dss`. """ -function _dss2pmd_pvsystem_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "pvsystem") - pmd_data["pvsystem"] = [] - end +function _dss2eng_pvsystem!(data_eng::Dict, data_dss::Dict, import_all::Bool) + + for (name, dss_obj) in get(data_dss, "pvsystem", Dict{String,Any}()) + Memento.warn(_LOGGER, "Converting PVSystem \"$(dss_obj["name"])\" into generator with limits determined by OpenDSS property 'kVA'") + + _apply_like!(dss_obj, data_dss, "pvsystem") + defaults = _apply_ordered_properties(_create_pvsystem(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + + eng_obj = Dict{String,Any}() - for pvsystem in get(dss_data, "pvsystem", []) - _apply_like!(pvsystem, dss_data, "pvsystem") - defaults = _apply_ordered_properties(_create_pvsystem(pvsystem["bus1"], pvsystem["name"]; _to_sym_keys(pvsystem)...), pvsystem) + eng_obj["name"] = name + eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] - pvsystemDict = Dict{String,Any}() + eng_obj["phases"] = defaults["phases"] - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) + eng_obj["ppv"] = defaults["kva"] + eng_obj["qpv"] = defaults["kva"] + eng_obj["vpv"] = defaults["kv"] - pvsystemDict["name"] = defaults["name"] - pvsystemDict["pv_bus"] = find_bus(name, pmd_data) - pvsystemDict["p"] = _PMs.MultiConductorVector(_parse_array(defaults["kw"] / 1e3, nodes, nconductors)) - pvsystemDict["q"] = _PMs.MultiConductorVector(_parse_array(defaults["kvar"] / 1e3, nodes, nconductors)) - pvsystemDict["status"] = convert(Int, defaults["enabled"]) + eng_obj["model"] = 2 + eng_obj["startup"] = 0.0 + eng_obj["shutdown"] = 0.0 + eng_obj["ncost"] = 3 + eng_obj["cost"] = [0.0, 1.0, 0.0] - pvsystemDict["index"] = length(pmd_data["pvsystem"]) + 1 + eng_obj["status"] = convert(Int, defaults["enabled"]) - pvsystemDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - pvsystemDict["source_id"] = "pvsystem.$(defaults["name"])" + eng_obj["source_id"] = "pvsystem.$(name)" - used = ["phases", "bus1", "name"] - _PMs._import_remaining!(pvsystemDict, defaults, import_all; exclude=used) + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end + + if !haskey(data_eng, "pvsystem") + data_eng["pvsystem"] = Dict{String,Any}() + end - push!(pmd_data["pvsystem"], pvsystemDict) + data_eng["pvsystem"][name] = eng_obj end end """ - _dss2pmd_storage!(pmd_data, dss_data, import_all) + _dss2pmd_storage!(data_eng, data_dss, import_all) -Adds PowerModels-style storage to `pmd_data` from `dss_data` +Adds PowerModels-style storage to `data_eng` from `data_dss` """ -function _dss2pmd_storage_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "storage") - pmd_data["storage"] = [] - end +function _dss2eng_storage!(data_eng::Dict, data_dss::Dict, import_all::Bool) + + for (name, dss_obj) in get(data_dss, "storage", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "storage") + defaults = _apply_ordered_properties(_create_storage(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) - for storage in get(dss_data, "storage", []) - _apply_like!(storage, dss_data, "storage") - defaults = _apply_ordered_properties(_create_storage(storage["bus1"], storage["name"]; _to_sym_keys(storage)...), storage) + eng_obj = Dict{String,Any}() - storageDict = Dict{String,Any}() + eng_obj["phases"] = defaults["phases"] - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) + eng_obj["name"] = name + eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] - storageDict["name"] = defaults["name"] - storageDict["storage_bus"] = find_bus(name, pmd_data) - storageDict["energy"] = defaults["kwhstored"] / 1e3 - storageDict["energy_rating"] = defaults["kwhrated"] / 1e3 - storageDict["charge_rating"] = defaults["%charge"] * defaults["kwrated"] / 1e3 / 100.0 - storageDict["discharge_rating"] = defaults["%discharge"] * defaults["kwrated"] / 1e3 / 100.0 - storageDict["charge_efficiency"] = defaults["%effcharge"] / 100.0 - storageDict["discharge_efficiency"] = defaults["%effdischarge"] / 100.0 - storageDict["thermal_rating"] = _PMs.MultiConductorVector(_parse_array(defaults["kva"] / 1e3 / nconductors, nodes, nconductors)) - storageDict["qmin"] = _PMs.MultiConductorVector(_parse_array(-defaults["kvar"] / 1e3 / nconductors, nodes, nconductors)) - storageDict["qmax"] = _PMs.MultiConductorVector(_parse_array( defaults["kvar"] / 1e3 / nconductors, nodes, nconductors)) - storageDict["r"] = _PMs.MultiConductorVector(_parse_array(defaults["%r"] / 100.0, nodes, nconductors)) - storageDict["x"] = _PMs.MultiConductorVector(_parse_array(defaults["%x"] / 100.0, nodes, nconductors)) - storageDict["p_loss"] = defaults["%idlingkw"] * defaults["kwrated"] / 1e3 - storageDict["q_loss"] = defaults["%idlingkvar"] * defaults["kvar"] / 1e3 + eng_obj["energy"] = defaults["kwhstored"] + eng_obj["energy_rating"] = defaults["kwhrated"] + eng_obj["charge_rating"] = defaults["%charge"] * defaults["kwrated"] / 100.0 + eng_obj["discharge_rating"] = defaults["%discharge"] * defaults["kwrated"] / 100.0 + eng_obj["charge_efficiency"] = defaults["%effcharge"] / 100.0 + eng_obj["discharge_efficiency"] = defaults["%effdischarge"] / 100.0 + eng_obj["thermal_rating"] = defaults["kva"] + eng_obj["qmin"] = -defaults["kvar"] + eng_obj["qmax"] = defaults["kvar"] + eng_obj["r"] = defaults["%r"] / 100.0 + eng_obj["x"] = defaults["%x"] / 100.0 + eng_obj["p_loss"] = defaults["%idlingkw"] * defaults["kwrated"] + eng_obj["q_loss"] = defaults["%idlingkvar"] * defaults["kvar"] - storageDict["status"] = convert(Int, defaults["enabled"]) + eng_obj["status"] = convert(Int, defaults["enabled"]) - storageDict["ps"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) - storageDict["qs"] = _PMs.MultiConductorVector(_parse_array(0.0, nodes, nconductors)) + eng_obj["ps"] = 0.0 + eng_obj["qs"] = 0.0 - storageDict["index"] = length(pmd_data["storage"]) + 1 + eng_obj["source_id"] = "storage.$(name)" - storageDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - storageDict["source_id"] = "storage.$(defaults["name"])" + if import_all + _import_all(eng_obj, defaults, dss_obj["prop_order"]) + end - used = ["phases", "bus1", "name"] - _PMs._import_remaining!(storageDict, defaults, import_all; exclude=used) + if !haskey(data_eng, "storage") + data_eng["storage"] = Dict{String,Any}() + end - push!(pmd_data["storage"], storageDict) + data_eng["storage"][name] = eng_obj end end @@ -907,15 +778,15 @@ end """ - _correct_duplicate_components!(dss_data) + _correct_duplicate_components!(data_dss) -Finds duplicate components in `dss_data` and merges up, meaning that older +Finds duplicate components in `data_dss` and merges up, meaning that older data (lower indices) is always overwritten by newer data (higher indices). """ -function _correct_duplicate_components!(dss_data::Dict) +function _correct_duplicate_components!(data_dss::Dict) out = Dict{String,Array}() - for (k, v) in dss_data - if !(k in ["options"]) + for (k, v) in data_dss + if !(k in _exclude_duplicate_check) out[k] = [] for comp in v if isa(comp, Dict) @@ -929,37 +800,37 @@ function _correct_duplicate_components!(dss_data::Dict) end end end - merge!(dss_data, out) + merge!(data_dss, out) end "Creates a virtual branch between the `virtual_sourcebus` and `sourcebus` with the impedance given by `circuit`" -function _create_sourcebus_vbranch_dm!(pmd_data::Dict, circuit::Dict) +function _create_sourcebus_vbranch_dm!(data_eng::Dict, circuit::Dict) #TODO convert to pu rs = circuit["rmatrix"] xs = circuit["xmatrix"] N = size(rs)[1] - add_line!(pmd_data, id="_virtual_source_imp", + add_line!(data_eng, id="_virtual_source_imp", f_bus="_virtual_sourcebus", t_bus="sourcebus", f_connections=collect(1:N), t_connections=collect(1:N), rs=rs, xs=xs ) - #vbranch = _create_vbranch!(pmd_data, sourcebus, vsourcebus; name="sourcebus_vbranch", br_r=br_r, br_x=br_x) + #vbranch = _create_vbranch!(data_eng, sourcebus, vsourcebus; name="sourcebus_vbranch", br_r=br_r, br_x=br_x) end "Combines transformers with 'bank' keyword into a single transformer" -function _bank_transformers!(pmd_data::Dict) - transformer_names = Dict(trans["name"] => n for (n, trans) in get(pmd_data, "transformer_comp", Dict())) - bankable_transformers = [trans for trans in values(get(pmd_data, "transformer_comp", Dict())) if haskey(trans, "bank")] +function _bank_transformers!(data_eng::Dict) + transformer_names = Dict(trans["name"] => n for (n, trans) in get(data_eng, "transformer_comp", Dict())) + bankable_transformers = [trans for trans in values(get(data_eng, "transformer_comp", Dict())) if haskey(trans, "bank")] banked_transformers = Dict() for transformer in bankable_transformers bank = transformer["bank"] if !(bank in keys(banked_transformers)) - n = length(pmd_data["transformer_comp"])+length(banked_transformers)+1 + n = length(data_eng["transformer_comp"])+length(banked_transformers)+1 banked_transformers[bank] = deepcopy(transformer) banked_transformers[bank]["name"] = deepcopy(transformer["bank"]) @@ -1004,11 +875,11 @@ function _bank_transformers!(pmd_data::Dict) end for transformer in bankable_transformers - delete!(pmd_data["transformer_comp"], transformer_names[transformer["name"]]) + delete!(data_eng["transformer_comp"], transformer_names[transformer["name"]]) end for transformer in values(banked_transformers) - pmd_data["transformer_comp"]["$(transformer["index"])"] = deepcopy(transformer) + data_eng["transformer_comp"]["$(transformer["index"])"] = deepcopy(transformer) end end @@ -1020,108 +891,95 @@ Parses options defined with the `set` command in OpenDSS. """ function parse_options(options) out = Dict{String,Any}() - if haskey(options, "voltagebases") - out["voltagebases"] = _parse_array(Float64, options["voltagebases"]) - end - if !haskey(options, "defaultbasefreq") - Memento.warn(_LOGGER, "defaultbasefreq is not defined, default for circuit set to 60 Hz") - out["defaultbasefreq"] = 60.0 - else - out["defaultbasefreq"] = parse(Float64, options["defaultbasefreq"]) + for (option, dtype) in _dss_option_dtypes + if haskey(options, option) + value = options[option] + if _isa_array(value) + out[option] = _parse_array(dtype, value) + elseif _isa_matrix(value) + out[option] = _parse_matrix(dtype, value) + else + out[option] = parse(dtype, value) + end + end end - return out end "Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" -function parse_opendss_dm(dss_data::Dict; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict - pmd_data = create_data_model() - - _correct_duplicate_components!(dss_data) - - parse_dss_with_dtypes!(dss_data, ["line", "linecode", "load", "generator", "capacitor", - "reactor", "circuit", "transformer", "pvsystem", - "storage"]) - - if haskey(dss_data, "options") - condensed_opts = [Dict{String,Any}()] - for opt in dss_data["options"] - merge!(condensed_opts[1], opt) - end - dss_data["options"] = condensed_opts - end +function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, bank_transformers::Bool=true)::Dict{String,Any} + data_eng = create_data_model() - merge!(pmd_data, parse_options(get(dss_data, "options", [Dict{String,Any}()])[1])) + parse_dss_with_dtypes!(data_dss, _dss_supported_components) - #pmd_data["per_unit"] = false - #pmd_data["source_type"] = "dss" - #pmd_data["source_version"] = string(VersionNumber("0")) + data_eng["dss_options"] = parse_options(get(data_dss, "options", Dict{String,Any}())) - if haskey(dss_data, "circuit") - circuit = dss_data["circuit"][1] + if haskey(data_dss, "circuit") + circuit = data_dss["circuit"] defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), circuit["name"]; _to_sym_keys(circuit)...) - #pmd_data["name"] = defaults["name"] - pmd_data["settings"]["v_var_scalar"] = 1E3 - pmd_data["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))*1E3/pmd_data["settings"]["v_var_scalar"] - pmd_data["settings"]["set_vbase_bus"] = "sourcebus" + data_eng["name"] = circuit["name"] + data_eng["sourcebus"] = defaults["bus1"] + data_eng["model"] = "engineering" + + data_eng["settings"]["v_var_scalar"] = 1e3 + data_eng["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))*1e3/data_eng["settings"]["v_var_scalar"] + data_eng["settings"]["set_vbase_bus"] = data_eng["sourcebus"] + data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1e6/data_eng["settings"]["v_var_scalar"] + data_eng["settings"]["basefreq"] = get(get(data_dss, "options", Dict()), "defaultbasefreq", 60.0) - pmd_data["settings"]["set_sbase_val"] = defaults["basemva"]*1E6/pmd_data["settings"]["v_var_scalar"] - pmd_data["settings"]["basefreq"] = pop!(pmd_data, "defaultbasefreq") - #pmd_data["pu"] = defaults["pu"] - #pmd_data["conductors"] = defaults["phases"] - #pmd_data["sourcebus"] = defaults["bus1"] + data_eng["files"] = data_dss["filename"] else Memento.error(_LOGGER, "Circuit not defined, not a valid circuit!") end - _dss2pmd_bus_dm!(pmd_data, dss_data, import_all, vmin, vmax) - _dss2pmd_line_dm!(pmd_data, dss_data, import_all) - _dss2pmd_transformer_dm!(pmd_data, dss_data, import_all) + _dss2eng_bus!(data_eng, data_dss, import_all) + _dss2eng_linecode!(data_eng, data_dss, import_all) + _dss2eng_line!(data_eng, data_dss, import_all) - _dss2pmd_load_dm!(pmd_data, dss_data, import_all) - _dss2pmd_shunt_dm!(pmd_data, dss_data, import_all) + # _dss2eng_xfrmcode!(data_eng, data_dss, import_all) + _dss2eng_transformer!(data_eng, data_dss, import_all) + #_dss2eng_line_reactor!(data_eng, data_dss, import_all) - #_dss2pmd_reactor!(pmd_data, dss_data, import_all) - #_dss2pmd_gen!(pmd_data, dss_data, import_all) - #_dss2pmd_pvsystem!(pmd_data, dss_data, import_all) - #_dss2pmd_storage!(pmd_data, dss_data, import_all) + # _dss2eng_loadshape!(data_eng, data_dss, import_all) + _dss2eng_load!(data_eng, data_dss, import_all) - #pmd_data["dcline"] = [] - #pmd_data["switch"] = [] + _dss2eng_capacitor!(data_eng, data_dss, import_all) + _dss2eng_shunt_reactor!(data_eng, data_dss, import_all) - #InfrastructureModels.arrays_to_dicts!(pmd_data) + _dss2eng_generator!(data_eng, data_dss, import_all) - if bank_transformers - _bank_transformers!(pmd_data) - end + _dss2eng_pvsystem!(data_eng, data_dss, import_all) - #_create_sourcebus_vbranch_dm!(pmd_data, defaults) + _dss2eng_storage!(data_eng, data_dss, import_all) - _discover_terminals!(pmd_data) + if bank_transformers + _bank_transformers!(data_eng) + end - #_adjust_sourcegen_bounds!(pmd_data) + _discover_terminals!(data_eng) - #pmd_data["files"] = dss_data["filename"] - return pmd_data + return data_eng end -function _discover_terminals!(pmd_data) - terminals = Dict{String, Set{Int}}([(bus["id"], Set{Int}()) for (_,bus) in pmd_data["bus"]]) - for (_,line) in pmd_data["line"] +"" +function _discover_terminals!(data_eng::Dict{String,<:Any}) + terminals = Dict{String, Set{Int}}([(bus["id"], Set{Int}()) for (_,bus) in data_eng["bus"]]) + + for (_,dss_obj) in data_eng["line"] # ignore 0 terminal - push!(terminals[line["f_bus"]], setdiff(line["f_connections"], [0])...) - push!(terminals[line["t_bus"]], setdiff(line["t_connections"], [0])...) + push!(terminals[dss_obj["f_bus"]], setdiff(dss_obj["f_connections"], [0])...) + push!(terminals[dss_obj["t_bus"]], setdiff(dss_obj["t_connections"], [0])...) end - if haskey(pmd_data, "transformer_nw") - for (_,tr) in pmd_data["transformer_nw"] + if haskey(data_eng, "transformer_nw") + for (_,tr) in data_eng["transformer_nw"] for w in 1:length(tr["bus"]) # ignore 0 terminal push!(terminals[tr["bus"][w]], setdiff(tr["connections"][w], [0])...) @@ -1129,14 +987,14 @@ function _discover_terminals!(pmd_data) end end - for (id, bus) in pmd_data["bus"] - pmd_data["bus"][id]["terminals"] = sort(collect(terminals[id])) + for (id, bus) in data_eng["bus"] + data_eng["bus"][id]["terminals"] = sort(collect(terminals[id])) end # identify neutrals and propagate along cables - bus_neutral = _find_neutrals(pmd_data) + bus_neutral = _find_neutrals(data_eng) - for (id,bus) in pmd_data["bus"] + for (id,bus) in data_eng["bus"] if haskey(bus, "awaiting_ground") || haskey(bus_neutral, id) # this bus will need a neutral if haskey(bus_neutral, id) @@ -1160,7 +1018,7 @@ function _discover_terminals!(pmd_data) else comp["connections"] .+= (comp["connections"].==0)*neutral end - @show comp["connections"] + # @show comp["connections"] end #delete!(bus, "awaiting_ground") end @@ -1170,21 +1028,23 @@ function _discover_terminals!(pmd_data) end end -function _find_neutrals(pmd_data) - vertices = [(id, t) for (id, bus) in pmd_data["bus"] for t in bus["terminals"]] + +"" +function _find_neutrals(data_eng) + vertices = [(id, t) for (id, bus) in data_eng["bus"] for t in bus["terminals"]] neutrals = [] - edges = Set([((line["f_bus"], line["f_connections"][c]),(line["t_bus"], line["t_connections"][c])) for (id, line) in pmd_data["line"] for c in 1:length(line["f_connections"])]) + edges = Set([((dss_obj["f_bus"], dss_obj["f_connections"][c]),(dss_obj["t_bus"], dss_obj["t_connections"][c])) for (id, dss_obj) in data_eng["line"] for c in 1:length(dss_obj["f_connections"])]) - bus_neutrals = [(id,bus["neutral"]) for (id,bus) in pmd_data["bus"] if haskey(bus, "neutral")] + bus_neutrals = [(id,bus["neutral"]) for (id,bus) in data_eng["bus"] if haskey(bus, "neutral")] trans_neutrals = [] - for (_, tr) in pmd_data["transformer_nw"] + for (_, tr) in data_eng["transformer_nw"] for w in 1:length(tr["connections"]) if tr["configuration"][w] == "wye" push!(trans_neutrals, (tr["bus"][w], tr["connections"][w][end])) end end end - load_neutrals = [(load["bus"],load["connections"][end]) for (_,load) in pmd_data["load"] if load["configuration"]=="wye"] + load_neutrals = [(dss_obj["bus"],dss_obj["connections"][end]) for (_,dss_obj) in data_eng["load"] if dss_obj["configuration"]=="wye"] neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) stack = deepcopy(neutrals) @@ -1207,18 +1067,14 @@ end "Parses a DSS file into a PowerModels usable format" -function parse_opendss_dm(io::IOStream; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict - dss_data = parse_dss(io) +function parse_opendss_dm(io::IOStream; import_all::Bool=false, bank_transformers::Bool=true)::Dict + data_dss = parse_dss(io) - return parse_opendss_dm(dss_data; import_all=import_all) + return parse_opendss_dm(data_dss; import_all=import_all) end -""" - _get_conductors_ordered(busname; neutral=true) - -Returns an ordered list of defined conductors. If ground=false, will omit any `0` -""" +"Returns an ordered list of defined conductors. If ground=false, will omit any `0`" function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_length=true)::Array parts = split(busname, '.'; limit=2) ret = [] @@ -1230,7 +1086,13 @@ function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_l end if check_length && length(default)!=length(ret) - Memento.error("An incorrect number of nodes was specified; |$(parts[2])|!=$(length(default)).") + Memento.error("An incorrect number of nodes was specified on $(parts[1]); |$(parts[2])|!=$(length(default)).") end return ret end + + +"" +function _import_all!(component::Dict{String,<:Any}, defaults::Dict{String,<:Any}, prop_order::Array{<:AbstractString,1}) + component["dss"] = Dict{String,Any}((key, defaults[key]) for key in prop_order) +end From 1b4e002b7935c49e449fea4cf76da31e884cd7e4 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 07:52:18 -0700 Subject: [PATCH 030/224] REF: "model" -> "data_model" ADD: busoords parsing feature --- src/core/data_model_mapping.jl | 2 +- src/io/common_dm.jl | 10 +++++----- src/io/opendss_dm.jl | 24 ++++++++++++++++++------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index 672b0bc9a..d750346a4 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -22,7 +22,7 @@ function data_model_map!(data_model) end end - data_model["model"] = "mathematical" + data_model["data_model"] = "mathematical" return data_model end diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl index e0d42c0ef..0dd988e2e 100644 --- a/src/io/common_dm.jl +++ b/src/io/common_dm.jl @@ -22,19 +22,19 @@ end "" -function parse_file_dm(file::String; model::String="engineering", kwargs...) +function parse_file_dm(file::String; data_model::String="engineering", kwargs...) pmd_data = open(file) do io parse_file_dm(io; filetype=split(lowercase(file), '.')[end], kwargs...) end - if model == "mathematical" + if data_model == "mathematical" # to get the old indexing pmd_data_old = open(file) do io parse_file(io; filetype=split(lowercase(file), '.')[end], kwargs...) end index_presets = Dict(comp_type=>Dict(comp["name"]=>comp["index"] for (id, comp) in pmd_data_old[comp_type]) for comp_type in ["bus"]) - transform_model!(pmd_data; index_presets=index_presets) + transform_data_model!(pmd_data; index_presets=index_presets) end return pmd_data @@ -42,8 +42,8 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model!(data::Dict{String,Any}; index_presets::Dict{String,<:Any}=Dict{String,Any}()) - if get(data, "model", "mathematical") == "engineering" +function transform_data_model!(data::Dict{String,<:Any}; index_presets::Dict{String,<:Any}=Dict{String,Any}()) + if get(data, "data_model", "mathematical") == "engineering" data_model_map!(data) data_model_make_pu!(data) data_model_index!(data, index_presets=index_presets) diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 6ce7f572c..f47b7bccd 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -49,13 +49,24 @@ function _discover_buses(data_dss::Dict{String,<:Any})::Array end +"Parses buscoords [lon,lat] (if present) into their respective buses" +function _dss2eng_buscoords!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}) + for (name, coords) in get(data_dss, "buscoords", Dict{String,Any}()) + if haskey(data_eng["bus"], name) + bus = data_eng["bus"][name] + bus["lon"] = coords["x"] + bus["lat"] = coords["y"] + end + end +end + + """ _dss2pmd_bus!(data_eng, data_dss) Adds PowerModels-style buses to `data_eng` from `data_dss`. """ function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) - buses = _discover_buses(data_dss) for (n, (bus, nodes)) in enumerate(buses) @@ -63,7 +74,6 @@ function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any add_bus!(data_eng, id=bus, status=1, bus_type=1) end - end @@ -922,12 +932,13 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, data_eng["name"] = circuit["name"] data_eng["sourcebus"] = defaults["bus1"] - data_eng["model"] = "engineering" + data_eng["data_model"] = "engineering" - data_eng["settings"]["v_var_scalar"] = 1e3 - data_eng["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))*1e3/data_eng["settings"]["v_var_scalar"] + # TODO rename fields + data_eng["settings"]["kv_kvar_scalar"] = 1 + data_eng["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))/data_eng["settings"]["v_var_scalar"] data_eng["settings"]["set_vbase_bus"] = data_eng["sourcebus"] - data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1e6/data_eng["settings"]["v_var_scalar"] + data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1e3/data_eng["settings"]["v_var_scalar"] data_eng["settings"]["basefreq"] = get(get(data_dss, "options", Dict()), "defaultbasefreq", 60.0) data_eng["files"] = data_dss["filename"] @@ -936,6 +947,7 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, end _dss2eng_bus!(data_eng, data_dss, import_all) + _dss2eng_buscoords!(data_eng, data_dss) _dss2eng_linecode!(data_eng, data_dss, import_all) _dss2eng_line!(data_eng, data_dss, import_all) From 2acf0744e69ebbbc7fd48b21c347a2bae3ebfb25 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 08:41:18 -0700 Subject: [PATCH 031/224] RM: old opendss.jl --- src/PowerModelsDistribution.jl | 5 +- src/io/opendss.jl | 1823 -------------------------------- 2 files changed, 2 insertions(+), 1826 deletions(-) delete mode 100644 src/io/opendss.jl diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 2b1af1fe1..37fd8ae8f 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -46,10 +46,9 @@ module PowerModelsDistribution include("io/json.jl") include("io/dss_parse.jl") include("io/dss_structs.jl") - include("io/opendss.jl") - #include("io/common_dm.jl") - #include("io/opendss_dm.jl") + include("io/common_dm.jl") + include("io/opendss_dm.jl") include("io/data_model_components.jl") include("core/data_model_mapping.jl") include("core/data_model_pu.jl") diff --git a/src/io/opendss.jl b/src/io/opendss.jl deleted file mode 100644 index 3927d3915..000000000 --- a/src/io/opendss.jl +++ /dev/null @@ -1,1823 +0,0 @@ -# OpenDSS parser -import LinearAlgebra: isdiag, diag, pinv - - -"Structure representing OpenDSS `dss_source_id` giving the type of the component `dss_type`, its name `dss_name`, and the active phases `active_phases`" -struct DSSSourceId - dss_type::AbstractString - dss_name::AbstractString - active_phases::Set{Int} -end - - -"Parses a component's OpenDSS source information into the `dss_source_id` struct" -function _parse_dss_source_id(component::Dict)::DSSSourceId - dss_type, dss_name = split(component["source_id"], '.') - return DSSSourceId(dss_type, dss_name, Set(component["active_phases"])) -end - - -"returns the linecode with name `id`" -function _get_linecode(dss_data::Dict, id::AbstractString) - if haskey(dss_data, "linecode") - for item in dss_data["linecode"] - if item["name"] == id - return item - end - end - end - return Dict{String,Any}() -end - - -""" - _discover_buses(dss_data) - -Discovers all of the buses (not separately defined in OpenDSS), from "lines". -""" -function _discover_buses(dss_data::Dict)::Array - bus_names = [] - buses = [] - for compType in ["line", "transformer", "reactor"] - if haskey(dss_data, compType) - compList = dss_data[compType] - for compObj in compList - if compType == "transformer" - compObj = _create_transformer(compObj["name"]; _to_sym_keys(compObj)...) - for bus in compObj["buses"] - name, nodes = _parse_busname(bus) - if !(name in bus_names) - push!(bus_names, name) - push!(buses, (name, nodes)) - end - end - elseif haskey(compObj, "bus2") - for key in ["bus1", "bus2"] - name, nodes = _parse_busname(compObj[key]) - if !(name in bus_names) - push!(bus_names, name) - push!(buses, (name, nodes)) - end - end - end - end - end - end - if length(buses) == 0 - Memento.error(_LOGGER, "dss_data has no branches!") - else - return buses - end -end - - -""" - _dss2pmd_bus!(pmd_data, dss_data) - -Adds PowerModels-style buses to `pmd_data` from `dss_data`. -""" -function _dss2pmd_bus!(pmd_data::Dict, dss_data::Dict, import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1) - if !haskey(pmd_data, "bus") - pmd_data["bus"] = [] - end - - nconductors = pmd_data["conductors"] - buses = _discover_buses(dss_data) - for (n, (bus, nodes)) in enumerate(buses) - busDict = Dict{String,Any}() - - busDict["bus_i"] = n - busDict["index"] = n - busDict["name"] = bus - - busDict["bus_type"] = 1 - - busDict["vm"] = _parse_array(1.0, nodes, nconductors) - busDict["va"] = _parse_array([_wrap_to_180(-rad2deg(2*pi/nconductors*(i-1))) for i in 1:nconductors], nodes, nconductors) - - busDict["vmin"] = _parse_array(vmin, nodes, nconductors, vmin) - busDict["vmax"] = _parse_array(vmax, nodes, nconductors, vmax) - - busDict["base_kv"] = pmd_data["basekv"] - - push!(pmd_data["bus"], busDict) - end - - # create virtual sourcebus - circuit = _create_vsource(get(dss_data["circuit"][1], "bus1", "sourcebus"), dss_data["circuit"][1]["name"]; _to_sym_keys(dss_data["circuit"][1])...) - - busDict = Dict{String,Any}() - - nodes = Array{Bool}([1 1 1 0]) - ph1_ang = circuit["angle"] - vm = circuit["pu"] - - busDict["bus_i"] = length(pmd_data["bus"])+1 - busDict["index"] = length(pmd_data["bus"])+1 - busDict["name"] = "virtual_sourcebus" - - busDict["bus_type"] = 3 - - busDict["vm"] = _parse_array(vm, nodes, nconductors) - busDict["va"] = _parse_array([_wrap_to_180(-rad2deg(2*pi/nconductors*(i-1))+ph1_ang) for i in 1:nconductors], nodes, nconductors) - - busDict["vmin"] = _parse_array(vm, nodes, nconductors, vm) - busDict["vmax"] = _parse_array(vm, nodes, nconductors, vm) - - busDict["base_kv"] = pmd_data["basekv"] - - push!(pmd_data["bus"], busDict) -end - - -""" - find_component(pmd_data, name, compType) - -Returns the component of `compType` with `name` from `data` of type -Dict{String,Array}. -""" -function find_component(data::Dict, name::AbstractString, compType::AbstractString)::Dict - for comp in values(data[compType]) - if comp["name"] == name - return comp - end - end - Memento.warn(_LOGGER, "Could not find $compType \"$name\"") - return Dict{String,Any}() -end - - -""" - find_bus(busname, pmd_data) - -Finds the index number of the bus in existing data from the given `busname`. -""" -function find_bus(busname::AbstractString, pmd_data::Dict) - bus = find_component(pmd_data, busname, "bus") - if haskey(bus, "bus_i") - return bus["bus_i"] - else - Memento.error(_LOGGER, "cannot find connected bus with id \"$busname\"") - end -end - - -""" - _dss2pmd_load!(pmd_data, dss_data, import_all) - -Adds PowerModels-style loads to `pmd_data` from `dss_data`. -""" -function _dss2pmd_load!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "load") - pmd_data["load"] = [] - end - - for load in get(dss_data, "load", []) - _apply_like!(load, dss_data, "load") - defaults = _apply_ordered_properties(_create_load(load["bus1"], load["name"]; _to_sym_keys(load)...), load) - - loadDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - kv = defaults["kv"] - - expected_kv = pmd_data["basekv"] / sqrt(pmd_data["conductors"]) - if !isapprox(kv, expected_kv; atol=expected_kv * 0.01) - Memento.warn(_LOGGER, "Load has kv=$kv, not the expected kv=$(expected_kv). Results may not match OpenDSS") - end - - loadDict["name"] = defaults["name"] - loadDict["load_bus"] = find_bus(name, pmd_data) - - load_name = defaults["name"] - - cnds = [1, 2, 3, 0][nodes[1,:]] - loadDict["conn"] = defaults["conn"] - nph = defaults["phases"] - delta_map = Dict([1,2]=>1, [2,3]=>2, [1,3]=>3) - if nph==1 - # PMD convention is to specify the voltage across the load - # this what OpenDSS does for 1-phase loads only - loadDict["vnom_kv"] = kv - # default is to connect betwheen L1 and N - cnds = (isempty(cnds)) ? [1, 0] : cnds - # if only one connection specified, implicitly connected to N - # bus1=x.c == bus1=x.c.0 - if length(cnds)==1 - cnds = [cnds..., 0] - end - # if more than two, only first two are considered - if length(cnds)>2 - # this no longer works if order is not preserved - # throw an error instead of behaving like OpenDSS - # cnds = cnds[1:2] - Memento.error(_LOGGER, "A 1-phase load cannot specify more than two terminals.") - end - # conn property has no effect - # delta/wye is determined by whether load connected to ground - # or between two phases - if cnds==[0, 0] - pqd_premul = zeros(3) - elseif cnds[2]==0 || cnds[1]==0 - # this is a wye load in the PMD sense - loadDict["conn"] = "wye" - ph = (cnds[2]==0) ? cnds[1] : cnds[2] - pqd_premul = zeros(3) - pqd_premul[ph] = 1 - else - # this is a delta load in the PMD sense - loadDict["conn"] = "delta" - pqd_premul = zeros(3) - pqd_premul[delta_map[cnds]] = 1 - end - elseif nph==2 - # there are some extremely weird edge cases for this - # the user can enter weird stuff and OpenDSS will still show some result - # for example, take - # nphases=3 bus1=x.1.2.0 - # this looks like a combination of a single-phase PMD delta and wye load - # so throw an error and ask to reformulate as single and three phase loads - Memento.error(_LOGGER, "Two-phase loads (nphases=2) are not supported, as these lead to unexpected behaviour. Reformulate this load as a combination of single-phase loads.") - elseif nph==3 - # for 2 and 3 phase windings, kv is always in LL, also for wye - # whilst PMD model uses actual voltage across load; so LN for wye - if loadDict["conn"]=="wye" - loadDict["vnom_kv"] = kv/sqrt(3) - else - loadDict["vnom_kv"] = kv - end - if cnds==[] - pqd_premul = [1/3, 1/3, 1/3] - else - if (length(cnds)==3 || length(cnds)==4) && cnds==unique(cnds) - #variations of [1, 2, 3] and [1, 2, 3, 0] - pqd_premul = [1/3, 1/3, 1/3] - else - Memento.error(_LOGGER, "Specified connections for three-phase load $name not allowed.") - end - end - else - Memento.error(_LOGGER, "For a load, nphases should be in [1,3].") - end - loadDict["pd"] = pqd_premul.*defaults["kw"]./1e3 - loadDict["qd"] = pqd_premul.*defaults["kvar"]./1e3 - - # parse the model - model = defaults["model"] - # some info on OpenDSS load models - ################################## - # Constant can still be scaled by other settings, fixed cannot - # Note that in the current feature set, fixed therefore equals constant - # 1: Constant P and Q, default - if model == 2 - # 2: Constant Z - elseif model == 3 - # 3: Constant P and quadratic Q - Memento.warn(_LOGGER, "$load_name: load model 3 not supported. Treating as model 1.") - model = 1 - elseif model == 4 - # 4: Exponential - Memento.warn(_LOGGER, "$load_name: load model 4 not supported. Treating as model 1.") - model = 1 - elseif model == 5 - # 5: Constant I - #warn(_LOGGER, "$name: load model 5 not supported. Treating as model 1.") - #model = 1 - elseif model == 6 - # 6: Constant P and fixed Q - Memento.warn(_LOGGER, "$load_name: load model 6 identical to model 1 in current feature set. Treating as model 1.") - model = 1 - elseif model == 7 - # 7: Constant P and quadratic Q (i.e., fixed reactance) - Memento.warn(_LOGGER, "$load_name: load model 7 not supported. Treating as model 1.") - model = 1 - elseif model == 8 - # 8: ZIP - Memento.warn(_LOGGER, "$load_name: load model 8 not supported. Treating as model 1.") - model = 1 - end - # save adjusted model type to dict, human-readable - model_int2str = Dict(1=>"constant_power", 2=>"constant_impedance", 5=>"constant_current") - loadDict["model"] = model_int2str[model] - - loadDict["status"] = convert(Int, defaults["enabled"]) - - loadDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - loadDict["source_id"] = "load.$load_name" - - loadDict["index"] = length(pmd_data["load"]) + 1 - - used = ["phases", "bus1", "name"] - _PMs._import_remaining!(loadDict, defaults, import_all; exclude=used) - - push!(pmd_data["load"], loadDict) - end -end - - -""" - _dss2pmd_shunt!(pmd_data, dss_data, import_all) - -Adds PowerModels-style shunts to `pmd_data` from `dss_data`. -""" -function _dss2pmd_shunt!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "shunt") - pmd_data["shunt"] = [] - end - - for shunt in get(dss_data, "capacitor", []) - _apply_like!(shunt, dss_data, "capacitor") - defaults = _apply_ordered_properties(_create_capacitor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) - - shuntDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - vnom_ln = defaults["kv"] - # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) - if defaults["phases"] > 1 - vnom_ln = vnom_ln/sqrt(3) - end - # 'kvar' is specified for all phases at once; we want the per-phase one, in MVar - qnom = (defaults["kvar"]/1E3)/defaults["phases"] - # indexing qnom[1] is a dirty fix to support both kvar=[x] and kvar=x - # TODO fix this in a clear way, in dss_structs.jl - b_cap = qnom[1]/vnom_ln^2 - # get the base addmittance, with a LN voltage base - Sbase = 1 # not yet pmd_data["baseMVA"] because this is done in _PMs.make_per_unit - Ybase_ln = Sbase/(pmd_data["basekv"]/sqrt(3))^2 - # now convent b_cap to per unit - b_cap_pu = b_cap/Ybase_ln - - b = fill(b_cap_pu, defaults["phases"]) - N = length(b) - if defaults["conn"]=="wye" - B = LinearAlgebra.diagm(0=>b) - else # shunt["conn"]=="delta" - # create delta transformation matrix Md - Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) - Md[N,1] = -1 - B = Md'*LinearAlgebra.diagm(0=>b)*Md - end - - active_phases = [n for n in 1:nconductors if nodes[n] > 0] - - if length(active_phases) < 3 - Bf = zeros(3, 3) - Bf[active_phases, active_phases] = B - B = Bf - end - - shuntDict["shunt_bus"] = find_bus(name, pmd_data) - shuntDict["name"] = defaults["name"] - shuntDict["gs"] = fill(0.0, 3, 3) - shuntDict["bs"] = B - shuntDict["status"] = convert(Int, defaults["enabled"]) - shuntDict["index"] = length(pmd_data["shunt"]) + 1 - - shuntDict["active_phases"] = active_phases - shuntDict["source_id"] = "capacitor.$(defaults["name"])" - - used = ["bus1", "phases", "name"] - _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) - - push!(pmd_data["shunt"], shuntDict) - end - - - for shunt in get(dss_data, "reactor", []) - if !haskey(shunt, "bus2") - _apply_like!(shunt, dss_data, "reactor") - defaults = _apply_ordered_properties(_create_reactor(shunt["bus1"], shunt["name"]; _to_sym_keys(shunt)...), shunt) - - shuntDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - Zbase = (pmd_data["basekv"] / sqrt(3.0))^2 * nconductors / pmd_data["baseMVA"] # Use single-phase base impedance for each phase - Gcap = Zbase * sum(defaults["kvar"]) / (nconductors * 1e3 * (pmd_data["basekv"] / sqrt(3.0))^2) - - shuntDict["shunt_bus"] = find_bus(name, pmd_data) - shuntDict["name"] = defaults["name"] - shuntDict["gs"] = LinearAlgebra.diagm(0=>_parse_array(0.0, nodes, nconductors)) # TODO: - shuntDict["bs"] = LinearAlgebra.diagm(0=>_parse_array(Gcap, nodes, nconductors)) - shuntDict["status"] = convert(Int, defaults["enabled"]) - shuntDict["index"] = length(pmd_data["shunt"]) + 1 - - shuntDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - shuntDict["source_id"] = "reactor.$(defaults["name"])" - - used = ["bus1", "phases", "name"] - _PMs._import_remaining!(shuntDict, defaults, import_all; exclude=used) - - push!(pmd_data["shunt"], shuntDict) - end - end -end - - -""" - _dss2pmd_gen!(pmd_data, dss_data, import_all) - -Adds PowerModels-style generators to `pmd_data` from `dss_data`. -""" -function _dss2pmd_gen!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "gen") - pmd_data["gen"] = [] - end - - # sourcebus generator (created by circuit) - circuit = dss_data["circuit"][1] - defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), "sourcegen"; _to_sym_keys(circuit)...) - - genDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - genDict["gen_bus"] = find_bus("virtual_sourcebus", pmd_data) - genDict["name"] = defaults["name"] - genDict["gen_status"] = convert(Int, defaults["enabled"]) - - # TODO: populate with VSOURCE properties - genDict["pg"] = _parse_array( 0.0, nodes, nconductors) - genDict["qg"] = _parse_array( 0.0, nodes, nconductors) - - genDict["qmin"] = _parse_array(-NaN, nodes, nconductors) - genDict["qmax"] = _parse_array( NaN, nodes, nconductors) - - genDict["pmin"] = _parse_array(-NaN, nodes, nconductors) - genDict["pmax"] = _parse_array( NaN, nodes, nconductors) - - genDict["model"] = 2 - genDict["startup"] = 0.0 - genDict["shutdown"] = 0.0 - genDict["ncost"] = 3 - genDict["cost"] = [0.0, 1.0, 0.0] - - genDict["index"] = length(pmd_data["gen"]) + 1 - - genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - genDict["source_id"] = "vsource.$(defaults["name"])" - - genDict["conn"] = "wye" - - used = ["name", "phases", "bus1"] - _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) - - push!(pmd_data["gen"], genDict) - - - for gen in get(dss_data, "generator", []) - _apply_like!(gen, dss_data, "generator") - defaults = _apply_ordered_properties(_create_generator(gen["bus1"], gen["name"]; _to_sym_keys(gen)...), gen) - - genDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - genDict["gen_bus"] = find_bus(name, pmd_data) - genDict["name"] = defaults["name"] - genDict["gen_status"] = convert(Int, defaults["enabled"]) - genDict["pg"] = _parse_array(defaults["kw"] / (1e3 * nconductors), nodes, nconductors) - genDict["qg"] = _parse_array(defaults["kvar"] / (1e3 * nconductors), nodes, nconductors) - genDict["vg"] = _parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors) - - genDict["qmin"] = _parse_array(defaults["minkvar"] / (1e3 * nconductors), nodes, nconductors) - genDict["qmax"] = _parse_array(defaults["maxkvar"] / (1e3 * nconductors), nodes, nconductors) - - genDict["apf"] = _parse_array(0.0, nodes, nconductors) - - genDict["pmax"] = genDict["pg"] # Assumes generator is at rated power - genDict["pmin"] = 0.0 * genDict["pg"] # 0% of pmax - - genDict["pc1"] = genDict["pmax"] - genDict["pc2"] = genDict["pmin"] - genDict["qc1min"] = genDict["qmin"] - genDict["qc1max"] = genDict["qmax"] - genDict["qc2min"] = genDict["qmin"] - genDict["qc2max"] = genDict["qmax"] - - # For distributed generation ramp rates are not usually an issue - # and they are not supported in OpenDSS - genDict["ramp_agc"] = genDict["pmax"] - - genDict["ramp_q"] = _parse_array(max.(abs.(genDict["qmin"]), abs.(genDict["qmax"])), nodes, nconductors) - genDict["ramp_10"] = genDict["pmax"] - genDict["ramp_30"] = genDict["pmax"] - - genDict["control_model"] = defaults["model"] - - # if PV generator mode convert attached bus to PV bus - if genDict["control_model"] == 3 - pmd_data["bus"][genDict["gen_bus"]]["bus_type"] = 2 - end - - genDict["model"] = 2 - genDict["startup"] = 0.0 - genDict["shutdown"] = 0.0 - genDict["ncost"] = 3 - genDict["cost"] = [0.0, 1.0, 0.0] - - genDict["index"] = length(pmd_data["gen"]) + 1 - - genDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - genDict["source_id"] = "generator.$(defaults["name"])" - - genDict["conn"] = "wye" - - used = ["name", "phases", "bus1"] - _PMs._import_remaining!(genDict, defaults, import_all; exclude=used) - - push!(pmd_data["gen"], genDict) - end - - for pv in get(dss_data, "pvsystem", []) - Memento.warn(_LOGGER, "Converting PVSystem \"$(pv["name"])\" into generator with limits determined by OpenDSS property 'kVA'") - - _apply_like!(pv, dss_data, "pvsystem") - defaults = _apply_ordered_properties(_create_pvsystem(pv["bus1"], pv["name"]; _to_sym_keys(pv)...), pv) - - pvDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - pvDict["name"] = defaults["name"] - pvDict["gen_bus"] = find_bus(name, pmd_data) - - pvDict["pg"] = _parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors) - pvDict["qg"] = _parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors) - pvDict["vg"] = _parse_array(defaults["kv"] / pmd_data["basekv"], nodes, nconductors) - - pvDict["pmin"] = _parse_array(0.0, nodes, nconductors) - pvDict["pmax"] = _parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors) - - pvDict["qmin"] = -_parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors) - pvDict["qmax"] = _parse_array(defaults["kva"] / (1e3 * nconductors), nodes, nconductors) - - pvDict["gen_status"] = convert(Int, defaults["enabled"]) - - pvDict["model"] = 2 - pvDict["startup"] = 0.0 - pvDict["shutdown"] = 0.0 - pvDict["ncost"] = 3 - pvDict["cost"] = [0.0, 1.0, 0.0] - - pvDict["index"] = length(pmd_data["gen"]) + 1 - - pvDict["active_phases"] = [nodes[n] > 0 ? 1 : 0 for n in 1:nconductors] - pvDict["source_id"] = "pvsystem.$(defaults["name"])" - - pvDict["conn"] = "wye" - - used = ["name", "phases", "bus1"] - _PMs._import_remaining!(pvDict, defaults, import_all; exclude=used) - - push!(pmd_data["gen"], pvDict) - end -end - - -""" - _dss2pmd_branch!(pmd_data, dss_data, import_all) - -Adds PowerModels-style branches to `pmd_data` from `dss_data`. -""" -function _dss2pmd_branch!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "branch") - pmd_data["branch"] = [] - end - - nconductors = pmd_data["conductors"] - - for line in get(dss_data, "line", []) - _apply_like!(line, dss_data, "line") - - if haskey(line, "linecode") - linecode = deepcopy(_get_linecode(dss_data, get(line, "linecode", ""))) - if haskey(linecode, "like") - linecode = merge(find_component(dss_data, linecode["like"], "linecode"), linecode) - end - - linecode["units"] = get(line, "units", "none") == "none" ? "none" : get(linecode, "units", "none") - linecode["circuit_basefreq"] = pmd_data["basefreq"] - - linecode = _create_linecode(get(linecode, "name", ""); _to_sym_keys(linecode)...) - delete!(linecode, "name") - else - linecode = Dict{String,Any}() - end - - if haskey(line, "basefreq") && line["basefreq"] != pmd_data["basefreq"] - Memento.warn(_LOGGER, "basefreq=$(line["basefreq"]) on line $(line["name"]) does not match circuit basefreq=$(pmd_data["basefreq"])") - line["circuit_basefreq"] = pmd_data["basefreq"] - end - - defaults = _apply_ordered_properties(_create_line(line["bus1"], line["bus2"], line["name"]; _to_sym_keys(line)...), line; code_dict=linecode) - - bf, nodes = _parse_busname(defaults["bus1"]) - - phase_order_fr = _get_conductors_ordered(defaults["bus1"]; neutral=false, nconductors=nconductors) - phase_order_to = _get_conductors_ordered(defaults["bus2"]; neutral=false, nconductors=nconductors) - - @assert phase_order_fr == phase_order_to "Order of connections must be identical on either end of a line" - - bt = _parse_busname(defaults["bus2"])[1] - - branchDict = Dict{String,Any}() - - branchDict["name"] = defaults["name"] - - branchDict["f_bus"] = find_bus(bf, pmd_data) - branchDict["t_bus"] = find_bus(bt, pmd_data) - - branchDict["length"] = defaults["length"] - - rmatrix = _reorder_matrix(_parse_matrix(defaults["rmatrix"], nodes, nconductors), phase_order_fr) - xmatrix = _reorder_matrix(_parse_matrix(defaults["xmatrix"], nodes, nconductors), phase_order_fr) - cmatrix = _reorder_matrix(_parse_matrix(defaults["cmatrix"], nodes, nconductors), phase_order_fr) - - Zbase = (pmd_data["basekv"] / sqrt(3))^2 * nconductors / (pmd_data["baseMVA"]) - - Zbase = Zbase/3 - # The factor 3 here is needed to convert from a voltage base - # in line-to-line (LL) to a voltage base in line-to-neutral (LN). - # V_LL = √3*V_LN - # Zbase_new = Zbase_old*(Vbase_new/Vbase_old)^2 = Zbase_old*(1/√3)^2 - # In the parser, LL voltage base is used for per unit conversion. - # However, in the mathematical model, the voltage magnitude per phase - # is fixed at 1. So implicitly, we later on state that the voltage base - # is actually in LN. We compensate here for that. - - branchDict["br_r"] = rmatrix * defaults["length"] / Zbase - branchDict["br_x"] = xmatrix * defaults["length"] / Zbase - - branchDict["g_fr"] = LinearAlgebra.diagm(0=>_parse_array(0.0, nodes, nconductors)) - branchDict["g_to"] = LinearAlgebra.diagm(0=>_parse_array(0.0, nodes, nconductors)) - - branchDict["b_fr"] = Zbase * (2.0 * pi * pmd_data["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 - branchDict["b_to"] = Zbase * (2.0 * pi * pmd_data["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 - - branchDict["c_rating_a"] = _parse_array(defaults["normamps"], nodes, nconductors) - branchDict["c_rating_b"] = _parse_array(defaults["emergamps"], nodes, nconductors) - branchDict["c_rating_c"] = _parse_array(defaults["emergamps"], nodes, nconductors) - - branchDict["tap"] = _parse_array(1.0, nodes, nconductors, 1.0) - branchDict["shift"] = _parse_array(0.0, nodes, nconductors) - - branchDict["br_status"] = convert(Int, defaults["enabled"]) - - branchDict["angmin"] = _parse_array(-60.0, nodes, nconductors, -60.0) - branchDict["angmax"] = _parse_array( 60.0, nodes, nconductors, 60.0) - - branchDict["transformer"] = false - branchDict["switch"] = defaults["switch"] - - branchDict["index"] = length(pmd_data["branch"]) + 1 - - nodes = .+([_parse_busname(defaults[n])[2] for n in ["bus1", "bus2"]]...) - branchDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - branchDict["source_id"] = "line.$(defaults["name"])" - - used = ["name", "bus1", "bus2", "rmatrix", "xmatrix"] - _PMs._import_remaining!(branchDict, defaults, import_all; exclude=used) - - push!(pmd_data["branch"], branchDict) - end -end - - -""" - _dss2pmd_transformer!(pmd_data, dss_data, import_all) - -Adds ThreePhasePowerModels-style transformers to `pmd_data` from `dss_data`. -""" -function _dss2pmd_transformer!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "transformer_comp") - pmd_data["transformer_comp"] = Array{Any,1}() - end - - for transformer in get(dss_data, "transformer", []) - _apply_like!(transformer, dss_data, "transformer") - defaults = _apply_ordered_properties(_create_transformer(transformer["name"]; _to_sym_keys(transformer)...), transformer) - - nconductors = pmd_data["conductors"] - nrw = defaults["windings"] - prop_suffix_w = ["", ["_$w" for w in 2:nrw]...] - if nrw>3 - # All of the code is compatible with any number of windings, - # except for the parsing of the loss model (the pair-wise reactance) - Memento.error(_LOGGER, "For now parsing of xscarray is not supported. At most 3 windings are allowed, not $nrw.") - end - - transDict = Dict{String,Any}() - transDict["name"] = defaults["name"] - transDict["source_id"] = "transformer.$(defaults["name"])" - transDict["buses"] = Array{Int, 1}(undef, nrw) - if !isempty(defaults["bank"]) - transDict["bank"] = defaults["bank"] - end - for i in 1:nrw - bnstr = defaults["buses"][i] - bus, nodes = _parse_busname(bnstr) - active_phases = [n for n in 1:nconductors if nodes[n] > 0] - nodes_123 = [true true true] - if !all(nodes[1:3]) && isempty(defaults["bank"]) - Memento.warn(_LOGGER, "Only three-phase transformers are supported. The bus specification $bnstr is treated as $bus instead.") - elseif !isempty(defaults["bank"]) - if haskey(transDict, "active_phases") - if transDict["active_phases"] != active_phases - Memento.error(_LOGGER, "Mismatched phase connections on transformer windings not supported when banking transformers") - end - else - transDict["active_phases"] = active_phases - end - elseif all(nodes[1:3]) - transDict["active_phases"] = [1, 2, 3] - else - transDict["active_phases"] = [1, 2, 3] - Memento.warn(_LOGGER, "Only three-phase transformers are supported. The bus specification $bnstr is treated as $bus instead.") - end - transDict["buses"][i] = find_bus(bus, pmd_data) - end - - # voltage and power ratings - #transDict["vnom_kv"] = defaults["kvs"] - #transDict["snom_kva"] = defaults["kvas"] - transDict["rate_a"] = [ones(nconductors)*defaults["normhkva"] for i in 1:nrw] - transDict["rate_b"] = [ones(nconductors)*defaults["normhkva"] for i in 1:nrw] - transDict["rate_c"] = [ones(nconductors)*defaults["emerghkva"] for i in 1:nrw] - # convert to 1 MVA base - transDict["rate_a"] *= 1E-3 - transDict["rate_b"] *= 1E-3 - transDict["rate_c"] *= 1E-3 - # connection properties - dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") - dyz_primary = dyz_map[defaults["conns"][1]] - transDict["conns"] = Array{String,1}(undef, nrw) - - transDict["config"] = Dict{Int,Any}() - transDict["config"][1] = Dict( - "type"=>dyz_primary, - "polarity"=>1, - "cnd"=>[1, 2, 3], - "grounded"=>true, - "vm_nom"=>defaults["kvs"][1] - ) - #transDict["conns"][1] = string("123+", dyz_primary) - for w in 2:nrw - type = dyz_map[defaults["conns"][w]] - if dyz_primary==type - cnd = [1,2,3] - polarity = 1 - else - if defaults["leadlag"] in ["ansi", "lag"] - #Yd1 => (123+y,123+d) - #Dy1 => (123+d,231-y) - #pp_w = (type=="delta") ? "123+" : "231-" - cnd = (type=="delta") ? [1, 2, 3] : [2, 3, 1] - polarity = (type=="delta") ? 1 : -1 - else # hence defaults["leadlag"] in ["euro", "lead"] - #Yd11 => (123+y,312-d) - #Dy11 => (123+d,123+y) - #pp_w = (type=="delta") ? "312-" : "123+" - cnd = (type=="delta") ? [3, 1, 2] : [1, 2, 3] - polarity = (type=="delta") ? -1 : 1 - end - end - transDict["config"][w] = Dict( - "type"=>type, - "polarity"=>polarity, - "cnd"=>cnd, - "vm_nom"=>defaults["kvs"][w] - ) - if type=="wye" - transDict["config"][w]["grounded"] = true - end - end - - # tap properties - transDict["tm"] = [ones(Float64,3)*defaults["taps"][i] for i in 1:nrw] - transDict["tm_min"] = [ones(Float64,3)*defaults["mintap"] for i in 1:nrw] - transDict["tm_max"] = [ones(Float64,3)*defaults["maxtap"] for i in 1:nrw] - transDict["tm_step"] = [ones(Int,3)*defaults["numtaps"] for i in 1:nrw] - transDict["fixed"] = [ones(Bool,3) for i in 1:nrw] - - # loss model (converted to SI units, referred to secondary) - function zpn_to_abc(z, p, n; atol=1E-13) - a = exp(im*2*pi/3) - C = 1/sqrt(3)*[1 1 1; 1 a a^2; 1 a^2 a] - res = inv(C)*[z 0 0; 0 p 0; 0 0 n]*C - res = (abs.(res).>atol).*res - return res - end - pos_to_abc(p) = zpn_to_abc(p, p, p) - zbase = 1^2/(defaults["kvas"][1]/1E3) - transDict["rs"] = Array{Matrix{Float64}, 1}(undef, nrw) - transDict["gsh"] = Array{Matrix{Float64}, 1}(undef, nrw) - transDict["bsh"] = Array{Matrix{Float64}, 1}(undef, nrw) - for w in 1:nrw - zs_w_p = defaults["%rs"][w]/100*zbase - Zs_w = pos_to_abc(zs_w_p) - - if haskey(transformer, "rneut") || haskey(transformer, "xneut") - #TODO handle neutral impedance - # neutral impedance is ignored for now; all transformers are - # grounded (that is, those with a wye and zig-zag winding). - Memento.warn(_LOGGER, "The neutral impedance, (rneut and xneut properties), is ignored; the neutral (for wye and zig-zag windings) is connected directly to the ground.") - end - - transDict["rs"][w] = real.(Zs_w) - # shunt elements are added at second winding - if w==2 - ysh_w_p = (defaults["%noloadloss"]-im*defaults["%imag"])/100/zbase - Ysh_w = pos_to_abc(ysh_w_p) - transDict["gsh"][w] = real.(Ysh_w) - transDict["bsh"][w] = imag.(Ysh_w) - else - transDict["gsh"][w] = zeros(Float64, 3, 3) - transDict["bsh"][w] = zeros(Float64, 3, 3) - end - end - transDict["xs"] = Dict{String, Matrix{Float64}}() - - Zsc = Dict{Tuple{Int,Int}, Complex}() - if nrw==2 - xs_map = Dict("xhl"=>(1,2)) - elseif nrw==3 - xs_map = Dict("xhl"=>(1,2), "xht"=>(1,3), "xlt"=>(2,3)) - end - for (k,v) in xs_map - Zsc[(v)] = im*defaults[k]/100*zbase - end - Zbr = _sc2br_impedance(Zsc) - for (k,zs_ij_p) in Zbr - Zs_ij = pos_to_abc(zs_ij_p) - transDict["xs"]["$(k[1])-$(k[2])"] = imag.(Zs_ij) - end - - push!(pmd_data["transformer_comp"], transDict) - end -end - - -""" -Converts a set of short-circuit tests to an equivalent reactance network. -Reference: -R. C. Dugan, “A perspective on transformer modeling for distribution system analysis,” -in 2003 IEEE Power Engineering Society General Meeting (IEEE Cat. No.03CH37491), 2003, vol. 1, pp. 114-119 Vol. 1. -""" -function _sc2br_impedance(Zsc) - N = maximum([maximum(k) for k in keys(Zsc)]) - # check whether no keys are missing - # Zsc should contain tupples for upper triangle of NxN - for i in 1:N - for j in i+1:N - if !haskey(Zsc, (i,j)) - if haskey(Zsc, (j,i)) - # Zsc is symmetric; use value of lower triangle if defined - Zsc[(i,j)] = Zsc[(j,i)] - else - Memento.error(_LOGGER, "Short-circuit impedance between winding $i and $j is missing.") - end - end - end - end - # make Zb - Zb = zeros(Complex{Float64}, N-1,N-1) - for i in 1:N-1 - Zb[i,i] = Zsc[(1,i+1)] - end - for i in 1:N-1 - for j in 1:i-1 - Zb[i,j] = (Zb[i,i]+Zb[j,j]-Zsc[(j+1,i+1)])/2 - Zb[j,i] = Zb[i,j] - end - end - # get Ybus - Y = pinv(Zb) - Y = [-Y*ones(N-1) Y] - Y = [-ones(1,N-1)*Y; Y] - # extract elements - Zbr = Dict() - for k in keys(Zsc) - Zbr[k] = (abs(Y[k...])==0) ? Inf : -1/Y[k...] - end - return Zbr -end - - -""" - _dss2pmd_reactor!(pmd_data, dss_data, import_all) - -Adds PowerModels-style branch components based on DSS reactors to `pmd_data` from `dss_data` -""" -function _dss2pmd_reactor!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "branch") - pmd_data["branch"] = [] - end - - if haskey(dss_data, "reactor") - Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating like line") - for reactor in dss_data["reactor"] - if haskey(reactor, "bus2") - _apply_like!(reactor, dss_data, "reactor") - defaults = _apply_ordered_properties(_create_reactor(reactor["bus1"], reactor["name"], reactor["bus2"]; _to_sym_keys(reactor)...), reactor) - - reactDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - - f_bus, nodes = _parse_busname(defaults["bus1"]) - t_bus = _parse_busname(defaults["bus2"])[1] - - reactDict["name"] = defaults["name"] - reactDict["f_bus"] = find_bus(f_bus, pmd_data) - reactDict["t_bus"] = find_bus(t_bus, pmd_data) - - reactDict["br_r"] = _parse_matrix(diagm(0 => fill(0.2, nconductors)), nodes, nconductors) - reactDict["br_x"] = _parse_matrix(zeros(nconductors, nconductors), nodes, nconductors) - - reactDict["g_fr"] = _parse_array(0.0, nodes, nconductors) - reactDict["g_to"] = _parse_array(0.0, nodes, nconductors) - reactDict["b_fr"] = _parse_array(0.0, nodes, nconductors) - reactDict["b_to"] = _parse_array(0.0, nodes, nconductors) - - for key in ["g_fr", "g_to", "b_fr", "b_to"] - reactDict[key] = LinearAlgebra.diagm(0=>reactDict[key]) - end - - reactDict["c_rating_a"] = _parse_array(defaults["normamps"], nodes, nconductors) - reactDict["c_rating_b"] = _parse_array(defaults["emergamps"], nodes, nconductors) - reactDict["c_rating_c"] = _parse_array(defaults["emergamps"], nodes, nconductors) - - reactDict["tap"] = _parse_array(1.0, nodes, nconductors, NaN) - reactDict["shift"] = _parse_array(0.0, nodes, nconductors) - - reactDict["br_status"] = convert(Int, defaults["enabled"]) - - reactDict["angmin"] = _parse_array(-60.0, nodes, nconductors, -60.0) - reactDict["angmax"] = _parse_array( 60.0, nodes, nconductors, 60.0) - - reactDict["transformer"] = true - - reactDict["index"] = length(pmd_data["branch"]) + 1 - - nodes = .+([_parse_busname(defaults[n])[2] for n in ["bus1", "bus2"]]...) - reactDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - reactDict["source_id"] = "reactor.$(defaults["name"])" - - used = [] - _PMs._import_remaining!(reactDict, defaults, import_all; exclude=used) - - push!(pmd_data["branch"], reactDict) - end - end - end -end - - -""" - _dss2pmd_pvsystem!(pmd_data, dss_data) - -Adds PowerModels-style pvsystems to `pmd_data` from `dss_data`. -""" -function _dss2pmd_pvsystem!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "pvsystem") - pmd_data["pvsystem"] = [] - end - - for pvsystem in get(dss_data, "pvsystem", []) - _apply_like!(pvsystem, dss_data, "pvsystem") - defaults = _apply_ordered_properties(_create_pvsystem(pvsystem["bus1"], pvsystem["name"]; _to_sym_keys(pvsystem)...), pvsystem) - - pvsystemDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - pvsystemDict["name"] = defaults["name"] - pvsystemDict["pv_bus"] = find_bus(name, pmd_data) - pvsystemDict["p"] = _parse_array(defaults["kw"] / 1e3, nodes, nconductors) - pvsystemDict["q"] = _parse_array(defaults["kvar"] / 1e3, nodes, nconductors) - pvsystemDict["status"] = convert(Int, defaults["enabled"]) - - pvsystemDict["index"] = length(pmd_data["pvsystem"]) + 1 - - pvsystemDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - pvsystemDict["source_id"] = "pvsystem.$(defaults["name"])" - - used = ["phases", "bus1", "name"] - _PMs._import_remaining!(pvsystemDict, defaults, import_all; exclude=used) - - push!(pmd_data["pvsystem"], pvsystemDict) - end -end - - -""" - _dss2pmd_storage!(pmd_data, dss_data, import_all) - -Adds PowerModels-style storage to `pmd_data` from `dss_data` -""" -function _dss2pmd_storage!(pmd_data::Dict, dss_data::Dict, import_all::Bool) - if !haskey(pmd_data, "storage") - pmd_data["storage"] = [] - end - - for storage in get(dss_data, "storage", []) - _apply_like!(storage, dss_data, "storage") - defaults = _apply_ordered_properties(_create_storage(storage["bus1"], storage["name"]; _to_sym_keys(storage)...), storage) - - storageDict = Dict{String,Any}() - - nconductors = pmd_data["conductors"] - name, nodes = _parse_busname(defaults["bus1"]) - - storageDict["name"] = defaults["name"] - storageDict["storage_bus"] = find_bus(name, pmd_data) - storageDict["energy"] = defaults["kwhstored"] / 1e3 - storageDict["energy_rating"] = defaults["kwhrated"] / 1e3 - storageDict["charge_rating"] = defaults["%charge"] * defaults["kwrated"] / 1e3 / 100.0 - storageDict["discharge_rating"] = defaults["%discharge"] * defaults["kwrated"] / 1e3 / 100.0 - storageDict["charge_efficiency"] = defaults["%effcharge"] / 100.0 - storageDict["discharge_efficiency"] = defaults["%effdischarge"] / 100.0 - storageDict["thermal_rating"] = _parse_array(defaults["kva"] / 1e3 / nconductors, nodes, nconductors) - storageDict["qmin"] = _parse_array(-defaults["kvar"] / 1e3 / nconductors, nodes, nconductors) - storageDict["qmax"] = _parse_array( defaults["kvar"] / 1e3 / nconductors, nodes, nconductors) - storageDict["r"] = _parse_array(defaults["%r"] / 100.0, nodes, nconductors) - storageDict["x"] = _parse_array(defaults["%x"] / 100.0, nodes, nconductors) - storageDict["p_loss"] = defaults["%idlingkw"] * defaults["kwrated"] / 1e3 - storageDict["q_loss"] = defaults["%idlingkvar"] * defaults["kvar"] / 1e3 - - storageDict["status"] = convert(Int, defaults["enabled"]) - - storageDict["ps"] = _parse_array(0.0, nodes, nconductors) - storageDict["qs"] = _parse_array(0.0, nodes, nconductors) - - storageDict["index"] = length(pmd_data["storage"]) + 1 - - storageDict["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - storageDict["source_id"] = "storage.$(defaults["name"])" - - used = ["phases", "bus1", "name"] - _PMs._import_remaining!(storageDict, defaults, import_all; exclude=used) - - push!(pmd_data["storage"], storageDict) - end -end - - -""" - _adjust_sourcegen_bounds!(pmd_data) - -Changes the bounds for the sourcebus generator by checking the emergamps of all -of the branches attached to the sourcebus and taking the sum of non-infinite -values. Defaults to Inf if all emergamps connected to sourcebus are also Inf. -This method was updated to include connected transformers as well. It know -has to occur after the call to InfrastructureModels.arrays_to_dicts, so the code -was adjusted to accomodate that. -""" -function _adjust_sourcegen_bounds!(pmd_data) - emergamps = Array{Float64,1}([0.0]) - sourcebus_n = find_bus(pmd_data["sourcebus"], pmd_data) - for (_,line) in pmd_data["branch"] - if (line["f_bus"] == sourcebus_n || line["t_bus"] == sourcebus_n) && !startswith(line["source_id"], "virtual") - append!(emergamps, get(line, "c_rating_b", get(line, "rate_b", missing))) - end - end - - if haskey(pmd_data, "transformer") - for (_,trans) in pmd_data["transformer"] - if trans["f_bus"] == sourcebus_n || trans["t_bus"] == sourcebus_n - append!(emergamps, trans["rate_b"]) - end - end - end - - bound = sum(emergamps) - - pmd_data["gen"]["1"]["pmin"] = fill(-bound, size(pmd_data["gen"]["1"]["pmin"])) - pmd_data["gen"]["1"]["pmax"] = fill( bound, size(pmd_data["gen"]["1"]["pmin"])) - pmd_data["gen"]["1"]["qmin"] = fill(-bound, size(pmd_data["gen"]["1"]["pmin"])) - pmd_data["gen"]["1"]["qmax"] = fill( bound, size(pmd_data["gen"]["1"]["pmin"])) - - # set current rating of vbranch modelling internal impedance - vbranch = [br for (id, br) in pmd_data["branch"] if br["name"]=="sourcebus_vbranch"][1] - vbranch["rate_a"] = fill(bound, length(vbranch["rate_a"])) -end - - -""" - - function _decompose_transformers!(pmd_data) - -Replaces complex transformers with a composition of ideal transformers and branches -which model losses. New buses (virtual, no physical meaning) are added. -""" -function _decompose_transformers!(pmd_data; import_all::Bool=false) - if !haskey(pmd_data, "transformer") - pmd_data["transformer"] = Dict{String, Any}() - end - ncnds = pmd_data["conductors"] - for (tr_id, trans) in pmd_data["transformer_comp"] - nrw = length(trans["buses"]) - endnode_id_w = Array{Int, 1}(undef, nrw) - bus_reduce = [] - branch_reduce = [] - # sum ratings for all windings to have internal worst-case ratings - rate_a = sum(trans["rate_a"]) - rate_b = sum(trans["rate_b"]) - rate_c = sum(trans["rate_c"]) - for w in 1:nrw - # 2-WINDING TRANSFORMER - trans_dict = Dict{String, Any}() - trans_dict["name"] = "tr$(tr_id)_w$(w)" - trans_dict["source_id"] = "$(trans["source_id"])_$(w)" - trans_dict["active_phases"] = [1, 2, 3] - _push_dict_ret_key!(pmd_data["transformer"], trans_dict) - # connection settings - trans_dict["config_fr"] = trans["config"][w] - trans_dict["config_to"] = Dict( - "type"=>"wye", - "polarity"=>1, - "cnd"=>[1, 2, 3], - "grounded"=>true, - "vm_nom"=>1.0 - ) - # temporary fix for prop renaming - trans_dict["polarity"] = trans_dict["config_fr"]["polarity"] - trans_dict["configuration"] = trans_dict["config_fr"]["type"] - trans_dict["f_connections"] = trans_dict["config_fr"]["cnd"] - trans_dict["t_connections"] = trans_dict["config_to"]["cnd"] - scale = trans_dict["configuration"]=="delta" ? sqrt(3) : 1.0 - trans_dict["tm_nom"] = trans_dict["config_fr"]["vm_nom"]*scale - - trans_dict["f_bus"] = trans["buses"][w] - # make virtual bus and mark it for reduction - vbus_tr = _create_vbus!(pmd_data, basekv=1.0, name="tr$(tr_id)_w$(w)_b1") - trans_dict["t_bus"] = vbus_tr["index"] - append!(bus_reduce, vbus_tr["index"]) - # convert to baseMVA, because this is not done per_unit now) - trans_dict["rate_a"] = trans["rate_a"][w]/pmd_data["baseMVA"] - trans_dict["rate_b"] = trans["rate_b"][w]/pmd_data["baseMVA"] - trans_dict["rate_c"] = trans["rate_c"][w]/pmd_data["baseMVA"] - # tap settings - trans_dict["tm"] = trans["tm"][w] - trans_dict["fixed"] = trans["fixed"][w] - trans_dict["tm_max"] = trans["tm_max"][w] - trans_dict["tm_min"] = trans["tm_min"][w] - trans_dict["tm_step"] = trans["tm_step"][w] - # WINDING SERIES RESISTANCE - # make virtual bus and mark it for reduction - vbus_br = _create_vbus!(pmd_data, basekv=1.0, name="tr$(tr_id)_w$(w)_b2") - append!(bus_reduce, vbus_br["index"]) - # make virtual branch and mark it for reduction - br = _create_vbranch!( - pmd_data, vbus_tr["index"], vbus_br["index"], - vbase=1.0, - br_r=trans["rs"][w], - g_fr=trans["gsh"][w], - b_fr=trans["bsh"][w], - rate_a=rate_a, - rate_b=rate_b, - rate_c=rate_c, - name="tr$(tr_id)_w$(w)_rs" - ) - append!(branch_reduce, br["index"]) - # save the trailing node for the reactance model - endnode_id_w[w] = vbus_br["index"] - end - # now add the fully connected graph for reactances - for w in 1:nrw - for v in w+1:nrw - br = _create_vbranch!( - pmd_data, endnode_id_w[w], endnode_id_w[v], - vbase=1.0, - br_x=trans["xs"][string(w,"-",v)], - rate_a=rate_a, - rate_b=rate_b, - rate_c=rate_c, - name="tr$(tr_id)_xs_$(w)to$(v)" - ) - append!(branch_reduce, br["index"]) - end - end - _rm_redundant_pd_elements!(pmd_data, buses=string.(bus_reduce), branches=string.(branch_reduce)) - end - # remove the transformer_comp dict unless import_all is flagged - if !import_all - delete!(pmd_data, "transformer_comp") - end -end - - -""" -This function adds a new bus to the data model and returns its dictionary. -It is virtual in the sense that it does not correspond to a bus in the network, -but is part of the decomposition of the transformer. -""" -function _create_vbus!(pmd_data; vmin=0, vmax=Inf, basekv=pmd_data["basekv"], name="", source_id="") - vbus = Dict{String, Any}("bus_type"=>"1", "name"=>name) - vbus_id = _push_dict_ret_key!(pmd_data["bus"], vbus) - vbus["bus_i"] = vbus_id - vbus["source_id"] = source_id - ncnds = pmd_data["conductors"] - vbus["vm"] = ones(Float64, ncnds) - vbus["va"] = zeros(Float64, ncnds) - vbus["vmin"] = ones(Float64, ncnds)*vmin - vbus["vmax"] = ones(Float64, ncnds)*vmax - vbus["base_kv"] = basekv - return vbus -end - - -""" -This function adds a new branch to the data model and returns its dictionary. -It is virtual in the sense that it does not correspond to a branch in the -network, but is part of the decomposition of the transformer. -""" -function _create_vbranch!(pmd_data, f_bus::Int, t_bus::Int; - name="", source_id="", active_phases=[1, 2, 3], - kwargs...) - ncnd = pmd_data["conductors"] - kwargs = Dict{Symbol,Any}(kwargs) - vbase = haskey(kwargs, :vbase) ? kwargs[:vbase] : pmd_data["basekv"] - # TODO assumes per_unit will be flagged - sbase = haskey(kwargs, :sbase) ? kwargs[:sbase] : pmd_data["baseMVA"] - zbase = vbase^2/sbase - # convert to LN vbase in instead of LL vbase - zbase *= (1/3) - vbranch = Dict{String, Any}("f_bus"=>f_bus, "t_bus"=>t_bus, "name"=>name) - vbranch["active_phases"] = active_phases - vbranch["source_id"] = "virtual_branch.$name" - for k in [:br_r, :br_x, :g_fr, :g_to, :b_fr, :b_to] - if !haskey(kwargs, k) - vbranch[string(k)] = zeros(ncnd, ncnd) - else - if k in [:br_r, :br_x] - vbranch[string(k)] = kwargs[k]./zbase - else - vbranch[string(k)] = kwargs[k].*zbase - end - end - end - vbranch["angmin"] = -ones(ncnd)*60 - vbranch["angmax"] = ones(ncnd)*60 - vbranch["rate_a"] = get(kwargs, :rate_a, fill(Inf, length(active_phases))) - vbranch["shift"] = zeros(ncnd) - vbranch["tap"] = ones(ncnd) - vbranch["transformer"] = false - vbranch["switch"] = false - vbranch["br_status"] = 1 - for k in [:rate_a, :rate_b, :rate_c, :c_rating_a, :c_rating_b, :c_rating_c] - if haskey(kwargs, k) - vbranch[string(k)] = kwargs[k] - end - end - _push_dict_ret_key!(pmd_data["branch"], vbranch) - return vbranch -end - - -"This function appends a component to a component dictionary of a pmd data model" -function _push_dict_ret_key!(dict::Dict{String, Any}, v::Dict{String, Any}; assume_no_gaps=false) - if isempty(dict) - k = 1 - elseif assume_no_gaps - k = length(keys(dict))+1 - else - k = maximum([parse(Int, x) for x in keys(dict)])+1 - end - - dict[string(k)] = v - v["index"] = k - return k -end - - -""" -This function removes zero impedance branches. Only for transformer loss model! -Branches with zero impedances are deleted, and one of the buses it connects. -For now, the implementation should only be used on the loss model of -transformers. When deleting buses, references at shunts, loads... should -be updated accordingly. In the current implementation, that is only done -for shunts. The other elements, such as loads, do not appear in the -transformer loss model. -""" -function _rm_redundant_pd_elements!(pmd_data; buses=keys(pmd_data["bus"]), branches=keys(pmd_data["branch"])) - # temporary dictionary for pi-model shunt elements - shunts_g = Dict{Int, Any}() - shunts_b = Dict{Int, Any}() - for (br_id, br) in pmd_data["branch"] - f_bus = br["f_bus"] - t_bus = br["t_bus"] - # if branch is flagged - if br_id in branches - # flags for convenience - is_selfloop = f_bus==t_bus # guaranteed to be reducable because branch is flagged - is_shorted = all(br["br_r"] .==0) && all(br["br_x"] .==0) - is_reducable = string(f_bus) in buses || string(t_bus) in buses - if is_shorted && is_reducable - # choose bus to remove - rm_bus = (f_bus in buses) ? f_bus : t_bus - kp_bus = (rm_bus==f_bus) ? t_bus : f_bus - elseif is_selfloop - kp_bus = t_bus - else - # nothing to do, go to next branch - continue - end - # move shunts to the bus that will be left - if !haskey(shunts_g, kp_bus) - shunts_g[kp_bus] = zeros(3, 3) - shunts_b[kp_bus] = zeros(3, 3) - end - - # bus shunts are diagonal, but branch shunts can b e full matrices - # ensure no data is lost by only keeping the diagonal - # this should not be the case for the current transformer parsing - for key in ["g_fr", "g_to", "b_fr", "b_to"] - @assert(all(br[key]-diagm(0=>diag(br[key])).==0)) - end - - shunts_g[kp_bus] .+= br["g_fr"] - shunts_g[kp_bus] .+= br["g_to"] - shunts_b[kp_bus] .+= br["b_fr"] - shunts_b[kp_bus] .+= br["b_to"] - # remove branch from pmd_data - delete!(pmd_data["branch"], string(br_id)) - if is_shorted && is_reducable - # remove bus from pmd_data - delete!(pmd_data["bus"], string(rm_bus)) - # replace bus references in branches - for (br_id, br) in pmd_data["branch"] - if br["f_bus"] == rm_bus - br["f_bus"] = kp_bus - end - if br["t_bus"] == rm_bus - br["t_bus"] = kp_bus - end - end - # replace bus references in transformers - for (_, tr) in pmd_data["transformer"] - if tr["f_bus"] == rm_bus - tr["f_bus"] = kp_bus - end - if tr["t_bus"] == rm_bus - tr["t_bus"] = kp_bus - end - end - # replace bus references in gens, loads, shunts, storage - for comp_type in ["gen", "load", "shunt", "storage"] - for (_, comp) in pmd_data[comp_type] - if comp["$(comp_type)_bus"] == rm_bus - comp["$(comp_type)_bus"] = kp_bus - end - end - end - # fix new shunt buses - for shunts in [shunts_g, shunts_b] - for (bus, shunt) in shunts - if bus == rm_bus - shunts[kp_bus] .+= shunt - delete!(shunts, bus) - end - end - end - # TODO clean up other references to the removed bus - # like for example loads, generators, ... - # skipped for now, not relevant for transformer loss model - # + pvsystem - # ... - end - elseif f_bus==t_bus - # this might occur if not all buses and branches are marked for removal - # a branch in parallel with a removed branch can turn into a self-loop - # and if that branch is not marked for removal, we end up here - Memento.error(_LOGGER, "Specified set of buses and branches leads to a self-loop.") - end - end - # create shunts for lumped pi-model shunts - for (bus, shunt_g) in shunts_g - shunt_b = shunts_b[bus] - if !all(shunt_g .==0) || !all(shunt_b .==0) - Memento.warn(_LOGGER, "Pi-model shunt was moved to a bus shunt. Off-diagonals will be discarded in the data model.") - # The shunts are part of PM, and will be scaled later on by make_per_unit, - # unlike PMD level components. The shunts here originate from PMD level - # components which were already scaled. Therefore, we have to undo the - # scaling here to prevent double scaling later on. - gs = shunt_g./1*pmd_data["baseMVA"] - bs = shunt_b./1*pmd_data["baseMVA"] - _add_shunt!(pmd_data, bus, gs=gs, bs=bs) - end - end -end - - -""" -Helper function to add a new shunt. The shunt element is always inserted at the -internal bus of the second winding in OpenDSS. If one of the branches of the -loss model connected to this bus, has zero impedance (for example, if XHL==0 -or XLT==0 or R[3]==0), then this bus might be removed by -_rm_redundant_pd_elements!, in which case a new shunt should be inserted at the -remaining bus of the removed branch. -""" -function _add_shunt!(pmd_data, bus; gs=zeros(3,3), bs=zeros(3,3), vbase_kv=1, sbase_mva=1) - # TODO check whether keys are consistent with the actual data model - shunt_dict = Dict{String, Any}("status"=>1, "shunt_bus"=>bus) - zbase = vbase_kv^2/sbase_mva - shunt_dict["gs"] = gs*zbase - shunt_dict["bs"] = bs*zbase - _push_dict_ret_key!(pmd_data["shunt"], shunt_dict, assume_no_gaps=false) -end - - -""" - function _adjust_base!(pmd_data) - -Updates the voltage base at each bus, so that the ratios of the voltage bases -across a transformer are consistent with the ratios of voltage ratings of the -windings. Default behaviour is to start at the primary winding of the first -transformer, and to propagate from there. Branches are updated; the impedances -and addmittances are rescaled to be consistent with the new voltage bases. -""" -function _adjust_base!(pmd_data; start_at_first_tr_prim=false) - # initialize arrays etc. for the recursive part - edges_br = [(br["index"], br["f_bus"], br["t_bus"]) for (br_id_str, br) in pmd_data["branch"]] - edges_tr = [(tr["index"], tr["f_bus"], tr["t_bus"]) for (tr_id_str, tr) in pmd_data["transformer"]] - edges_br_visited = Dict{Int, Bool}([(edge[1], false) for edge in edges_br]) - edges_tr_visited = Dict{Int, Bool}([(edge[1], false) for edge in edges_tr]) - bus_ids = [parse(Int, x) for x in keys(pmd_data["bus"])] - nodes_visited = Dict{Int, Bool}([(bus_id, false) for bus_id in bus_ids]) - # retrieve old voltage bases from connected nodes before starting - br_basekv_old = Dict([(br["index"], pmd_data["bus"][string(br["f_bus"])]["base_kv"]) for (br_id_str, br) in pmd_data["branch"]]) - # start from the primary of the first transformer - if start_at_first_tr_prim && haskey(pmd_data, "transformer") && haskey(pmd_data["transformer"], "1") - trans_first = pmd_data["transformer"]["1"] - source = trans_first["f_bus"] - base_kv_new = trans_first["config_fr"]["vm_nom"] - else - # start at type 3 bus if present - buses_3 = [bus["index"] for (bus_id_str, bus) in pmd_data["bus"] if bus["bus_type"]==3] - buses_2 = [bus["index"] for (bus_id_str, bus) in pmd_data["bus"] if bus["bus_type"]==2] - if length(buses_3)>0 - source = buses_3[1] - elseif length(buses_2)>0 - source = buses_2[1] - else - Memento.warn(_LOGGER, "No bus of type 3 found; selecting random bus instead.") - source = parse(Int, rand(keys(pmd_data["bus"]))) - end - base_kv_new = pmd_data["basekv"] - end - _adjust_base_rec!(pmd_data, source, base_kv_new, nodes_visited, edges_br, edges_br_visited, edges_tr, edges_tr_visited, br_basekv_old) - if !all(values(nodes_visited)) - Memento.warn(_LOGGER, "The network contains buses which are not reachable from the start node for the change of voltage base.") - end -end - - -""" -This is the recursive code that goes with _adjust_base!; _adjust_base! -initializes arrays and other data that is passed along in the calls to this -recursive function. For very large networks, this might have to be rewritten -to not rely on recursion. -""" -function _adjust_base_rec!(pmd_data, source::Int, base_kv_new::Float64, nodes_visited, edges_br, edges_br_visited, edges_tr, edges_tr_visited, br_basekv_old) - source_dict = pmd_data["bus"][string(source)] - base_kv_prev = source_dict["base_kv"] - if !(base_kv_prev≈base_kv_new) - # only possible when meshed; ensure consistency - if nodes_visited[source] - Memento.error(_LOGGER, "Transformer ratings lead to an inconsistent definition for the voltage base at bus $source.") - end - source_dict["base_kv"] = base_kv_new - # update the connected shunts with the new voltage base - source_shunts = [shunt for (sh_id_str, shunt) in pmd_data["shunt"] if shunt["shunt_bus"]==source] - for shunt in source_shunts - _adjust_base_shunt!(pmd_data, shunt["index"], base_kv_prev, base_kv_new) - end - source_name = haskey(source_dict, "name") ? source_dict["name"] : "" - if source_dict["bus_type"]==3 - #TODO is this the desired behaviour, keep SI units for type 3 bus? - source_dict["vm"] *= base_kv_prev/base_kv_new - source_dict["vmax"] *= base_kv_prev/base_kv_new - source_dict["vmin"] *= base_kv_prev/base_kv_new - Memento.info(_LOGGER, "Rescaling vm, vmin and vmax conform with new base_kv at type 3 bus $source($source_name): $base_kv_prev => $base_kv_new") - else - Memento.info(_LOGGER, "Resetting base_kv at bus $source($source_name): $base_kv_prev => $base_kv_new") - end - # TODO rescale vmin, vmax, vm - # what is the desired behaviour here? - # should the p.u. set point stay the same, or the set point in SI units? - end - nodes_visited[source] = true - # propagate through the connected branches - for (br_id, f_bus, t_bus) in [edge for edge in edges_br if !edges_br_visited[edge[1]]] - # check !edges_br_visited[edge[1]] again, might be visited by now - if (f_bus==source || t_bus==source) && !edges_br_visited[br_id] - # this edge will be visited - edges_br_visited[br_id] = true - source_new = (f_bus==source) ? t_bus : f_bus - # assume the branch was undimensionalised with the basekv of the node - # it is connected to; ideally this will be a property of the branch - # itself in the future to ensure consistency - base_kv_branch_prev = br_basekv_old[br_id] - if base_kv_branch_prev != base_kv_new - br = pmd_data["branch"]["$br_id"] - br_name = haskey(br, "name") ? br["name"] : "" - Memento.info(_LOGGER, "Rescaling impedances at branch $br_id($br_name), conform with change of voltage base: $base_kv_branch_prev => $base_kv_new") - _adjust_base_branch!(pmd_data, br_id, base_kv_branch_prev, base_kv_new) - end - # follow the edge to the adjacent node and repeat - _adjust_base_rec!(pmd_data, source_new, base_kv_new, nodes_visited, edges_br, edges_br_visited, edges_tr, edges_tr_visited, br_basekv_old) - end - end - # propogate through the connected transformers - for (tr_id, f_bus, t_bus) in [edge for edge in edges_tr if !edges_tr_visited[edge[1]]] - if f_bus==source || t_bus==source - # this edge is now being visited - edges_tr_visited[tr_id] = true - source_new = (f_bus==source) ? t_bus : f_bus - # scale the basekv across the transformer - trans = pmd_data["transformer"][string(tr_id)] - base_kv_new_tr = deepcopy(base_kv_new) - if source_new==t_bus - base_kv_new_tr *= (trans["config_to"]["vm_nom"]/trans["config_fr"]["vm_nom"]) - trans["tm_nom"] *= (base_kv_new_tr/base_kv_prev) - else - base_kv_new_tr *= (trans["config_fr"]["vm_nom"]/trans["config_to"]["vm_nom"]) - trans["tm_nom"] *= (base_kv_prev/base_kv_new_tr) - end - # follow the edge to the adjacent node and repeat - _adjust_base_rec!(pmd_data, source_new, base_kv_new_tr, nodes_visited, edges_br, edges_br_visited, edges_tr, edges_tr_visited, br_basekv_old) - end - end -end - - -"Rescales the parameters of a branch to reflect a change in voltage base" -function _adjust_base_branch!(pmd_data, br_id::Int, base_kv_old::Float64, base_kv_new::Float64) - branch = pmd_data["branch"][string(br_id)] - zmult = (base_kv_old/base_kv_new)^2 - branch["br_r"] *= zmult - branch["br_x"] *= zmult - branch["g_fr"] *= 1/zmult - branch["b_fr"] *= 1/zmult - branch["g_to"] *= 1/zmult - branch["b_to"] *= 1/zmult -end - - -"Rescales the parameters of a shunt to reflect a change in voltage base" -function _adjust_base_shunt!(pmd_data, sh_id::Int, base_kv_old::Float64, base_kv_new::Float64) - shunt = pmd_data["shunt"][string(sh_id)] - zmult = (base_kv_old/base_kv_new)^2 - shunt["bs"] *= 1/zmult - shunt["gs"] *= 1/zmult -end - - -""" - _where_is_comp(data, comp_id) - -Finds existing component of id `comp_id` in array of `data` and returns index. -Assumes all components in `data` are unique. -""" -function _where_is_comp(data::Array, comp_id::AbstractString)::Int - for (i, e) in enumerate(data) - if e["name"] == comp_id - return i - end - end - return 0 -end - - -""" - _correct_duplicate_components!(dss_data) - -Finds duplicate components in `dss_data` and merges up, meaning that older -data (lower indices) is always overwritten by newer data (higher indices). -""" -function _correct_duplicate_components!(dss_data::Dict) - out = Dict{String,Array}() - for (k, v) in dss_data - if !(k in ["options"]) - out[k] = [] - for comp in v - if isa(comp, Dict) - idx = _where_is_comp(out[k], comp["name"]) - if idx > 0 - merge!(out[k][idx], comp) - else - push!(out[k], comp) - end - end - end - end - end - merge!(dss_data, out) -end - - -"Creates a virtual branch between the `virtual_sourcebus` and `sourcebus` with the impedance given by `circuit`" -function _create_sourcebus_vbranch!(pmd_data::Dict, circuit::Dict) - sourcebus = find_bus(pmd_data["sourcebus"], pmd_data) - vsourcebus = find_bus("virtual_sourcebus", pmd_data) - - br_r = circuit["rmatrix"] - br_x = circuit["xmatrix"] - - vbranch = _create_vbranch!(pmd_data, sourcebus, vsourcebus; name="sourcebus_vbranch", br_r=br_r, br_x=br_x) -end - - -"Combines transformers with 'bank' keyword into a single transformer" -function _bank_transformers!(pmd_data::Dict) - transformer_names = Dict(trans["name"] => n for (n, trans) in get(pmd_data, "transformer_comp", Dict())) - bankable_transformers = [trans for trans in values(get(pmd_data, "transformer_comp", Dict())) if haskey(trans, "bank")] - banked_transformers = Dict() - for transformer in bankable_transformers - bank = transformer["bank"] - - if !(bank in keys(banked_transformers)) - n = length(pmd_data["transformer_comp"])+length(banked_transformers)+1 - - banked_transformers[bank] = deepcopy(transformer) - banked_transformers[bank]["name"] = deepcopy(transformer["bank"]) - banked_transformers[bank]["source_id"] = "transformer.$(transformer["bank"])" - banked_transformers[bank]["index"] = n - # set impedances / admittances to zero; only the specified phases should be non-zero - for key in ["rs", "xs", "bsh", "gsh"] - inds = key=="xs" ? keys(banked_transformers[bank][key]) : 1:length(banked_transformers[bank][key]) - for w in inds - banked_transformers[bank][key][w] *= 0 - end - end - delete!(banked_transformers[bank], "bank") - end - - banked_transformer = banked_transformers[bank] - for phase in transformer["active_phases"] - push!(banked_transformer["active_phases"], phase) - for (k, v) in banked_transformer - if isa(v, Vector) && eltype(v) <: Vector - # most properties are arrays (indexed over the windings) - for w in 1:length(v) - banked_transformer[k][w][phase] = deepcopy(transformer[k][w][phase]) - end - elseif isa(v, Vector) && eltype(v) <: Matrix - # most properties are arrays (indexed over the windings) - for w in 1:length(v) - banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) - end - elseif k=="xs" - # xs is a Dictionary indexed over pairs of windings - for w in keys(v) - banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) - end - end - end - end - end - - for transformer in bankable_transformers - delete!(pmd_data["transformer_comp"], transformer_names[transformer["name"]]) - end - - for transformer in values(banked_transformers) - pmd_data["transformer_comp"]["$(transformer["index"])"] = deepcopy(transformer) - end -end - - -"Parses buscoords [lon,lat] (if present) into their respective buses" -function _dss2pmd_buscoords!(pmd_data::Dict, dss_data::Dict) - for bc in get(dss_data, "buscoords", []) - bus = pmd_data["bus"]["$(find_bus(bc["name"], pmd_data))"] - bus["lon"] = bc["x"] - bus["lat"] = bc["y"] - end -end - - -""" - parse_options(options) - -Parses options defined with the `set` command in OpenDSS. -""" -function parse_options(options) - out = Dict{String,Any}() - if haskey(options, "voltagebases") - out["voltagebases"] = _parse_array(Float64, options["voltagebases"]) - end - - if !haskey(options, "defaultbasefreq") - Memento.warn(_LOGGER, "defaultbasefreq is not defined, default for circuit set to 60 Hz") - out["defaultbasefreq"] = 60.0 - else - out["defaultbasefreq"] = parse(Float64, options["defaultbasefreq"]) - end - - return out -end - - -"Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" -function parse_opendss(dss_data::Dict; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict - pmd_data = Dict{String,Any}() - - _correct_duplicate_components!(dss_data) - - parse_dss_with_dtypes!(dss_data, ["line", "linecode", "load", "generator", "capacitor", - "reactor", "circuit", "transformer", "pvsystem", - "storage"]) - - if haskey(dss_data, "options") - condensed_opts = [Dict{String,Any}()] - for opt in dss_data["options"] - merge!(condensed_opts[1], opt) - end - dss_data["options"] = condensed_opts - end - - merge!(pmd_data, parse_options(get(dss_data, "options", [Dict{String,Any}()])[1])) - - pmd_data["per_unit"] = false - pmd_data["source_type"] = "dss" - pmd_data["source_version"] = string(VersionNumber("0")) - - if haskey(dss_data, "circuit") - circuit = dss_data["circuit"][1] - defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), circuit["name"]; _to_sym_keys(circuit)...) - - pmd_data["name"] = defaults["name"] - pmd_data["basekv"] = defaults["basekv"] - pmd_data["baseMVA"] = defaults["basemva"] - pmd_data["basefreq"] = pop!(pmd_data, "defaultbasefreq") - pmd_data["pu"] = defaults["pu"] - pmd_data["conductors"] = defaults["phases"] - pmd_data["sourcebus"] = defaults["bus1"] - else - Memento.error(_LOGGER, "Circuit not defined, not a valid circuit!") - end - - _dss2pmd_bus!(pmd_data, dss_data, import_all, vmin, vmax) - _dss2pmd_load!(pmd_data, dss_data, import_all) - _dss2pmd_shunt!(pmd_data, dss_data, import_all) - _dss2pmd_branch!(pmd_data, dss_data, import_all) - _dss2pmd_transformer!(pmd_data, dss_data, import_all) - _dss2pmd_reactor!(pmd_data, dss_data, import_all) - _dss2pmd_gen!(pmd_data, dss_data, import_all) - _dss2pmd_pvsystem!(pmd_data, dss_data, import_all) - _dss2pmd_storage!(pmd_data, dss_data, import_all) - - pmd_data["dcline"] = [] - pmd_data["switch"] = [] - - InfrastructureModels.arrays_to_dicts!(pmd_data) - - _dss2pmd_buscoords!(pmd_data, dss_data) - - if bank_transformers - _bank_transformers!(pmd_data) - end - - for optional in ["dcline", "load", "shunt", "storage", "pvsystem", "branch"] - if length(pmd_data[optional]) == 0 - pmd_data[optional] = Dict{String,Any}() - end - end - - _create_sourcebus_vbranch!(pmd_data, defaults) - - if haskey(pmd_data, "transformer_comp") - # this has to be done before calling _adjust_sourcegen_bounds! - _decompose_transformers!(pmd_data; import_all=import_all) - _adjust_base!(pmd_data) - else - pmd_data["transformer"] = Dict{String, Any}() - end - - _adjust_sourcegen_bounds!(pmd_data) - - pmd_data["files"] = dss_data["filename"] - - return pmd_data -end - - -"Parses a DSS file into a PowerModels usable format" -function parse_opendss(io::IOStream; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict - dss_data = parse_dss(io) - - return parse_opendss(dss_data; import_all=import_all) -end From c6001e71efcad9335f3e42a6e5a120b5b63b05ba Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 08:41:57 -0700 Subject: [PATCH 032/224] RM: old index in transformation --- src/io/common_dm.jl | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl index 0dd988e2e..4c8d8fb39 100644 --- a/src/io/common_dm.jl +++ b/src/io/common_dm.jl @@ -28,13 +28,7 @@ function parse_file_dm(file::String; data_model::String="engineering", kwargs... end if data_model == "mathematical" - # to get the old indexing - pmd_data_old = open(file) do io - parse_file(io; filetype=split(lowercase(file), '.')[end], kwargs...) - end - index_presets = Dict(comp_type=>Dict(comp["name"]=>comp["index"] for (id, comp) in pmd_data_old[comp_type]) for comp_type in ["bus"]) - - transform_data_model!(pmd_data; index_presets=index_presets) + transform_data_model!(pmd_data) end return pmd_data @@ -42,11 +36,11 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model!(data::Dict{String,<:Any}; index_presets::Dict{String,<:Any}=Dict{String,Any}()) +function transform_data_model!(data::Dict{String,<:Any}) if get(data, "data_model", "mathematical") == "engineering" data_model_map!(data) data_model_make_pu!(data) - data_model_index!(data, index_presets=index_presets) + data_model_index!(data) data_model_make_compatible_v8!(data) else if haskey(data, "") From 217e96f87f8e06673398b162de9fec753e9a7d7b Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 08:42:12 -0700 Subject: [PATCH 033/224] FIX: _apply_like! --- src/io/dss_parse.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index c1b64fb5d..6190158a0 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -1209,7 +1209,7 @@ function _apply_like!(raw_dss, data_dss, comp_type) end if prop in links - linked_dss = find_component(data_dss, raw_dss[prop], comp_type) + linked_dss = get(get(data_dss, comp_type, Dict{String,Any}()), raw_dss[prop], Dict{String,Any}()) if isempty(linked_dss) Memento.warn(_LOGGER, "$comp_type.$(raw_dss["name"]): $prop=$(raw_dss[prop]) cannot be found") else From cd2e68225673e2e420c3a5c61e42516da63b0a71 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 08:42:36 -0700 Subject: [PATCH 034/224] DOC: remove function call example for internal functions --- src/io/dss_structs.jl | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index 47e07440c..f93b48a2f 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -16,8 +16,6 @@ _convert_to_meters = Dict{String,Any}("mi" => 1609.3, """ - _create_linecode(name; kwargs...) - Creates a Dict{String,Any} containing all of the properties of a Linecode. See OpenDSS documentation for valid fields and ways to specify the different properties. DEPRECIATED: Calculation all done inside of _create_line() due to Rg, @@ -113,8 +111,6 @@ end """ - _create_line(bus1, bus2, name; kwargs...) - Creates a Dict{String,Any} containing all of the properties for a Line. See OpenDSS documentation for valid fields and ways to specify the different properties. @@ -239,8 +235,6 @@ end """ - _create_load(bus1, name; kwargs...) - Creates a Dict{String,Any} containing all of the expected properties for a Load. See OpenDSS documentation for valid fields and ways to specify the different properties. @@ -325,8 +319,6 @@ end """ - _create_generator(bus1, name; kwargs...) - Creates a Dict{String,Any} containing all of the expected properties for a Generator. See OpenDSS documentation for valid fields and ways to specify the different properties. @@ -390,8 +382,6 @@ end """ - _create_capacitor(bus1, name, bus2=0; kwargs) - Creates a Dict{String,Any} containing all of the expected properties for a Capacitor. If `bus2` is not specified, the capacitor will be treated as a shunt. See OpenDSS documentation for valid fields and ways to specify the @@ -573,8 +563,6 @@ end """ - _create_vsource(bus1, name, bus2=0; kwargs...) - Creates a Dict{String,Any} containing all of the expected properties for a Voltage Source. If `bus2` is not specified, VSource will be treated like a generator. Mostly used as `sourcebus` which represents the circuit. See @@ -784,8 +772,6 @@ end """ - _create_transformer(name; kwargs...) - Creates a Dict{String,Any} containing all of the expected properties for a Transformer. See OpenDSS documentation for valid fields and ways to specify the different properties. @@ -909,8 +895,6 @@ end """ - _create_pvsystem(bus1, name; kwargs...) - Creates a Dict{String,Any} containing all of the expected properties for a PVSystem. See OpenDSS document https://github.com/tshort/OpenDSS/blob/master/Doc/OpenDSS%20PVSystem%20Model.doc @@ -988,8 +972,6 @@ end """ - _create_storage(bus1, name; kwargs...) - Creates a Dict{String,Any} containing all expected properties for a storage element. See OpenDSS documentation for valid fields and ways to specify the different properties. @@ -1045,8 +1027,6 @@ end """ - _create_loadshape(name; kwargs...) - Creates a Dict{String,Any} containing all expected properties for a LoadShape element. See OpenDSS documentation for valid fields and ways to specify different properties. From 0f830c150d50d40597f9b658108ad27afd5b31b1 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 08:43:03 -0700 Subject: [PATCH 035/224] ADD: loadshape parsing to eng model --- src/io/opendss_dm.jl | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index f47b7bccd..7a3399ac7 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -8,11 +8,7 @@ const _dss_supported_components = ["line", "linecode", "load", "generator", "cap const _dss_option_dtypes = Dict{String,Type}("defaultbasefreq" => Float64, "voltagebases" => Float64) -""" - _discover_buses(data_dss) - -Discovers all of the buses (not separately defined in OpenDSS), from "lines". -""" +"Discovers all of the buses (not separately defined in OpenDSS), from \"lines\"" function _discover_buses(data_dss::Dict{String,<:Any})::Array bus_names = [] buses = [] @@ -101,11 +97,33 @@ function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< end -""" - _dss2pmd_load!(data_eng, data_dss, import_all) +"" +function _dss2eng_loadshape!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) + for (name, dss_obj) in get(data_dss, "loadshape", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "loadshape") + defaults = _apply_ordered_properties(_create_loadshape(name; _to_sym_keys(dss_obj)...), dss_obj) -Adds PowerModels-style loads to `data_eng` from `data_dss`. -""" + eng_obj = Dict{String,Any}() + + eng_obj["hour"] = defaults["hour"] + eng_obj["pmult"] = defaults["pmult"] + eng_obj["qmult"] = defaults["qmult"] + eng_obj["use_actual"] = defaults["useactual"] + + if !haskey(data_eng, "loadshape") + data_eng["loadshape"] = Dict{String,Any}() + end + + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end + + data_eng["loadshape"][name] = eng_obj + end +end + + +"Adds loads to `data_eng` from `data_dss`" function _dss2eng_load!(data_eng::Dict, data_dss::Dict, import_all::Bool, ground_terminal::Int=4) for (name, dss_obj) in get(data_dss, "load", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "load") @@ -225,7 +243,6 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String t_terminals = [f_terminals[2:end]..., f_terminals[1]] end - # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) #TODO figure out for more than 3 phases vnom_ln = defaults["kv"] @@ -957,7 +974,7 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, #_dss2eng_line_reactor!(data_eng, data_dss, import_all) - # _dss2eng_loadshape!(data_eng, data_dss, import_all) + _dss2eng_loadshape!(data_eng, data_dss, import_all) _dss2eng_load!(data_eng, data_dss, import_all) _dss2eng_capacitor!(data_eng, data_dss, import_all) From 0c3158d45e134e11ba429edd2c6c17ca1c158f70 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 08:45:34 -0700 Subject: [PATCH 036/224] REF: move data model transformation to IO function --- src/io/common_dm.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl index 4c8d8fb39..ffcdfb63c 100644 --- a/src/io/common_dm.jl +++ b/src/io/common_dm.jl @@ -3,7 +3,7 @@ Parses the IOStream of a file into a Three-Phase PowerModels data structure. """ -function parse_file_dm(io::IO; import_all::Bool=false, filetype::AbstractString="json", bank_transformers::Bool=true) +function parse_file_dm(io::IO; data_model::String="engineering", import_all::Bool=false, filetype::AbstractString="json", bank_transformers::Bool=true) if filetype == "m" pmd_data = PowerModelsDistribution.parse_matlab(io) elseif filetype == "dss" @@ -15,6 +15,10 @@ function parse_file_dm(io::IO; import_all::Bool=false, filetype::AbstractString= Memento.error(_LOGGER, "only .m and .dss files are supported") end + if data_model == "mathematical" + transform_data_model!(pmd_data) + end + #correct_network_data!(pmd_data) return pmd_data @@ -22,15 +26,11 @@ end "" -function parse_file_dm(file::String; data_model::String="engineering", kwargs...) +function parse_file_dm(file::String; kwargs...) pmd_data = open(file) do io parse_file_dm(io; filetype=split(lowercase(file), '.')[end], kwargs...) end - if data_model == "mathematical" - transform_data_model!(pmd_data) - end - return pmd_data end From c96e653e46181e7dfad8986f483d90f3fa633ce0 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 08:57:21 -0700 Subject: [PATCH 037/224] ADD: line reactors --- src/io/opendss_dm.jl | 142 ++++++++++++------------------------------- 1 file changed, 40 insertions(+), 102 deletions(-) diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 7a3399ac7..fd23313e5 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -602,84 +602,66 @@ function _dss2eng_transformer!(data_eng::Dict, data_dss::Dict, import_all::Bool) end -""" - _dss2pmd_reactor!(data_eng, data_dss, import_all) - -Adds PowerModels-style branch components based on DSS reactors to `data_eng` from `data_dss` -""" -function _dss2eng_reactor!(data_eng::Dict, data_dss::Dict, import_all::Bool) - if !haskey(data_eng, "branch") - data_eng["branch"] = [] - end - - if haskey(data_dss, "reactor") - Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating like line") - for (name, reactor) in data_dss["reactor"] - if haskey(reactor, "bus2") - _apply_like!(reactor, data_dss, "reactor") - defaults = _apply_ordered_properties(_create_reactor(reactor["bus1"], reactor["name"], reactor["bus2"]; _to_sym_keys(reactor)...), reactor) +"Adds line reactors to `data_eng` from `data_dss`" +function _dss2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + for (name, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) + if haskey(dss_obj, "bus2") + Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating reactor.$name like line") + _apply_like!(dss_obj, data_dss, "reactor") + defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], name, dss_obj["bus2"]; _to_sym_keys(dss_obj)...), dss_obj) - eng_obj = Dict{String,Any}() + eng_obj = Dict{String,Any}() - nconductors = data_eng["conductors"] + nphases = defaults["phases"] + eng_obj["phases"] = nphases - f_bus, nodes = _parse_busname(defaults["bus1"]) - t_bus = _parse_busname(defaults["bus2"])[1] + eng_obj["f_bus"] = _parse_busname(defaults["bus1"])[1] + eng_obj["t_bus"] = _parse_busname(defaults["bus2"])[1] - eng_obj["name"] = name - eng_obj["f_bus"] = find_bus(f_bus, data_eng) - eng_obj["t_bus"] = find_bus(t_bus, data_eng) + eng_obj["br_r"] = diagm(0 => fill(0.2, nphases)) + eng_obj["br_x"] = zeros(nphases, nphases) - eng_obj["br_r"] = _PMs.MultiConductorMatrix(_parse_matrix(diagm(0 => fill(0.2, nconductors)), nodes, nconductors)) - eng_obj["br_x"] = _PMs.MultiConductorMatrix(_parse_matrix(zeros(nconductors, nconductors), nodes, nconductors)) + eng_obj["g_fr"] = fill(0.0, nphases) + eng_obj["g_to"] = fill(0.0, nphases) + eng_obj["b_fr"] = fill(0.0, nphases) + eng_obj["b_to"] = fill(0.0, nphases) - eng_obj["g_fr"] = _parse_array(0.0, nodes, nconductors) - eng_obj["g_to"] = _parse_array(0.0, nodes, nconductors) - eng_obj["b_fr"] = _parse_array(0.0, nodes, nconductors) - eng_obj["b_to"] = _parse_array(0.0, nodes, nconductors) - - for key in ["g_fr", "g_to", "b_fr", "b_to"] - eng_obj[key] = _PMs.MultiConductorMatrix(LinearAlgebra.diagm(0=>eng_obj[key].values)) - end - - eng_obj["c_rating_a"] = _parse_array(defaults["normamps"], nodes, nconductors) - eng_obj["c_rating_b"] = _parse_array(defaults["emergamps"], nodes, nconductors) - eng_obj["c_rating_c"] = _parse_array(defaults["emergamps"], nodes, nconductors) + for key in ["g_fr", "g_to", "b_fr", "b_to"] + eng_obj[key] = LinearAlgebra.diagm(0=>eng_obj[key]) + end - eng_obj["tap"] = _parse_array(1.0, nodes, nconductors, NaN) - eng_obj["shift"] = _parse_array(0.0, nodes, nconductors) + eng_obj["c_rating_a"] = defaults["normamps"] + eng_obj["c_rating_b"] = defaults["emergamps"] + eng_obj["c_rating_c"] = defaults["emergamps"] - eng_obj["br_status"] = convert(Int, defaults["enabled"]) + eng_obj["tap"] = fill(1.0, nphases) + eng_obj["shift"] = fill(0.0, nphases) - eng_obj["angmin"] = _parse_array(-60.0, nodes, nconductors, -60.0) - eng_obj["angmax"] = _parse_array( 60.0, nodes, nconductors, 60.0) + eng_obj["br_status"] = convert(Int, defaults["enabled"]) - eng_obj["transformer"] = true + eng_obj["angmin"] = fill(-60.0, nphases) + eng_obj["angmax"] = fill( 60.0, nphases) - eng_obj["index"] = length(data_eng["branch"]) + 1 + eng_obj["transformer"] = true - nodes = .+([_parse_busname(defaults[n])[2] for n in ["bus1", "bus2"]]...) - eng_obj["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - eng_obj["source_id"] = "reactor.$(name)" + eng_obj["source_id"] = "reactor.$(name)" - if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) - end + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end - push!(data_eng["branch"], eng_obj) + if !haskey(data_eng, "line_reactor") + data_eng["line_reactor"] = Dict{String,Any}() end + + data_eng["line_reactor"][name] = eng_obj end end end -""" - _dss2pmd_pvsystem!(data_eng, data_dss) - -Adds PowerModels-style pvsystems to `data_eng` from `data_dss`. -""" +"Adds PowerModels-style pvsystems to `data_eng` from `data_dss`" function _dss2eng_pvsystem!(data_eng::Dict, data_dss::Dict, import_all::Bool) - for (name, dss_obj) in get(data_dss, "pvsystem", Dict{String,Any}()) Memento.warn(_LOGGER, "Converting PVSystem \"$(dss_obj["name"])\" into generator with limits determined by OpenDSS property 'kVA'") @@ -788,49 +770,6 @@ function _push_dict_ret_key!(dict::Dict{String, Any}, v::Dict{String, Any}; assu end -""" - _where_is_comp(data, comp_id) - -Finds existing component of id `comp_id` in array of `data` and returns index. -Assumes all components in `data` are unique. -""" -function _where_is_comp(data::Array, comp_id::AbstractString)::Int - for (i, e) in enumerate(data) - if e["name"] == comp_id - return i - end - end - return 0 -end - - -""" - _correct_duplicate_components!(data_dss) - -Finds duplicate components in `data_dss` and merges up, meaning that older -data (lower indices) is always overwritten by newer data (higher indices). -""" -function _correct_duplicate_components!(data_dss::Dict) - out = Dict{String,Array}() - for (k, v) in data_dss - if !(k in _exclude_duplicate_check) - out[k] = [] - for comp in v - if isa(comp, Dict) - idx = _where_is_comp(out[k], comp["name"]) - if idx > 0 - merge!(out[k][idx], comp) - else - push!(out[k], comp) - end - end - end - end - end - merge!(data_dss, out) -end - - "Creates a virtual branch between the `virtual_sourcebus` and `sourcebus` with the impedance given by `circuit`" function _create_sourcebus_vbranch_dm!(data_eng::Dict, circuit::Dict) #TODO convert to pu @@ -972,7 +911,7 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, # _dss2eng_xfrmcode!(data_eng, data_dss, import_all) _dss2eng_transformer!(data_eng, data_dss, import_all) - #_dss2eng_line_reactor!(data_eng, data_dss, import_all) + _dss2eng_line_reactor!(data_eng, data_dss, import_all) _dss2eng_loadshape!(data_eng, data_dss, import_all) _dss2eng_load!(data_eng, data_dss, import_all) @@ -992,7 +931,6 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, _discover_terminals!(data_eng) - return data_eng end From 3b2b2c73d16bf79406bebd41cb16322e12213a50 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 09:08:34 -0700 Subject: [PATCH 038/224] DOC: docstring updates --- src/io/opendss_dm.jl | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index fd23313e5..f8d68b754 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -57,11 +57,7 @@ function _dss2eng_buscoords!(data_eng::Dict{String,<:Any}, data_dss::Dict{String end -""" - _dss2pmd_bus!(data_eng, data_dss) - -Adds PowerModels-style buses to `data_eng` from `data_dss`. -""" +"Adds nodes as buses to `data_eng` from `data_dss`" function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) buses = _discover_buses(data_dss) for (n, (bus, nodes)) in enumerate(buses) @@ -73,7 +69,7 @@ function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any end -"" +"Adds sourcebus as a voltage source to `data_eng` from `data_dss`" function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) # create virtual sourcebus circuit = _create_vsource(get(data_dss["circuit"], "bus1", "sourcebus"), data_dss["circuit"]["name"]; _to_sym_keys(data_dss["circuit"])...) @@ -92,12 +88,12 @@ function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String end -"" +"Adds voltage sources to `data_eng` from `data_dss`" function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) end -"" +"Adds loadshapes to `data_eng` from `data_dss`" function _dss2eng_loadshape!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) for (name, dss_obj) in get(data_dss, "loadshape", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "loadshape") @@ -362,6 +358,7 @@ function _calc_ground_shunt_admittance_matrix(cnds, Y, ground) end +"" function _rm_floating_cnd(cnds, Y, f) P = setdiff(cnds, f) f_inds = _get_idxs(cnds, [f]) @@ -417,6 +414,7 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String end +"Adds lines to `data_eng` from `data_dss`" function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "linecode", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "linecode") @@ -443,11 +441,7 @@ function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, end -""" - _dss2pmd_line!(data_eng, data_dss, import_all) - -Adds PowerModels-style lines to `data_eng` from `data_dss`. -""" +"Adds lines to `data_eng` from `data_dss`" function _dss2eng_line!(data_eng::Dict, data_dss::Dict, import_all::Bool) for (name, dss_obj) in get(data_dss, "line", Dict()) _apply_like!(dss_obj, data_dss, "line") @@ -519,11 +513,7 @@ function _dss2eng_line!(data_eng::Dict, data_dss::Dict, import_all::Bool) end -""" - _dss2pmd_transformer!(data_eng, data_dss, import_all) - -Adds PMD-style transformers to `data_eng` from `data_dss`. -""" +"Adds transformers to `data_eng` from `data_dss`" function _dss2eng_transformer!(data_eng::Dict, data_dss::Dict, import_all::Bool) if !haskey(data_eng, "transformer_nw") data_eng["transformer_nw"] = Dict{String,Any}() @@ -660,7 +650,7 @@ function _dss2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{Str end -"Adds PowerModels-style pvsystems to `data_eng` from `data_dss`" +"Adds pvsystems to `data_eng` from `data_dss`" function _dss2eng_pvsystem!(data_eng::Dict, data_dss::Dict, import_all::Bool) for (name, dss_obj) in get(data_dss, "pvsystem", Dict{String,Any}()) Memento.warn(_LOGGER, "Converting PVSystem \"$(dss_obj["name"])\" into generator with limits determined by OpenDSS property 'kVA'") @@ -702,11 +692,7 @@ function _dss2eng_pvsystem!(data_eng::Dict, data_dss::Dict, import_all::Bool) end -""" - _dss2pmd_storage!(data_eng, data_dss, import_all) - -Adds PowerModels-style storage to `data_eng` from `data_dss` -""" +"Adds storage to `data_eng` from `data_dss`" function _dss2eng_storage!(data_eng::Dict, data_dss::Dict, import_all::Bool) for (name, dss_obj) in get(data_dss, "storage", Dict{String,Any}()) From 48d654548fd1eb91af5ea699c0a03e3d41e0b61b Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Thu, 27 Feb 2020 18:58:00 +0100 Subject: [PATCH 039/224] trans bugs --- src/core/data.jl | 24 ++++++ src/core/data_model_mapping.jl | 6 +- src/io/opendss_dm.jl | 138 +++++++++++---------------------- test/transformer.jl | 103 ++++++++++++------------ 4 files changed, 128 insertions(+), 143 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index 55d47a783..b68bd1347 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -661,3 +661,27 @@ function set_upper_bound(x::JuMP.VariableRef, bound) JuMP.set_upper_bound(x, bound) end end + + +"" +function sol_polar_voltage!(pm::_PMs.AbstractPowerModel, solution::Dict) + if haskey(solution, "nw") + nws_data = solution["nw"] + else + nws_data = Dict("0" => solution) + end + + for (n, nw_data) in nws_data + if haskey(nw_data, "bus") + for (i,bus) in nw_data["bus"] + if haskey(bus, "vr") && haskey(bus, "vi") + bus["vm"] = sqrt.(bus["vr"].^2 + bus["vi"].^2) + bus["va"] = _wrap_to_pi(atan.(bus["vi"], bus["vr"])) + + delete!(bus, "vr") + delete!(bus, "vi") + end + end + end + end +end diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index 4e8ed2cc8..a43446d2e 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -223,11 +223,11 @@ function _decompose_transformer_nw!(data_model) # rs is specified with respect to each winding r_s = trans["rs"].*zbase - g_sh = (trans["noloadloss"]*snom[1]/3)/vnom[1]^2 - b_sh = (trans["imag"]*snom[1]/3)/vnom[1]^2 + g_sh = (trans["noloadloss"]*snom[1])/vnom[1]^2 + b_sh = -(trans["imag"]*snom[1])/vnom[1]^2 # data is measured externally, but we now refer it to the internal side - ratios = vnom/1E3 + ratios = vnom/data_model["settings"]["v_var_scalar"] x_sc = x_sc./ratios[1]^2 r_s = r_s./ratios.^2 g_sh = g_sh*ratios[1]^2 diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 1d34e0f2e..b8b3ca965 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -629,6 +629,18 @@ function _dss2pmd_line_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bool) end +function _barrel_roll(x::Vector, shift) + N = length(x) + if shift < 0 + shift = shift + ceil(Int, shift/N)*N + end + + shift = mod(shift, N) + + return x[[(i-1+shift)%N+1 for i in 1:N]] +end + + """ _dss2pmd_transformer!(pmd_data, dss_data, import_all) @@ -674,12 +686,15 @@ function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bo for w in 1:nrw transDict["bus"][w] = _parse_busname(defaults["buses"][w])[1] - conn = dyz_map[defaults["conns"][w]] - transDict["configuration"][w] = conn + conf = dyz_map[defaults["conns"][w]] + transDict["configuration"][w] = conf - terminals_default = conn=="wye" ? [1:nphases..., 0] : collect(1:nphases) - terminals_w = _get_conductors_ordered_dm(defaults["buses"][w], default=terminals_default) + terminals_default = conf=="wye" ? [1:nphases..., 0] : collect(1:nphases) + + # append ground if connections one too short + terminals_w = _get_conductors_ordered_dm(defaults["buses"][w], default=terminals_default, pad_ground=(conf=="wye")) transDict["connections"][w] = terminals_w + if 0 in terminals_w bus = transDict["bus"][w] if !haskey(pmd_data["bus"][bus], "awaiting_ground") @@ -689,6 +704,24 @@ function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bo end transDict["polarity"][w] = 1 transDict["tm"][w] = fill(defaults["taps"][w], nphases) + + if w>1 + prim_conf = transDict["configuration"][1] + if defaults["leadlag"] in ["ansi", "lag"] + if prim_conf=="delta" && conf=="wye" + transDict["polarity"][w] = -1 + transDict["connections"][w] = [_barrel_roll(transDict["connections"][w][1:end-1], 1)..., transDict["connections"][w][end]] + end + else # hence defaults["leadlag"] in ["euro", "lead"] + if prim_conf=="wye" && conf=="delta" + @show defaults["leadlag"], prim_conf, conf + transDict["polarity"][w] = -1 + transDict["connections"][w] = _barrel_roll(transDict["connections"][w], -1) + end + + end + end + @show transDict["connections"] end #transDict["source_id"] = "transformer.$(defaults["name"])" @@ -706,9 +739,11 @@ function _dss2pmd_transformer_dm!(pmd_data::Dict, dss_data::Dict, import_all::Bo transDict["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 end - add_virtual!(pmd_data, "transformer_nw", create_transformer_nw(; + trans = create_transformer_nw(; Dict(Symbol.(keys(transDict)).=>values(transDict))... - )) + ) + + add_virtual!(pmd_data, "transformer_nw", trans) end end @@ -950,91 +985,6 @@ function _create_sourcebus_vbranch_dm!(pmd_data::Dict, circuit::Dict) end -"Combines transformers with 'bank' keyword into a single transformer" -function _bank_transformers!(pmd_data::Dict) - transformer_names = Dict(trans["name"] => n for (n, trans) in get(pmd_data, "transformer_comp", Dict())) - bankable_transformers = [trans for trans in values(get(pmd_data, "transformer_comp", Dict())) if haskey(trans, "bank")] - banked_transformers = Dict() - for transformer in bankable_transformers - bank = transformer["bank"] - - if !(bank in keys(banked_transformers)) - n = length(pmd_data["transformer_comp"])+length(banked_transformers)+1 - - banked_transformers[bank] = deepcopy(transformer) - banked_transformers[bank]["name"] = deepcopy(transformer["bank"]) - banked_transformers[bank]["source_id"] = "transformer.$(transformer["bank"])" - banked_transformers[bank]["index"] = n - # set impedances / admittances to zero; only the specified phases should be non-zero - for key in ["rs", "xs", "bsh", "gsh"] - inds = key=="xs" ? keys(banked_transformers[bank][key]) : 1:length(banked_transformers[bank][key]) - for w in inds - banked_transformers[bank][key][w] *= 0 - end - end - delete!(banked_transformers[bank], "bank") - end - - banked_transformer = banked_transformers[bank] - for phase in transformer["active_phases"] - push!(banked_transformer["active_phases"], phase) - for (k, v) in banked_transformer - if isa(v, _PMs.MultiConductorVector) - banked_transformer[k][phase] = deepcopy(transformer[k][phase]) - elseif isa(v, _PMs.MultiConductorMatrix) - banked_transformer[k][phase, :] .= deepcopy(transformer[k][phase, :]) - elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorVector - # most properties are arrays (indexed over the windings) - for w in 1:length(v) - banked_transformer[k][w][phase] = deepcopy(transformer[k][w][phase]) - end - elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorMatrix - # most properties are arrays (indexed over the windings) - for w in 1:length(v) - banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) - end - elseif k=="xs" - # xs is a Dictionary indexed over pairs of windings - for w in keys(v) - banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) - end - end - end - end - end - - for transformer in bankable_transformers - delete!(pmd_data["transformer_comp"], transformer_names[transformer["name"]]) - end - - for transformer in values(banked_transformers) - pmd_data["transformer_comp"]["$(transformer["index"])"] = deepcopy(transformer) - end -end - - -""" - parse_options(options) - -Parses options defined with the `set` command in OpenDSS. -""" -function parse_options(options) - out = Dict{String,Any}() - if haskey(options, "voltagebases") - out["voltagebases"] = _parse_array(Float64, options["voltagebases"]) - end - - if !haskey(options, "defaultbasefreq") - Memento.warn(_LOGGER, "defaultbasefreq is not defined, default for circuit set to 60 Hz") - out["defaultbasefreq"] = 60.0 - else - out["defaultbasefreq"] = parse(Float64, options["defaultbasefreq"]) - end - - return out -end - - "Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" function parse_opendss_dm(dss_data::Dict; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, bank_transformers::Bool=true)::Dict pmd_data = create_data_model() @@ -1219,7 +1169,7 @@ end Returns an ordered list of defined conductors. If ground=false, will omit any `0` """ -function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_length=true)::Array +function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_length=true, pad_ground=false)::Array parts = split(busname, '.'; limit=2) ret = [] if length(parts)==2 @@ -1229,6 +1179,10 @@ function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_l return default end + if pad_ground && length(ret)==length(default)-1 + ret = [ret..., 0] + end + if check_length && length(default)!=length(ret) Memento.error("An incorrect number of nodes was specified; |$(parts[2])|!=$(length(default)).") end diff --git a/test/transformer.jl b/test/transformer.jl index 0a094d638..7d0c0d52f 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -5,86 +5,92 @@ @testset "test transformer acp pf" begin @testset "2w transformer acp pf yy" begin file = "../test/data/opendss/ut_trans_2w_yy.dss" - pmd_data = PMD.parse_file(file) - sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(vm(sol, pmd_data, "3")-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 - @test norm(va(sol, pmd_data, "3")-[-0.1, -120.4, 119.8], Inf) <= 0.1 + pmd_data = PMD.parse_file_dm(file) + sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) + solution_identify!(sol["solution"], pmd_data) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 end @testset "2w transformer acp pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" - pmd_data = PMD.parse_file(file) - sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(vm(sol, pmd_data, "3")-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 - @test norm(va(sol, pmd_data, "3")-[29.8, -90.4, 149.8], Inf) <= 0.1 + pmd_data = PMD.parse_file_dm(file) + sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) + solution_identify!(sol["solution"], pmd_data) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([29.8, -90.4, 149.8]), Inf) <= 0.1 end @testset "2w transformer acp pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file_dm(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(vm(sol, pmd_data, "3")-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 - @test norm(va(sol, pmd_data, "3")-[-30.0, -150.4, 89.8], Inf) <= 0.1 + solution_identify!(sol["solution"], pmd_data) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 + @test norm(PMD._wrap_to_pi(sol["solution"]["bus"]["3"]["va"])-deg2rad.([-30.0, -150.4, 89.8]), Inf) <= 0.1 end end @testset "test transformer ivr pf" begin @testset "2w transformer ivr pf yy" begin file = "../test/data/opendss/ut_trans_2w_yy.dss" - pmd_data = PMD.parse_file(file) - sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver) - @test norm(calc_vm_acr(sol, pmd_data, "3")-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 - @test norm(calc_va_acr(sol, pmd_data, "3")-[-0.1, -120.4, 119.8], Inf) <= 0.1 + pmd_data = PMD.parse_file_dm(file) + sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) + solution_identify!(sol["solution"], pmd_data) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" - pmd_data = PMD.parse_file(file) - sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver) - @test norm(calc_vm_acr(sol, pmd_data, "3")-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 - @test norm(calc_va_acr(sol, pmd_data, "3")-[29.8, -90.4, 149.8], Inf) <= 0.1 + pmd_data = PMD.parse_file_dm(file) + sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) + solution_identify!(sol["solution"], pmd_data) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([29.8, -90.4, 149.8]), Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" - pmd_data = PMD.parse_file(file) - sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver) - @test norm(calc_vm_acr(sol, pmd_data, "3")-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 - @test norm(calc_va_acr(sol, pmd_data, "3")-[-30.0, -150.4, 89.8], Inf) <= 0.1 + pmd_data = PMD.parse_file_dm(file) + sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) + solution_identify!(sol["solution"], pmd_data) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([-30.0, -150.4, 89.8]), Inf) <= 0.1 end end - @testset "2w transformer ac pf yy - banked transformers" begin - file = "../test/data/opendss/ut_trans_2w_yy_bank.dss" - pmd1 = PMD.parse_file(file) - pmd2 = PMD.parse_file(file; bank_transformers=false) - result1 = run_ac_mc_pf(pmd1, ipopt_solver) - result2 = run_ac_mc_pf(pmd2, ipopt_solver) - - @test result1["termination_status"] == PMs.LOCALLY_SOLVED - @test result2["termination_status"] == PMs.LOCALLY_SOLVED - @test result1["solution"]["bus"] == result2["solution"]["bus"] - @test result1["solution"]["gen"] == result2["solution"]["gen"] - - dss = PMD.parse_dss(file) - PMD.parse_dss_with_dtypes!(dss, ["line", "load", "transformer"]) - trans = PMD._create_transformer(dss["transformer"][1]["name"]; PMD._to_sym_keys(dss["transformer"][1])...) - @test all(trans["%rs"] .== [1.0, 2.0]) - end + # @testset "2w transformer ac pf yy - banked transformers" begin + # file = "../test/data/opendss/ut_trans_2w_yy_bank.dss" + # pmd1 = PMD.parse_file_dm(file) + # pmd2 = PMD.parse_file_dm(file; bank_transformers=false) + # result1 = run_ac_mc_pf(pmd1, ipopt_solver) + # result2 = run_ac_mc_pf(pmd2, ipopt_solver) + # + # @test result1["termination_status"] == PMs.LOCALLY_SOLVED + # @test result2["termination_status"] == PMs.LOCALLY_SOLVED + # @test result1["solution"]["bus"] == result2["solution"]["bus"] + # @test result1["solution"]["gen"] == result2["solution"]["gen"] + # + # dss = PMD.parse_dss(file) + # PMD.parse_dss_with_dtypes!(dss, ["line", "load", "transformer"]) + # trans = PMD._create_transformer(dss["transformer"][1]["name"]; PMD._to_sym_keys(dss["transformer"][1])...) + # @test all(trans["%rs"] .== [1.0, 2.0]) + # end @testset "three winding transformer pf" begin @testset "3w transformer ac pf dyy - all non-zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_1.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file_dm(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(vm(sol, pmd_data, "3")-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 @test norm(va(sol, pmd_data, "3")-[30.1, -90.7, 151.2], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - some non-zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_2.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file_dm(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) #@test isapprox(vm(sol, pmd_data, "3"), [0.93876, 0.90227, 0.90454], atol=1E-4) @test norm(vm(sol, pmd_data, "3")-[0.93876, 0.90227, 0.90454], Inf) <= 1.5E-5 @@ -93,15 +99,16 @@ @testset "3w transformer ac pf dyy - all zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_3.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file_dm(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(vm(sol, pmd_data, "3")-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 - @test norm(va(sol, pmd_data, "3")-[30.6, -90.0, 151.9], Inf) <= 0.1 + solution_identify!(sol["solution"], pmd_data) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-rad2deg.([30.6, -90.0, 151.9]), Inf) <= 0.1 end @testset "3w transformer ac pf dyy - %loadloss=0" begin file = "../test/data/opendss/ut_trans_3w_dyy_3_loadloss.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file_dm(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) @test haskey(sol["solution"]["bus"], "10") @test norm(vm(sol, pmd_data, "3")-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 @@ -112,7 +119,7 @@ @testset "oltc tests" begin @testset "2w transformer acp opf_oltc yy" begin file = "../test/data/opendss/ut_trans_2w_yy_oltc.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file_dm(file) # free the taps pmd_data["transformer"]["1"]["fixed"] = zeros(Bool, 3) pmd_data["transformer"]["2"]["fixed"] = zeros(Bool, 3) From 661ee3f570a0a7c0f3c0de9614d813613a7563ad Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 12:05:22 -0700 Subject: [PATCH 040/224] FIX: transformer parsing TODO: _bank_transformers! is partially fixed, need to do the actual combination of transformers still --- src/io/opendss_dm.jl | 159 +++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 81 deletions(-) diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 13155b85d..2b654918f 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -64,7 +64,7 @@ function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any @assert(!(length(bus)>=8 && bus[1:8]=="_virtual"), "Bus $bus: identifiers should not start with _virtual.") - add_bus!(data_eng, id=bus, status=1, bus_type=1) + add_bus!(data_eng, bus; status=1, bus_type=1) end end @@ -84,7 +84,7 @@ function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String vm = fill(vm_pu, 3)*vnom va = _wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases]) - add_voltage_source!(data_eng, id="source", bus="sourcebus", connections=collect(1:phases), vm=vm, va=va, rs=circuit["rmatrix"], xs=circuit["xmatrix"]) + add_voltage_source!(data_eng, "sourcebus"; bus="sourcebus", connections=collect(1:phases), vm=vm, va=va, rs=circuit["rmatrix"], xs=circuit["xmatrix"]) end @@ -120,7 +120,7 @@ end "Adds loads to `data_eng` from `data_dss`" -function _dss2eng_load!(data_eng::Dict, data_dss::Dict, import_all::Bool, ground_terminal::Int=4) +function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, ground_terminal::Int=4) for (name, dss_obj) in get(data_dss, "load", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "load") defaults = _apply_ordered_properties(_create_load(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) @@ -179,7 +179,7 @@ function _dss2eng_load!(data_eng::Dict, data_dss::Dict, import_all::Bool, ground # now we can create the load; if you do not have the correct model, # pd/qd fields will be populated by default (should not happen for constant current/impedance) - eng_obj = add_load!(data_eng, id=name, model=model, connections=connections, bus=bus, configuration=conf) + eng_obj = create_load(model=model, connections=connections, bus=bus, configuration=conf) # if the ground is used directly, register load if 0 in connections @@ -209,6 +209,12 @@ function _dss2eng_load!(data_eng::Dict, data_dss::Dict, import_all::Bool, ground if import_all _import_all!(eng_obj, defaults, dss_obj["prop_order"]) end + + if !haskey(data_eng, "load") + data_eng["load"] = Dict{String,Any}() + end + + data_eng["load"][name] = eng_obj end end @@ -255,7 +261,7 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String # if one terminal is ground (0), reduce shunt addmittance matrix terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) - eng_obj = add_shunt!(data_eng, id=name, status=convert(Int, defaults["enabled"]), bus=bus_name, connections=terminals, g_sh=fill(0.0, size(B)...), b_sh=B) + eng_obj = create_shunt(status=convert(Int, defaults["enabled"]), bus=bus_name, connections=terminals, g_sh=fill(0.0, size(B)...), b_sh=B) if import_all _import_all!(eng_obj, defaults, dss_obj["prop_order"]) @@ -314,7 +320,7 @@ end Given a vector and a list of elements to find, this method will return a list of the positions of the elements in that vector. """ -function _get_idxs(vec::Array{<:Any, 1}, els::Array{<:Any, 1}) +function _get_idxs(vec::Array{<:Any,1}, els::Array{<:Any,1}) ret = Array{Int, 1}(undef, length(els)) for (i,f) in enumerate(els) for (j,l) in enumerate(vec) @@ -333,6 +339,7 @@ conductors 't_cnds', this method will return a list of conductors 'cnd' and a matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. """ function calc_shunt(f_cnds, t_cnds, y) + # TODO add types cnds = unique([f_cnds..., t_cnds...]) e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) @@ -347,6 +354,7 @@ method will calculate the reduced addmittance matrix if terminal 'ground' is grounded. """ function _calc_ground_shunt_admittance_matrix(cnds, Y, ground) + # TODO add types if ground in cnds cndsr = setdiff(cnds, ground) cndsr_inds = _get_idxs(cnds, cndsr) @@ -360,6 +368,7 @@ end "" function _rm_floating_cnd(cnds, Y, f) + # TODO add types P = setdiff(cnds, f) f_inds = _get_idxs(cnds, [f]) P_inds = _get_idxs(cnds, P) @@ -442,7 +451,7 @@ end "Adds lines to `data_eng` from `data_dss`" -function _dss2eng_line!(data_eng::Dict, data_dss::Dict, import_all::Bool) +function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "line", Dict()) _apply_like!(dss_obj, data_dss, "line") @@ -513,6 +522,7 @@ function _dss2eng_line!(data_eng::Dict, data_dss::Dict, import_all::Bool) end +"" function _barrel_roll(x::Vector, shift) N = length(x) if shift < 0 @@ -526,9 +536,9 @@ end "Adds transformers to `data_eng` from `data_dss`" -function _dss2eng_transformer!(data_eng::Dict, data_dss::Dict, import_all::Bool) - if !haskey(data_eng, "transformer_nw") - data_eng["transformer_nw"] = Dict{String,Any}() +function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + if !haskey(data_eng, "transformer") + data_eng["transformer"] = Dict{String,Any}() end for (name, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) @@ -562,6 +572,8 @@ function _dss2eng_transformer!(data_eng::Dict, data_dss::Dict, import_all::Bool) eng_obj["configuration"] = Array{String, 1}(undef, nrw) eng_obj["polarity"] = Array{Int, 1}(undef, nrw) + eng_obj["nphases"] = defaults["phases"] + for w in 1:nrw eng_obj["bus"][w] = _parse_busname(defaults["buses"][w])[1] @@ -590,19 +602,20 @@ function _dss2eng_transformer!(data_eng::Dict, data_dss::Dict, import_all::Bool) if defaults["leadlag"] in ["ansi", "lag"] if prim_conf=="delta" && conf=="wye" eng_obj["polarity"][w] = -1 - eng_obj["connections"][w] = [_barrel_roll(transDict["connections"][w][1:end-1], 1)..., eng_obj["connections"][w][end]] + eng_obj["connections"][w] = [_barrel_roll(eng_obj["connections"][w][1:end-1], 1)..., eng_obj["connections"][w][end]] end else # hence defaults["leadlag"] in ["euro", "lead"] if prim_conf=="wye" && conf=="delta" eng_obj["polarity"][w] = -1 - eng_obj["connections"][w] = _barrel_roll(transDict["connections"][w], -1) + eng_obj["connections"][w] = _barrel_roll(eng_obj["connections"][w], -1) end end end end - #eng_obj["source_id"] = "transformer.$(name)" + eng_obj["source_id"] = "transformer.$(name)" + if !isempty(defaults["bank"]) eng_obj["bank"] = defaults["bank"] end @@ -617,9 +630,18 @@ function _dss2eng_transformer!(data_eng::Dict, data_dss::Dict, import_all::Bool) eng_obj["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 end - add_virtual!(data_eng, "transformer_nw", create_transformer_nw(; - Dict(Symbol.(keys(eng_obj)).=>values(eng_obj))... - )) + eng_obj = create_transformer_nw(; Dict(Symbol.(keys(eng_obj)).=>values(eng_obj))...) + + if !haskey(data_eng, "transformer") + data_eng["transformer"] = Dict{String,Any}() + end + + if import_all + _import_all!(data_eng, defaults, dss_obj["prop_order"]) + end + + data_eng["transformer"][name] = eng_obj + # add_virtual!(data_eng, "transformer", eng_obj) end end @@ -683,7 +705,7 @@ end "Adds pvsystems to `data_eng` from `data_dss`" -function _dss2eng_pvsystem!(data_eng::Dict, data_dss::Dict, import_all::Bool) +function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "pvsystem", Dict{String,Any}()) Memento.warn(_LOGGER, "Converting PVSystem \"$(dss_obj["name"])\" into generator with limits determined by OpenDSS property 'kVA'") @@ -725,7 +747,7 @@ end "Adds storage to `data_eng` from `data_dss`" -function _dss2eng_storage!(data_eng::Dict, data_dss::Dict, import_all::Bool) +function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "storage", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "storage") @@ -773,7 +795,7 @@ end "This function appends a component to a component dictionary of a pmd data model" -function _push_dict_ret_key!(dict::Dict{String, Any}, v::Dict{String, Any}; assume_no_gaps=false) +function _push_dict_ret_key!(dict::Dict{String,<:Any}, v::Dict{String,<:Any}; assume_no_gaps::Bool=false) if isempty(dict) k = 1 elseif assume_no_gaps @@ -789,14 +811,14 @@ end "Creates a virtual branch between the `virtual_sourcebus` and `sourcebus` with the impedance given by `circuit`" -function _create_sourcebus_vbranch_dm!(data_eng::Dict, circuit::Dict) +function _create_sourcebus_vbranch_dm!(data_eng::Dict{String,<:Any}, circuit::Dict{String,<:Any}) #TODO convert to pu rs = circuit["rmatrix"] xs = circuit["xmatrix"] N = size(rs)[1] - add_line!(data_eng, id="_virtual_source_imp", + create_line(data_eng, id="_virtual_source_imp", f_bus="_virtual_sourcebus", t_bus="sourcebus", f_connections=collect(1:N), t_connections=collect(1:N), rs=rs, xs=xs @@ -806,64 +828,38 @@ end "Combines transformers with 'bank' keyword into a single transformer" -function _bank_transformers!(data_eng::Dict) - transformer_names = Dict(trans["name"] => n for (n, trans) in get(data_eng, "transformer_comp", Dict())) - bankable_transformers = [trans for trans in values(get(data_eng, "transformer_comp", Dict())) if haskey(trans, "bank")] - banked_transformers = Dict() - for transformer in bankable_transformers - bank = transformer["bank"] - - if !(bank in keys(banked_transformers)) - n = length(data_eng["transformer_comp"])+length(banked_transformers)+1 - - banked_transformers[bank] = deepcopy(transformer) - banked_transformers[bank]["name"] = deepcopy(transformer["bank"]) - banked_transformers[bank]["source_id"] = "transformer.$(transformer["bank"])" - banked_transformers[bank]["index"] = n - # set impedances / admittances to zero; only the specified phases should be non-zero - for key in ["rs", "xs", "bsh", "gsh"] - inds = key=="xs" ? keys(banked_transformers[bank][key]) : 1:length(banked_transformers[bank][key]) - for w in inds - banked_transformers[bank][key][w] *= 0 - end +function _bank_transformers!(data_eng::Dict{String,<:Any}) + banks = Dict{String,Array{String,1}}() + for (name, transformer) in get(data_eng, "transformer", Dict{String,Any}()) + if haskey(transformer, "bank") + if !haskey(banks, transformer["bank"]) + banks[transformer["bank"]] = Array{String,1}([name]) + else + push!(banks[transformer["bank"]], name) end - delete!(banked_transformers[bank], "bank") end + end - banked_transformer = banked_transformers[bank] - for phase in transformer["active_phases"] - push!(banked_transformer["active_phases"], phase) - for (k, v) in banked_transformer - if isa(v, _PMs.MultiConductorVector) - banked_transformer[k][phase] = deepcopy(transformer[k][phase]) - elseif isa(v, _PMs.MultiConductorMatrix) - banked_transformer[k][phase, :] .= deepcopy(transformer[k][phase, :]) - elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorVector - # most properties are arrays (indexed over the windings) - for w in 1:length(v) - banked_transformer[k][w][phase] = deepcopy(transformer[k][w][phase]) - end - elseif isa(v, Array) && eltype(v) <: _PMs.MultiConductorMatrix - # most properties are arrays (indexed over the windings) - for w in 1:length(v) - banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) - end - elseif k=="xs" - # xs is a Dictionary indexed over pairs of windings - for w in keys(v) - banked_transformer[k][w][phase, :] .= deepcopy(transformer[k][w][phase, :]) - end - end - end + banked = Dict{String,Any}() + for (bank, names) in banks + transformers = [data_eng["transformer"][name] for name in names] + + total_phases = sum(Int[transformer["nphases"] for transformer in transformers]) + + # TODO + if !haskey(banked, bank) + banked[bank] = deepcopy(transformers[1]) end end - for transformer in bankable_transformers - delete!(data_eng["transformer_comp"], transformer_names[transformer["name"]]) - end + for (bank, names) in banks + if haskey(banked, bank) + for name in names + delete!(data_eng["transformer"], name) + end + end - for transformer in values(banked_transformers) - data_eng["transformer_comp"]["$(transformer["index"])"] = deepcopy(transformer) + data_eng["transformer"][bank] = banked[bank] end end @@ -873,7 +869,7 @@ end Parses options defined with the `set` command in OpenDSS. """ -function parse_options(options) +function parse_options(options::Dict{String,<:Any}) out = Dict{String,Any}() for (option, dtype) in _dss_option_dtypes @@ -955,7 +951,7 @@ end "" function _discover_terminals!(data_eng::Dict{String,<:Any}) - terminals = Dict{String, Set{Int}}([(bus["id"], Set{Int}()) for (_,bus) in data_eng["bus"]]) + terminals = Dict{String, Set{Int}}([(name, Set{Int}()) for (name,bus) in data_eng["bus"]]) for (_,dss_obj) in data_eng["line"] # ignore 0 terminal @@ -963,8 +959,8 @@ function _discover_terminals!(data_eng::Dict{String,<:Any}) push!(terminals[dss_obj["t_bus"]], setdiff(dss_obj["t_connections"], [0])...) end - if haskey(data_eng, "transformer_nw") - for (_,tr) in data_eng["transformer_nw"] + if haskey(data_eng, "transformer") + for (_,tr) in data_eng["transformer"] for w in 1:length(tr["bus"]) # ignore 0 terminal push!(terminals[tr["bus"][w]], setdiff(tr["connections"][w], [0])...) @@ -1015,21 +1011,21 @@ end "" -function _find_neutrals(data_eng) +function _find_neutrals(data_eng::Dict{String,<:Any}) vertices = [(id, t) for (id, bus) in data_eng["bus"] for t in bus["terminals"]] neutrals = [] edges = Set([((dss_obj["f_bus"], dss_obj["f_connections"][c]),(dss_obj["t_bus"], dss_obj["t_connections"][c])) for (id, dss_obj) in data_eng["line"] for c in 1:length(dss_obj["f_connections"])]) bus_neutrals = [(id,bus["neutral"]) for (id,bus) in data_eng["bus"] if haskey(bus, "neutral")] trans_neutrals = [] - for (_, tr) in data_eng["transformer_nw"] + for (_, tr) in data_eng["transformer"] for w in 1:length(tr["connections"]) if tr["configuration"][w] == "wye" push!(trans_neutrals, (tr["bus"][w], tr["connections"][w][end])) end end end - load_neutrals = [(dss_obj["bus"],dss_obj["connections"][end]) for (_,dss_obj) in data_eng["load"] if dss_obj["configuration"]=="wye"] + load_neutrals = [(dss_obj["bus"],dss_obj["connections"][end]) for (_,dss_obj) in get(data_eng, "load", Dict{String,Any}()) if dss_obj["configuration"]=="wye"] neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) stack = deepcopy(neutrals) @@ -1060,7 +1056,7 @@ end "Returns an ordered list of defined conductors. If ground=false, will omit any `0`" -function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_length=true, pad_ground=false)::Array +function _get_conductors_ordered_dm(busname::AbstractString; default::Array=[], check_length::Bool=true, pad_ground::Bool=false)::Array parts = split(busname, '.'; limit=2) ret = [] if length(parts)==2 @@ -1075,7 +1071,8 @@ function _get_conductors_ordered_dm(busname::AbstractString; default=[], check_l end if check_length && length(default)!=length(ret) - Memento.error("An incorrect number of nodes was specified on $(parts[1]); |$(parts[2])|!=$(length(default)).") + # TODO + Memento.warn(_LOGGER, "An incorrect number of nodes was specified on $(parts[1]); |$(parts[2])|!=$(length(default)).") end return ret end From 95f2793c695cd16287dec9d043fe93f6d1285038 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 27 Feb 2020 13:57:43 -0700 Subject: [PATCH 041/224] REF: add_component! functions ids not in component dictionary anymore, made name a required parameter instead --- src/io/data_model_components.jl | 561 ++++++++++++++++---------------- src/io/data_model_test.jl | 92 +++--- 2 files changed, 323 insertions(+), 330 deletions(-) diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl index 6d8faecaf..2b6363666 100644 --- a/src/io/data_model_components.jl +++ b/src/io/data_model_components.jl @@ -21,61 +21,232 @@ end function component_dict_from_list!(list) dict = Dict{String, Any}() - for comp_dict in list - dict[comp_dict["id"]] = comp_dict + for object in list + dict[object["obj_name"]] = object end return dict end -REQUIRED_FIELDS = Dict{Symbol, Any}() -DTYPES = Dict{Symbol, Any}() -CHECKS = Dict{Symbol, Any}() +const _eng_model_dtypes = Dict{Symbol,Dict{Symbol,Type}}( + :linecode => Dict{Symbol,Type}( + :obj_name => Any, + :rs => Array{<:Real, 2}, + :xs => Array{<:Real, 2}, + :g_fr => Array{<:Real, 2}, + :g_to => Array{<:Real, 2}, + :b_fr => Array{<:Real, 2}, + :b_to => Array{<:Real, 2} + ), + :line => Dict{Symbol,Type}( + :obj_name => Any, + :status => Int, + :f_bus => AbstractString, + :t_bus => AbstractString, + :f_connections => Vector{<:Int}, + :t_connections => Vector{<:Int}, + :linecode => AbstractString, + :length => Real, + :c_rating =>Vector{<:Real}, + :s_rating =>Vector{<:Real}, + :angmin=>Vector{<:Real}, + :angmax=>Vector{<:Real}, + :rs => Array{<:Real, 2}, + :xs => Array{<:Real, 2}, + :g_fr => Array{<:Real, 2}, + :g_to => Array{<:Real, 2}, + :b_fr => Array{<:Real, 2}, + :b_to => Array{<:Real, 2}, + ), + :bus => Dict{Symbol,Type}( + :obj_name => Any, + :status => Int, + :bus_type => Int, + :terminals => Array{<:Any}, + :phases => Array{<:Int}, + :neutral => Union{Int, Missing}, + :grounded => Array{<:Any}, + :rg => Array{<:Real}, + :xg => Array{<:Real}, + :vm_pn_min => Real, + :vm_pn_max => Real, + :vm_pp_min => Real, + :vm_pp_max => Real, + :vm_min => Array{<:Real, 1}, + :vm_max => Array{<:Real, 1}, + :vm_fix => Array{<:Real, 1}, + :va_fix => Array{<:Real, 1}, + ), + :load => Dict{Symbol,Type}( + :obj_name => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :configuration => String, + :model => String, + :pd => Array{<:Real, 1}, + :qd => Array{<:Real, 1}, + :pd_ref => Array{<:Real, 1}, + :qd_ref => Array{<:Real, 1}, + :vnom => Array{<:Real, 1}, + :alpha => Array{<:Real, 1}, + :beta => Array{<:Real, 1}, + ), + :generator => Dict{Symbol,Type}( + :obj_name => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :configuration => String, + :cost => Vector{<:Real}, + :pg => Array{<:Real, 1}, + :qg => Array{<:Real, 1}, + :pg_min => Array{<:Real, 1}, + :pg_max => Array{<:Real, 1}, + :qg_min => Array{<:Real, 1}, + :qg_max => Array{<:Real, 1}, + ), + :transformer_nw => Dict{Symbol,Type}( + :obj_name => Any, + :status => Int, + :bus => Array{<:AbstractString, 1}, + :connections => Vector, + :vnom => Array{<:Real, 1}, + :snom => Array{<:Real, 1}, + :configuration => Array{String, 1}, + :polarity => Array{Bool, 1}, + :xsc => Array{<:Real, 1}, + :rs => Array{<:Real, 1}, + :noloadloss => Real, + :imag => Real, + :tm_fix => Array{Array{Bool, 1}, 1}, + :tm => Array{<:Array{<:Real, 1}, 1}, + :tm_min => Array{<:Array{<:Real, 1}, 1}, + :tm_max => Array{<:Array{<:Real, 1}, 1}, + :tm_step => Array{<:Array{<:Real, 1}, 1}, + ), + :capacitor => Dict{Symbol,Type}( + :obj_name => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :configuration => String, + :qd_ref => Array{<:Real, 1}, + :vnom => Real, + ), + :shunt => Dict{Symbol,Type}( + :obj_name => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :g_sh => Array{<:Real, 2}, + :b_sh => Array{<:Real, 2}, + ), + :voltage_source => Dict{Symbol,Type}( + :obj_name => Any, + :status => Int, + :bus => Any, + :connections => Vector, + :vm =>Array{<:Real}, + :va =>Array{<:Real}, + :pg_max =>Array{<:Real}, + :pg_min =>Array{<:Real}, + :qg_max =>Array{<:Real}, + :qg_min =>Array{<:Real}, + ), + :ev => Dict{Symbol,Type}(), + :storage => Dict{Symbol,Type}(), + :pv => Dict{Symbol,Type}(), + :wind => Dict{Symbol,Type}(), + :switch => Dict{Symbol,Type}(), + :autotransformer => Dict{Symbol,Type}(), + :synchronous_generator => Dict{Symbol,Type}(), + :zip_load => Dict{Symbol,Type}(), + :grounding => Dict{Symbol,Type}( + :bus => Any, + :rg => Real, + :xg => Real, + ), + :boundary => Dict{Symbol,Type}(), + :meter => Dict{Symbol,Type}() +) + +const _eng_model_req_fields= Dict{Symbol,Array{Symbol,1}}( + :linecode => Array{Symbol,1}([:obj_name, :rs, :xs, :g_fr, :g_to, :b_fr, :b_to]), + :line => Array{Symbol,1}([:obj_name, :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length]), + :bus => Array{Symbol,1}([:obj_name, :status, :terminals, :grounded, :rg, :xg]), + :load => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :configuration]), + :generator => Array{Symbol,1}([:obj_name, :status, :bus, :connections]), + :transformer_nw => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :vnom, :snom, :configuration, :polarity, :xsc, :rs, :noloadloss, :imag, :tm_fix, :tm, :tm_min, :tm_max, :tm_step]), + :capacitor => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :configuration, :qd_ref, :vnom]), + :shunt => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :g_sh, :b_sh]), + :voltage_source => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :vm, :va]), + :ev => Array{Symbol,1}([]), + :storage => Array{Symbol,1}([]), + :pv => Array{Symbol,1}([]), + :wind => Array{Symbol,1}([]), + :switch => Array{Symbol,1}([]), + :autotransformer => Array{Symbol,1}([]), + :synchronous_generator => Array{Symbol,1}([]), + :zip_load => Array{Symbol,1}([]), + :grounding => Array{Symbol,1}([]), + :boundary => Array{Symbol,1}([]), + :meter => Array{Symbol,1}([]) +) + +_eng_model_checks = Dict() -function check_dtypes(dict, dtypes, comp_type, id) + +"" +function check__eng_model_dtypes(dict, _eng_model_dtypes, comp_type, obj_name) for key in keys(dict) symb = Symbol(key) - if haskey(dtypes, symb) - @assert(isa(dict[key], dtypes[symb]), "$comp_type $id: the property $key should be a $(dtypes[symb]), not a $(typeof(dict[key])).") + if haskey(_eng_model_dtypes, symb) + @assert(isa(dict[key], _eng_model_dtypes[symb]), "$comp_type $obj_name: the property $key should be a $(_eng_model_dtypes[symb]), not a $(typeof(dict[key])).") else - #@assert(false, "$comp_type $id: the property $key is unknown.") + #@assert(false, "$comp_type $obj_name: the property $key is unknown.") end end end -function add!(data_model, comp_type, comp_dict) - @assert(haskey(comp_dict, "id"), "The component does not have an id defined.") - id = comp_dict["id"] - if !haskey(data_model, comp_type) - data_model[comp_type] = Dict{Any, Any}() +"" +function add!(data_eng::Dict{String,<:Any}, obj_type::AbstractString, obj_name::AbstractString, object::Dict{String,<:Any}) + # @assert(haskey(object, "obj_name"), "The component does not have an obj_name defined.") + if !haskey(data_eng, obj_type) + data_eng[obj_type] = Dict{String,Any}() else - @assert(!haskey(data_model[comp_type], id), "There is already a $comp_type with id $id.") + @assert(!haskey(data_eng[obj_type], obj_name), "There is already a $obj_type with name $obj_name.") end - data_model[comp_type][id] = comp_dict + + data_eng[obj_type][obj_name] = object end -function _add_unused_kwargs!(comp_dict, kwargs) + +"" +function _add_unused_kwargs!(object, kwargs) for (prop, val) in kwargs - if !haskey(comp_dict, "$prop") - comp_dict["$prop"] = val + if !haskey(object, "$prop") + object["$prop"] = val end end end + +"" function check_data_model(data) - for component in keys(DTYPES) + for component in keys(_eng_model_dtypes) if haskey(data, string(component)) - for (id, comp_dict) in data[string(component)] - if haskey(REQUIRED_FIELDS, component) - for field in REQUIRED_FIELDS[component] - @assert(haskey(comp_dict, string(field)), "The property \'$field\' is missing for $component $id.") + for (obj_name, object) in data[string(component)] + if haskey(_eng_model_req_fields, component) + for field in _eng_model_req_fields[component] + @assert(haskey(object, string(field)), "The property \'$field\' is missing for $component $obj_name.") end end - if haskey(DTYPES, component) - check_dtypes(comp_dict, DTYPES[component], component, id) + if haskey(_eng_model_dtypes, component) + check__eng_model_dtypes(object, _eng_model_dtypes[component], component, obj_name) end - if haskey(CHECKS, component) - CHECKS[component](data, comp_dict) + if haskey(_eng_model_checks, component) + _eng_model_checks[component](data, object) end end end @@ -83,38 +254,19 @@ function check_data_model(data) end +"" function create_data_model(; kwargs...) data_model = Dict{String, Any}("settings"=>Dict{String, Any}()) - add_kwarg!(data_model["settings"], kwargs, :v_var_scalar, 1E3) + add_kwarg!(data_model["settings"], kwargs, :v_var_scalar, 1e3) _add_unused_kwargs!(data_model["settings"], kwargs) return data_model end -# COMPONENTS -#*################# - -DTYPES[:ev] = Dict() -DTYPES[:storage] = Dict() -DTYPES[:pv] = Dict() -DTYPES[:wind] = Dict() -DTYPES[:switch] = Dict() -DTYPES[:shunt] = Dict() -DTYPES[:autotransformer] = Dict() -DTYPES[:synchronous_generator] = Dict() -DTYPES[:zip_load] = Dict() -DTYPES[:grounding] = Dict( - :bus => Any, - :rg => Real, - :xg => Real, -) -DTYPES[:synchronous_generator] = Dict() -DTYPES[:boundary] = Dict() -DTYPES[:meter] = Dict() - +"" function _check_same_size(data, keys; context=missing) size_comp = size(data[string(keys[1])]) for key in keys[2:end] @@ -123,6 +275,7 @@ function _check_same_size(data, keys; context=missing) end +"" function _check_has_size(data, keys, size_comp; context=missing, allow_missing=true) for key in keys if haskey(data, key) || !allow_missing @@ -131,61 +284,53 @@ function _check_has_size(data, keys, size_comp; context=missing, allow_missing=t end end -function _check_connectivity(data, comp_dict; context=missing) - if haskey(comp_dict, "f_bus") +"" +function _check_connectivity(data, object; context=missing) + if haskey(object, "f_bus") # two-port element - _check_bus_and_terminals(data, comp_dict["f_bus"], comp_dict["f_connections"], context) - _check_bus_and_terminals(data, comp_dict["t_bus"], comp_dict["t_connections"], context) - elseif haskey(comp_dict, "bus") - if isa(comp_dict["bus"], Vector) - for i in 1:length(comp_dict["bus"]) - _check_bus_and_terminals(data, comp_dict["bus"][i], comp_dict["connections"][i], context) + _check_bus_and_terminals(data, object["f_bus"], object["f_connections"], context) + _check_bus_and_terminals(data, object["t_bus"], object["t_connections"], context) + elseif haskey(object, "bus") + if isa(object["bus"], Vector) + for i in 1:length(object["bus"]) + _check_bus_and_terminals(data, object["bus"][i], object["connections"][i], context) end else - _check_bus_and_terminals(data, comp_dict["bus"], comp_dict["connections"], context) + _check_bus_and_terminals(data, object["bus"], object["connections"], context) end end end -function _check_bus_and_terminals(data, bus_id, terminals, context=missing) - @assert(haskey(data, "bus") && haskey(data["bus"], bus_id), "$context: the bus $bus_id is not defined.") - bus = data["bus"][bus_id] +"" +function _check_bus_and_terminals(data, bus_obj_name, terminals, context=missing) + @assert(haskey(data, "bus") && haskey(data["bus"], bus_obj_name), "$context: the bus $bus_obj_name is not defined.") + bus = data["bus"][bus_obj_name] for t in terminals - @assert(t in bus["terminals"], "$context: bus $(bus["id"]) does not have terminal \'$t\'.") + @assert(t in bus["terminals"], "$context: bus $(bus["obj_name"]) does not have terminal \'$t\'.") end end -function _check_has_keys(comp_dict, keys; context=missing) +"" +function _check_has_keys(object, keys; context=missing) for key in keys - @assert(haskey(comp_dict, key), "$context: the property $key is missing.") + @assert(haskey(object, key), "$context: the property $key is missing.") end end -function _check_configuration_infer_dim(comp_dict; context=missing) - conf = comp_dict["configuration"] + +"" +function _check_configuration_infer_dim(object; context=missing) + conf = object["configuration"] @assert(conf in ["delta", "wye"], "$context: the configuration should be \'delta\' or \'wye\', not \'$conf\'.") - return conf=="wye" ? length(comp_dict["connections"])-1 : length(comp_dict["connections"]) + return conf=="wye" ? length(object["connections"])-1 : length(object["connections"]) end # linecode - -DTYPES[:linecode] = Dict( - :id => Any, - :rs => Array{<:Real, 2}, - :xs => Array{<:Real, 2}, - :g_fr => Array{<:Real, 2}, - :g_to => Array{<:Real, 2}, - :b_fr => Array{<:Real, 2}, - :b_to => Array{<:Real, 2}, -) - -REQUIRED_FIELDS[:linecode] = keys(DTYPES[:linecode]) - -CHECKS[:linecode] = function check_linecode(data, linecode) +_eng_model_checks[:linecode] = function check_linecode(data, linecode) _check_same_size(linecode, [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to]) end @@ -211,41 +356,17 @@ function create_linecode(; kwargs...) end # line - -DTYPES[:line] = Dict( - :id => Any, - :status => Int, - :f_bus => AbstractString, - :t_bus => AbstractString, - :f_connections => Vector{<:Int}, - :t_connections => Vector{<:Int}, - :linecode => AbstractString, - :length => Real, - :c_rating =>Vector{<:Real}, - :s_rating =>Vector{<:Real}, - :angmin=>Vector{<:Real}, - :angmax=>Vector{<:Real}, - :rs => Array{<:Real, 2}, - :xs => Array{<:Real, 2}, - :g_fr => Array{<:Real, 2}, - :g_to => Array{<:Real, 2}, - :b_fr => Array{<:Real, 2}, - :b_to => Array{<:Real, 2}, -) - -REQUIRED_FIELDS[:line] = [:id, :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length] - -CHECKS[:line] = function check_line(data, line) - i = line["id"] +_eng_model_checks[:line] = function check_line(data, line) + i = line["obj_name"] # for now, always require a line code if haskey(line, "linecode") # line is defined with a linecode @assert(haskey(line, "length"), "line $i: a line defined through a linecode, should have a length property.") - linecode_id = line["linecode"] - @assert(haskey(data, "linecode") && haskey(data["linecode"], "$linecode_id"), "line $i: the linecode $linecode_id is not defined.") - linecode = data["linecode"]["$linecode_id"] + linecode_obj_name = line["linecode"] + @assert(haskey(data, "linecode") && haskey(data["linecode"], "$linecode_obj_name"), "line $i: the linecode $linecode_obj_name is not defined.") + linecode = data["linecode"]["$linecode_obj_name"] for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] @assert(!haskey(line, key), "line $i: a line with a linecode, should not specify $key; this is already done by the linecode.") @@ -262,7 +383,7 @@ CHECKS[:line] = function check_line(data, line) end end - _check_connectivity(data, line, context="line $(line["id"])") + _check_connectivity(data, line, context="line $(line["obj_name"])") end @@ -299,39 +420,16 @@ function create_line(; kwargs...) end # Bus +_eng_model_checks[:bus] = function check_bus(data, bus) + obj_name = bus["obj_name"] -DTYPES[:bus] = Dict( - :id => Any, - :status => Int, - :bus_type => Int, - :terminals => Array{<:Any}, - :phases => Array{<:Int}, - :neutral => Union{Int, Missing}, - :grounded => Array{<:Any}, - :rg => Array{<:Real}, - :xg => Array{<:Real}, - :vm_pn_min => Real, - :vm_pn_max => Real, - :vm_pp_min => Real, - :vm_pp_max => Real, - :vm_min => Array{<:Real, 1}, - :vm_max => Array{<:Real, 1}, - :vm_fix => Array{<:Real, 1}, - :va_fix => Array{<:Real, 1}, -) - -REQUIRED_FIELDS[:bus] = [:id, :status, :terminals, :grounded, :rg, :xg] - -CHECKS[:bus] = function check_bus(data, bus) - id = bus["id"] - - _check_same_size(bus, ["grounded", "rg", "xg"], context="bus $id") + _check_same_size(bus, ["grounded", "rg", "xg"], context="bus $obj_name") N = length(bus["terminals"]) - _check_has_size(bus, ["vm_max", "vm_min", "vm", "va"], N, context="bus $id") + _check_has_size(bus, ["vm_max", "vm_min", "vm", "va"], N, context="bus $obj_name") if haskey(bus, "neutral") - assert(haskey(bus, "phases"), "bus $id: has a neutral, but no phases.") + assert(haskey(bus, "phases"), "bus $obj_name: has a neutral, but no phases.") end end @@ -351,44 +449,25 @@ function create_bus(; kwargs...) end # Load +_eng_model_checks[:load] = function check_load(data, load) + obj_name = load["obj_name"] -DTYPES[:load] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :configuration => String, - :model => String, - :pd => Array{<:Real, 1}, - :qd => Array{<:Real, 1}, - :pd_ref => Array{<:Real, 1}, - :qd_ref => Array{<:Real, 1}, - :vnom => Array{<:Real, 1}, - :alpha => Array{<:Real, 1}, - :beta => Array{<:Real, 1}, -) - -REQUIRED_FIELDS[:load] = [:id, :status, :bus, :connections, :configuration] - -CHECKS[:load] = function check_load(data, load) - id = load["id"] - - N = _check_configuration_infer_dim(load; context="load $id") + N = _check_configuration_infer_dim(load; context="load $obj_name") model = load["model"] @assert(model in ["constant_power", "constant_impedance", "constant_current", "exponential"]) if model=="constant_power" - _check_has_keys(load, ["pd", "qd"], context="load $id, $model:") - _check_has_size(load, ["pd", "qd"], N, context="load $id, $model:") + _check_has_keys(load, ["pd", "qd"], context="load $obj_name, $model:") + _check_has_size(load, ["pd", "qd"], N, context="load $obj_name, $model:") elseif model=="exponential" - _check_has_keys(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], context="load $id, $model") - _check_has_size(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], N, context="load $id, $model:") + _check_has_keys(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], context="load $obj_name, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], N, context="load $obj_name, $model:") else - _check_has_keys(load, ["pd_ref", "qd_ref", "vnom"], context="load $id, $model") - _check_has_size(load, ["pd_ref", "qd_ref", "vnom"], N, context="load $id, $model:") + _check_has_keys(load, ["pd_ref", "qd_ref", "vnom"], context="load $obj_name, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vnom"], N, context="load $obj_name, $model:") end - _check_connectivity(data, load; context="load $id") + _check_connectivity(data, load; context="load $obj_name") end @@ -413,31 +492,13 @@ function create_load(; kwargs...) end # generator +_eng_model_checks[:generator] = function check_generator(data, generator) + obj_name = generator["obj_name"] -DTYPES[:generator] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :configuration => String, - :cost => Vector{<:Real}, - :pg => Array{<:Real, 1}, - :qg => Array{<:Real, 1}, - :pg_min => Array{<:Real, 1}, - :pg_max => Array{<:Real, 1}, - :qg_min => Array{<:Real, 1}, - :qg_max => Array{<:Real, 1}, -) - -REQUIRED_FIELDS[:generator] = [:id, :status, :bus, :connections] - -CHECKS[:generator] = function check_generator(data, generator) - id = generator["id"] + N = _check_configuration_infer_dim(generator; context="generator $obj_name") + _check_has_size(generator, ["pd", "qd", "pd_min", "pd_max", "qd_min", "qd_max"], N, context="generator $obj_name") - N = _check_configuration_infer_dim(generator; context="generator $id") - _check_has_size(generator, ["pd", "qd", "pd_min", "pd_max", "qd_min", "qd_max"], N, context="generator $id") - - _check_connectivity(data, generator; context="generator $id") + _check_connectivity(data, generator; context="generator $obj_name") end function create_generator(; kwargs...) @@ -455,35 +516,10 @@ end # Transformer, n-windings three-phase lossy - - -DTYPES[:transformer_nw] = Dict( - :id => Any, - :status => Int, - :bus => Array{<:AbstractString, 1}, - :connections => Vector, - :vnom => Array{<:Real, 1}, - :snom => Array{<:Real, 1}, - :configuration => Array{String, 1}, - :polarity => Array{Bool, 1}, - :xsc => Array{<:Real, 1}, - :rs => Array{<:Real, 1}, - :noloadloss => Real, - :imag => Real, - :tm_fix => Array{Array{Bool, 1}, 1}, - :tm => Array{<:Array{<:Real, 1}, 1}, - :tm_min => Array{<:Array{<:Real, 1}, 1}, - :tm_max => Array{<:Array{<:Real, 1}, 1}, - :tm_step => Array{<:Array{<:Real, 1}, 1}, -) - -REQUIRED_FIELDS[:transformer_nw] = keys(DTYPES[:transformer_nw]) - - -CHECKS[:transformer_nw] = function check_transformer_nw(data, trans) - id = trans["id"] +_eng_model_checks[:transformer_nw] = function check_transformer_nw(data, trans) + obj_name = trans["obj_name"] nrw = length(trans["bus"]) - _check_has_size(trans, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_min", "tm_max", "tm_step"], nrw, context="trans $id") + _check_has_size(trans, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_min", "tm_max", "tm_step"], nrw, context="trans $obj_name") @assert(length(trans["xsc"])==(nrw^2-nrw)/2) nphs = [] @@ -492,12 +528,12 @@ CHECKS[:transformer_nw] = function check_transformer_nw(data, trans) conf = trans["configuration"][w] conns = trans["connections"][w] nph = conf=="wye" ? length(conns)-1 : length(conns) - @assert(all(nph.==nphs), "transformer $id: winding $w has a different number of phases than the previous ones.") + @assert(all(nph.==nphs), "transformer $obj_name: winding $w has a different number of phases than the previous ones.") push!(nphs, nph) #TODO check length other properties end - _check_connectivity(data, trans; context="transformer_nw $id") + _check_connectivity(data, trans; context="transformer_nw $obj_name") end @@ -527,8 +563,8 @@ end # # # Transformer, two-winding three-phase # -# DTYPES[:transformer_2w_ideal] = Dict( -# :id => Any, +# _eng_model_dtypes[:transformer_2w_obj_nameeal] = Dict( +# :obj_name => Any, # :f_bus => String, # :t_bus => String, # :configuration => String, @@ -543,13 +579,13 @@ end # ) # # -# CHECKS[:transformer_2w_ideal] = function check_transformer_2w_ideal(data, trans) +# _eng_model_checks[:transformer_2w_obj_nameeal] = function check_transformer_2w_obj_nameeal(data, trans) # end # # -# function create_transformer_2w_ideal(id, f_bus, t_bus, tm_nom; kwargs...) +# function create_transformer_2w_obj_nameeal(obj_name, f_bus, t_bus, tm_nom; kwargs...) # trans = Dict{String,Any}() -# trans["id"] = id +# trans["obj_name"] = obj_name # trans["f_bus"] = f_bus # trans["t_bus"] = t_bus # trans["tm_nom"] = tm_nom @@ -566,35 +602,21 @@ end # Capacitor - -DTYPES[:capacitor] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :configuration => String, - :qd_ref => Array{<:Real, 1}, - :vnom => Real, -) - -REQUIRED_FIELDS[:capacitor] = keys(DTYPES[:capacitor]) - - -CHECKS[:capacitor] = function check_capacitor(data, cap) - id = cap["id"] +_eng_model_checks[:capacitor] = function check_capacitor(data, cap) + obj_name = cap["obj_name"] N = length(cap["connections"]) config = cap["configuration"] if config=="wye" - @assert(length(cap["qd_ref"])==N-1, "capacitor $id: qd_ref should have $(N-1) elements.") + @assert(length(cap["qd_ref"])==N-1, "capacitor $obj_name: qd_ref should have $(N-1) elements.") else - @assert(length(cap["qd_ref"])==N, "capacitor $id: qd_ref should have $N elements.") + @assert(length(cap["qd_ref"])==N, "capacitor $obj_name: qd_ref should have $N elements.") end @assert(config in ["delta", "wye", "wye-grounded", "wye-floating"]) if config=="delta" - @assert(N>=3, "Capacitor $id: delta-connected capacitors should have at least 3 elements.") + @assert(N>=3, "Capacitor $obj_name: delta-connected capacitors should have at least 3 elements.") end - _check_connectivity(data, cap; context="capacitor $id") + _check_connectivity(data, cap; context="capacitor $obj_name") end @@ -613,21 +635,8 @@ end # Shunt - -DTYPES[:shunt] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :g_sh => Array{<:Real, 2}, - :b_sh => Array{<:Real, 2}, -) - -REQUIRED_FIELDS[:shunt] = keys(DTYPES[:shunt]) - - -CHECKS[:shunt] = function check_shunt(data, shunt) - _check_connectivity(data, shunt; context="shunt $id") +_eng_model_checks[:shunt] = function check_shunt(data, shunt) + _check_connectivity(data, shunt; context="shunt $obj_name") end @@ -648,27 +657,11 @@ end # voltage source - -DTYPES[:voltage_source] = Dict( - :id => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :vm =>Array{<:Real}, - :va =>Array{<:Real}, - :pg_max =>Array{<:Real}, - :pg_min =>Array{<:Real}, - :qg_max =>Array{<:Real}, - :qg_min =>Array{<:Real}, -) - -REQUIRED_FIELDS[:voltage_source] = [:id, :status, :bus, :connections, :vm, :va] - -CHECKS[:voltage_source] = function check_voltage_source(data, vs) - id = vs["id"] - _check_connectivity(data, vs; context="voltage source $id") +_eng_model_checks[:voltage_source] = function check_voltage_source(data, vs) + obj_name = vs["obj_name"] + _check_connectivity(data, vs; context="voltage source $obj_name") N = length(vs["connections"]) - _check_has_size(vs, ["vm", "va", "pg_max", "pg_min", "qg_max", "qg_min"], N, context="voltage source $id") + _check_has_size(vs, ["vm", "va", "pg_max", "pg_min", "qg_max", "qg_min"], N, context="voltage source $obj_name") end @@ -686,6 +679,6 @@ end # create add_comp! methods -for comp in keys(DTYPES) - eval(Meta.parse("add_$(comp)!(data_model; kwargs...) = add!(data_model, \"$comp\", create_$comp(; kwargs...))")) +for comp in keys(_eng_model_dtypes) + eval(Meta.parse("add_$(comp)!(data_model, name; kwargs...) = add!(data_model, \"$comp\", name, create_$comp(; kwargs...))")) end diff --git a/src/io/data_model_test.jl b/src/io/data_model_test.jl index a394d7d95..60a21afbb 100644 --- a/src/io/data_model_test.jl +++ b/src/io/data_model_test.jl @@ -5,53 +5,53 @@ function make_test_data_model() data_model = create_data_model() - add_linecode!(data_model, id="6_conds", rs=ones(6, 6), xs=ones(6, 6)) - add_linecode!(data_model, id="4_conds", rs=ones(4, 4), xs=ones(4, 4)) - add_linecode!(data_model, id="3_conds", rs=ones(3, 3), xs=ones(3, 3)) - add_linecode!(data_model, id="2_conds", rs=ones(2, 2), xs=ones(2, 2)) + add_linecode!(data_model, "6_conds", rs=ones(6, 6), xs=ones(6, 6)) + add_linecode!(data_model, "4_conds", rs=ones(4, 4), xs=ones(4, 4)) + add_linecode!(data_model, "3_conds", rs=ones(3, 3), xs=ones(3, 3)) + add_linecode!(data_model, "2_conds", rs=ones(2, 2), xs=ones(2, 2)) # 3 phase + 3 neutral conductors - add_line!(data_model, id="1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6)) - add_line!(data_model, id="2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4]) + add_line!(data_model, "1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6)) + add_line!(data_model, "2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4]) # 3 phase + 1 neutral conductors - add_line!(data_model, id="3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2) + add_line!(data_model, "3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2) # 3 phase conductors - add_line!(data_model, id="4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) + add_line!(data_model, "4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) # 2 phase + 1 neutral conductors - add_line!(data_model, id="5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4]) + add_line!(data_model, "5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4]) # 1 phase + 1 neutral conductors - add_line!(data_model, id="6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4]) + add_line!(data_model, "6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4]) # 2 phase conductors - add_line!(data_model, id="7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) + add_line!(data_model, "7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) for i in 8:1000 add_line!(data_model, id="$i", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) end - add_bus!(data_model, id="1", terminals=collect(1:4)) - add_bus!(data_model, id="2", terminals=collect(1:6)) - add_bus!(data_model, id="3", terminals=collect(1:4)) - add_bus!(data_model, id="4") - add_bus!(data_model, id="5", terminals=collect(1:4)) - add_bus!(data_model, id="6", terminals=[1,3,4]) - add_bus!(data_model, id="7", terminals=[2,4]) - add_bus!(data_model, id="8", terminals=[1,2]) - add_bus!(data_model, id="9", terminals=[1,2,3,4]) - add_bus!(data_model, id="10", terminals=[1,2,3]) + add_bus!(data_model, "1", terminals=collect(1:4)) + add_bus!(data_model, "2", terminals=collect(1:6)) + add_bus!(data_model, "3", terminals=collect(1:4)) + add_bus!(data_model, "4") + add_bus!(data_model, "5", terminals=collect(1:4)) + add_bus!(data_model, "6", terminals=[1,3,4]) + add_bus!(data_model, "7", terminals=[2,4]) + add_bus!(data_model, "8", terminals=[1,2]) + add_bus!(data_model, "9", terminals=[1,2,3,4]) + add_bus!(data_model, "10", terminals=[1,2,3]) # - add_load!(data_model, id="1", bus="7", connections=[2,4], pd=[1.0], qd=[1.0]) - add_load!(data_model, id="2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)]) - add_load!(data_model, id="3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230]) - add_load!(data_model, id="4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5]) - add_load!(data_model, id="5", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3)) - add_load!(data_model, id="6", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3)) - add_load!(data_model, id="7", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3)) - add_load!(data_model, id="8", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]) - add_load!(data_model, id="9", bus="5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3)) - - add_generator!(data_model, id="1", bus="1", configuration="wye") - - add_transformer_nw!(data_model, id="1", bus=["5", "9", "10"], connections=[[1,2,3], [1,2,3,4], [1,2,3]], + add_load!(data_model, "1", bus="7", connections=[2,4], pd=[1.0], qd=[1.0]) + add_load!(data_model, "2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)]) + add_load!(data_model, "3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230]) + add_load!(data_model, "4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5]) + add_load!(data_model, "5", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3)) + add_load!(data_model, "6", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3)) + add_load!(data_model, "7", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3)) + add_load!(data_model, "8", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]) + add_load!(data_model, "9", bus="5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3)) + + add_generator!(data_model, "1", bus="1", configuration="wye") + + add_transformer_nw!(data_model, "1", bus=["5", "9", "10"], connections=[[1,2,3], [1,2,3,4], [1,2,3]], vnom=[0.230, 0.230, 0.230], snom=[0.230, 0.230, 0.230], configuration=["delta", "wye", "delta"], xsc=[0.0, 0.0, 0.0], @@ -60,11 +60,11 @@ function make_test_data_model() imag=0.05, ) - add_capacitor!(data_model, id="cap_3ph", bus="3", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3]) - add_capacitor!(data_model, id="cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3]) - add_capacitor!(data_model, id="cap_2ph_yg", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-grounded") - add_capacitor!(data_model, id="cap_2ph_yfl", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-floating") - add_capacitor!(data_model, id="cap_2ph_y", bus="5", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4]) + add_capacitor!(data_model, "cap_3ph", bus="3", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3]) + add_capacitor!(data_model, "cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3]) + add_capacitor!(data_model, "cap_2ph_yg", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-grounded") + add_capacitor!(data_model, "cap_2ph_yfl", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-floating") + add_capacitor!(data_model, "cap_2ph_y", bus="5", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4]) return data_model end @@ -74,9 +74,9 @@ function make_3wire_data_model() data_model = create_data_model() - add_linecode!(data_model, id="3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3))) + add_linecode!(data_model, "3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3))) - add_voltage_source!(data_model, id="source", bus="sourcebus", connections=collect(1:3), + add_voltage_source!(data_model, "source", bus="sourcebus", connections=collect(1:3), vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), rs=ones(3,3)/10 @@ -85,9 +85,9 @@ function make_3wire_data_model() # 3 phase conductors add_line!(data_model, id=:test, f_bus="sourcebus", t_bus="tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) - add_bus!(data_model, id="sourcebus", terminals=collect(1:3)) - add_bus!(data_model, id="tr_prim", terminals=collect(1:4)) - add_bus!(data_model, id="tr_sec", terminals=collect(1:4)) + add_bus!(data_model, "sourcebus", terminals=collect(1:3)) + add_bus!(data_model, "tr_prim", terminals=collect(1:4)) + add_bus!(data_model, "tr_sec", terminals=collect(1:4)) #add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) # add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], @@ -99,7 +99,7 @@ function make_3wire_data_model() # imag=0.00, # )) - add_transformer_nw!(data_model, id="1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], + add_transformer_nw!(data_model, "1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], vnom=[0.230, 0.230], snom=[0.230, 0.230], configuration=["wye", "wye"], xsc=[0.0], @@ -111,7 +111,7 @@ function make_3wire_data_model() # - add_load!(data_model, id="1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0]) + add_load!(data_model, "1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0]) # add!(data_model, "generator", create_generator("1", "source", # connections=[1, 2, 3, 4], From 3ae313d020f31f2e0198c1cf5d5d7b63d3265653 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 2 Mar 2020 16:14:20 -0700 Subject: [PATCH 042/224] UPD: transform data model --- src/io/common_dm.jl | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl index ffcdfb63c..3bcab5e55 100644 --- a/src/io/common_dm.jl +++ b/src/io/common_dm.jl @@ -16,7 +16,7 @@ function parse_file_dm(io::IO; data_model::String="engineering", import_all::Boo end if data_model == "mathematical" - transform_data_model!(pmd_data) + pmd_data = transform_data_model(pmd_data) end #correct_network_data!(pmd_data) @@ -36,16 +36,15 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model!(data::Dict{String,<:Any}) - if get(data, "data_model", "mathematical") == "engineering" - data_model_map!(data) - data_model_make_pu!(data) - data_model_index!(data) - data_model_make_compatible_v8!(data) +function transform_data_model(data::Dict{<:Any,<:Any}) + current_data_model = get(data, "data_model", "mathematical") + + if current_data_model == "engineering" + return _map_eng2math(data) + elseif current_data_model == "mathematical" + return _map_math2eng!(data) else - if haskey(data, "") - else - Memento.warn(_LOGGER, "Cannot transform mathematical model to engineering model, no mapping information available") - end + @warn "Data model '$current_data_model' is not recognized, no transformation performed" + return data end end From 60e46d00009438e535a35ac8535fca65f83787eb Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 2 Mar 2020 16:15:47 -0700 Subject: [PATCH 043/224] v_var_scalar -> kv_kvar_scalar fix options parsing --- src/io/data_model_components.jl | 2 +- src/io/opendss_dm.jl | 49 +++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl index 2b6363666..745bd8212 100644 --- a/src/io/data_model_components.jl +++ b/src/io/data_model_components.jl @@ -258,7 +258,7 @@ end function create_data_model(; kwargs...) data_model = Dict{String, Any}("settings"=>Dict{String, Any}()) - add_kwarg!(data_model["settings"], kwargs, :v_var_scalar, 1e3) + add_kwarg!(data_model["settings"], kwargs, :kv_kvar_scalar, 1e3) _add_unused_kwargs!(data_model["settings"], kwargs) diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 2b654918f..f594fe3af 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -5,7 +5,7 @@ import LinearAlgebra: isdiag, diag, pinv const _exclude_duplicate_check = ["options", "filename", "circuit"] const _dss_edge_components = ["line", "transformer", "reactor"] const _dss_supported_components = ["line", "linecode", "load", "generator", "capacitor", "reactor", "circuit", "transformer", "pvsystem", "storage", "loadshape"] -const _dss_option_dtypes = Dict{String,Type}("defaultbasefreq" => Float64, "voltagebases" => Float64) +const _dss_option_dtypes = Dict{String,Type}("defaultbasefreq" => Float64, "voltagebases" => Float64, "tolerance" => Float64) "Discovers all of the buses (not separately defined in OpenDSS), from \"lines\"" @@ -84,7 +84,7 @@ function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String vm = fill(vm_pu, 3)*vnom va = _wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases]) - add_voltage_source!(data_eng, "sourcebus"; bus="sourcebus", connections=collect(1:phases), vm=vm, va=va, rs=circuit["rmatrix"], xs=circuit["xmatrix"]) + add_voltage_source!(data_eng, "sourcebus"; bus="sourcebus", connections=collect(1:phases), vm=vm, va=va, rmatrix=circuit["rmatrix"], xmatrix=circuit["xmatrix"]) end @@ -486,17 +486,6 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) eng_obj["t_connections"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) - - # TODO calculate these in the conversion/mapping to mathematical model - # eng_obj["rs"] = rmatrix * defaults["length"] - # eng_obj["xs"] = xmatrix * defaults["length"] - - # eng_obj["g_fr"] = fill(0.0, nphases, nphases) - # eng_obj["g_to"] = fill(0.0, nphases, nphases) - - # eng_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 - # eng_obj["b_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * cmatrix * defaults["length"] / 1e9) / 2.0 - eng_obj["status"] = convert(Int, defaults["enabled"]) eng_obj["source_id"] = "line.$(name)" @@ -748,7 +737,6 @@ end "Adds storage to `data_eng` from `data_dss`" function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "storage", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "storage") defaults = _apply_ordered_properties(_create_storage(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) @@ -794,6 +782,20 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< end +"Adds vsources to `data_eng` from `data_dss`" +function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + for (name, dss_obj) in get(data_dss, "vsource", Dict{<:Any,<:Any}()) + eng_obj = Dict{String,Any}() + + if !haskey(data_eng, "vsource") + data_eng["vsource"] = Dict{String,Dict{String,Any}}() + end + + data_eng["vsource"][name] = eng_obj + end +end + + "This function appends a component to a component dictionary of a pmd data model" function _push_dict_ret_key!(dict::Dict{String,<:Any}, v::Dict{String,<:Any}; assume_no_gaps::Bool=false) if isempty(dict) @@ -894,7 +896,11 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, parse_dss_with_dtypes!(data_dss, _dss_supported_components) - data_eng["dss_options"] = parse_options(get(data_dss, "options", Dict{String,Any}())) + data_dss["options"] = parse_options(get(data_dss, "options", Dict{String,Any}())) + + if import_all + data_eng["dss_options"] = data_dss["options"] + end if haskey(data_dss, "circuit") circuit = data_dss["circuit"] @@ -905,11 +911,11 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, data_eng["data_model"] = "engineering" # TODO rename fields - data_eng["settings"]["kv_kvar_scalar"] = 1 - data_eng["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))/data_eng["settings"]["v_var_scalar"] + data_eng["settings"]["kv_kvar_scalar"] = 1e-3 + data_eng["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))/data_eng["settings"]["kv_kvar_scalar"] data_eng["settings"]["set_vbase_bus"] = data_eng["sourcebus"] - data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1e3/data_eng["settings"]["v_var_scalar"] - data_eng["settings"]["basefreq"] = get(get(data_dss, "options", Dict()), "defaultbasefreq", 60.0) + data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1/data_eng["settings"]["kv_kvar_scalar"] + data_eng["settings"]["basefreq"] = get(data_dss["options"], "defaultbasefreq", 60.0) data_eng["files"] = data_dss["filename"] else @@ -933,12 +939,13 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, _dss2eng_capacitor!(data_eng, data_dss, import_all) _dss2eng_shunt_reactor!(data_eng, data_dss, import_all) + _dss2eng_sourcebus!(data_eng, data_dss, import_all) _dss2eng_generator!(data_eng, data_dss, import_all) - _dss2eng_pvsystem!(data_eng, data_dss, import_all) - _dss2eng_storage!(data_eng, data_dss, import_all) + # _dss2eng_vsource!(data_eng, data_dss, import_all) + if bank_transformers _bank_transformers!(data_eng) end From aecb71651593c652e58fe4bd8748e45599bb39c8 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 2 Mar 2020 16:16:08 -0700 Subject: [PATCH 044/224] WIP: data model mapping prototype --- src/core/data_model_mapping.jl | 623 ++++++++++++++++++++++++++++++--- 1 file changed, 579 insertions(+), 44 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index a3b9ed672..9891974a2 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -1,33 +1,555 @@ import LinearAlgebra + +const _1to1_maps = Dict{String,Array{String,1}}( + "bus" => ["vm", "va", "vmin", "vmax"], + "load" => ["pd", "qd", "model", "configuration", "status"], + "capacitor" => ["status"], + "shunt_reactor" => ["status"], + "generator" => ["configuration", "status"], + "pvsystem" => ["status"], + "storage" => ["status"], + "line" => [], + "switch" => [], + "transformer" => [], + "vsource" => [], +) + +const _extra_eng_data = Dict{String,Array{String,1}}( + "root" => ["sourcebus", "files", "dss_options", "settings"], + "bus" => ["grounded", "neutral", "awaiting_ground", "xg", "phases", "rg", "terminals"], + "load" => [], + "line" => ["f_connections", "t_connections", "linecode"], +) + + +const _node_elements = ["load", "capacitor", "shunt_reactor", "generator", "pvsystem", "storage", "vsource"] + +const _edge_elements = ["line", "switch", "transformer"] + # MAP DATA MODEL DOWN -function data_model_map!(data_model) +function _map_eng2math(data_eng; kron_reduced::Bool=true) + @assert get(data_eng, "data_model", "mathematical") == "engineering" + + data_model_make_pu!(data_eng) + + data_math = Dict{String,Any}( + "name" => data_eng["name"], + "per_unit" => get(data_eng, "per_unit", false) + ) + + data_math["map"] = Dict{Int,Dict{Symbol,Any}}( + 1 => Dict{Symbol,Any}( + :component_type => "root", + :unmap_function => :_map_math2eng_root!, + :extra => Dict{String,Any}((k,v) for (k,v) in data_eng if k in _extra_eng_data["root"]) + ) + ) + + data_math["settings"] = deepcopy(data_eng["settings"]) + + data_math["lookup"] = Dict{String,Dict{Any,Int}}() + + data_math["conductors"] = kron_reduced ? 3 : 4 + data_math["basekv"] = data_eng["settings"]["set_vbase_val"] + data_math["baseMVA"] = data_eng["settings"]["set_sbase_val"] + + _map_eng2math_bus!(data_math, data_eng; kron_reduced=kron_reduced) + + _map_eng2math_load!(data_math, data_eng; kron_reduced=kron_reduced) + + _map_eng2math_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) + + _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) + # _map_eng2math_pvsystem!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) + # _map_eng2math_vsource!(data_math, data_eng; kron_reduced=kron_reduced) + + _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) + + # _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) + + _map_eng2math_sourcebus!(data_math, data_eng; kron_reduced=kron_reduced) + + # # needs to happen before _expand_linecode, as it might contain a linecode for the internal impedance + # add_mappings!(data_eng, "decompose_voltage_source", _decompose_voltage_source!(data_eng)) + # _expand_linecode!(data_eng) + # # creates shunt of 4x4; disabled for now (incompatible 3-wire kron-reduced) + # #add_mappings!(data_eng, "load_to_shunt", _load_to_shunt!(data_eng)) + # add_mappings!(data_eng, "capacitor_to_shunt", _capacitor_to_shunt!(data_eng)) + # add_mappings!(data_eng, "decompose_transformer_nw", _decompose_transformer_nw!(data_eng)) + # add_mappings!(data_eng, "_lossy_ground_to_shunt", _lossy_ground_to_shunt!(data_eng)) + + # # add low level component types if not present yet + # for comp_type in ["load", "generator", "bus", "line", "shunt", "transformer", "storage", "switch"] + # if !haskey(data_eng, comp_type) + # data_eng[comp_type] = Dict{String, Any}() + # end + # end + + data_math["dcline"] = Dict{String,Any}() + + # data_math["per_unit"] = false + + # data_model_make_pu!(data_math) - !haskey(data_model, "mappings") + delete!(data_math, "lookup") - # needs to happen before _expand_linecode, as it might contain a linecode for the internal impedance - add_mappings!(data_model, "decompose_voltage_source", _decompose_voltage_source!(data_model)) - _expand_linecode!(data_model) - # creates shunt of 4x4; disabled for now (incompatible 3-wire kron-reduced) - #add_mappings!(data_model, "load_to_shunt", _load_to_shunt!(data_model)) - add_mappings!(data_model, "capacitor_to_shunt", _capacitor_to_shunt!(data_model)) - add_mappings!(data_model, "decompose_transformer_nw", _decompose_transformer_nw!(data_model)) - add_mappings!(data_model, "_lossy_ground_to_shunt", _lossy_ground_to_shunt!(data_model)) - # add low level component types if not present yet - for comp_type in ["load", "generator", "bus", "line", "shunt", "transformer_2wa", "storage", "switch"] - if !haskey(data_model, comp_type) - data_model[comp_type] = Dict{String, Any}() + data_math["data_model"] = "mathematical" + + return data_math +end + + +"" +function _map_defaults(eng_obj::Dict{String,Any}, component_type::String, component_name::Any, kron_reduced::Bool=true; phases::Vector{Int}=[1, 2, 3], neutral::Int=4)::Dict{String,Any} + math_obj = Dict{String,Any}() + + math_obj["name"] = component_name + + if component_type in _node_elements + math_obj["source_id"] = eng_obj["source_id"] + terminals = eng_obj["connections"] + elseif component_type in _edge_elements + f_terminals = eng_obj["f_connections"] + t_terminals = eng_obj["t_connections"] + elseif component_type == "bus" + terminals = eng_obj["terminals"] + end + + # TODO clean this up + for key in _1to1_maps[component_type] + if haskey(eng_obj, key) + if kron_reduced + if component_type == "bus" + terminals = eng_obj["terminals"] + math_obj[key] = eng_obj[key][terminals.!=neutral] + elseif component_type in _node_elements + math_obj[key] = eng_obj[key] + _pad_properties!(math_obj, [key], eng_obj["connections"], phases) + elseif component_type in _edge_elements + math_obj[key] = eng_obj[key] + # TODO + end + else + math_obj[key] = eng_obj[key] + end end end - data_model["data_model"] = "mathematical" + return math_obj +end + + +"" +function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "bus") + data_math["bus"] = Dict{String,Any}() + end + + for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) + + phases = get(eng_obj, "phases", [1, 2, 3]) + neutral = get(eng_obj, "neutral", 4) + terminals = eng_obj["terminals"] + + @assert all(t in [phases..., neutral] for t in terminals) + + math_obj = _map_defaults(eng_obj, "bus", name, kron_reduced; phases=phases, neutral=neutral) + + math_obj["vm"] = get(eng_obj, "vm", fill(1.0, length(phases))) + math_obj["va"] = get(eng_obj, "va", [_wrap_to_180(-rad2deg(2*pi/length(phases)*(i-1))) for i in phases]) + + math_obj["vmin"] = fill(NaN, length(phases)) + math_obj["vmax"] = fill(NaN, length(phases)) + + math_obj["base_kv"] = eng_obj["vbase"] + + math_obj["bus_type"] = eng_obj["status"] == 1 ? 1 : 4 + + math_obj["index"] = length(data_math["bus"]) + 1 + math_obj["bus_i"] = math_obj["index"] + + data_math["bus"][string(math_obj["index"])] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :component_type => "bus", + :from => name, + :to => "$(math_obj["index"])", + :unmap_function => :_map_math2eng_bus!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["bus"]) + ) + + if !haskey(data_math["lookup"], "bus") + data_math["lookup"]["bus"] = Dict{Any,Int}() + end + + data_math["lookup"]["bus"][name] = math_obj["index"] + end +end + + +"" +function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "load") + data_math["load"] = Dict{String,Any}() + end + + for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) + math_obj = _map_defaults(eng_obj, "load", name, kron_reduced) + + if eng_obj["configuration"] == "wye" + bus = data_eng["bus"][eng_obj["bus"]] + # TODO add message for failure + @assert length(bus["grounded"]) == 1 && bus["grounded"][1] == eng_obj["connections"][end] + else + # TODO add message for failure + @assert all(load["connections"] .== phases) + end + + math_obj["load_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + + math_obj["index"] = length(data_math["load"]) + 1 + + data_math["load"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :component_type => "load", + :from => name, + :to => "$(math_obj["index"])", + :unmap_function => :_map_math2eng_load!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["load"]) + ) + + if !haskey(data_math["lookup"], "load") + data_math["lookup"]["load"] = Dict{Any,Int}() + end + + data_math["lookup"]["load"][name] = math_obj["index"] + end +end + + +"" +function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "shunt") + data_math["shunt"] = Dict{String,Any}() + end + + for (name, eng_obj) in get(data_eng, "capacitor", Dict{Any,Dict{String,Any}}()) + math_obj = _map_defaults(eng_obj, "load", name, kron_reduced) + + math_obj["shunt_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + + math_obj["index"] = length(data_math["shunt"]) + 1 + + data_math["shunt"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :component_type => "capacitor", + :from => name, + :to => "$(math_obj["index"])", + :unmap_function => :_map_math2eng_load!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["load"]) + ) + + if !haskey(data_math["lookup"], "capacitor") + data_math["lookup"]["capacitor"] = Dict{Any,Int}() + end + + data_math["lookup"]["capacitor"][name] = math_obj["index"] + end +end + + +"" +function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "shunt") + data_math["shunt"] = Dict{String,Any}() + end + +end + + +"" +function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "gen") + data_math["gen"] = Dict{String,Any}() + end +end + + +"" +function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "gen") + data_math["gen"] = Dict{String,Any}() + end + +end + + +"" +function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "storage") + data_math["storage"] = Dict{String,Any}() + end +end + + +"" +function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "gen") + data_math["gen"] = Dict{String,Any}() + end + + +end + + +"" +function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "branch") + data_math["branch"] = Dict{String,Any}() + end + + for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) + if haskey(eng_obj, "linecode") + linecode = data_eng["linecode"][eng_obj["linecode"]] + + for property in ["rmatrix", "xmatrix", "cmatrix"] + if !haskey(eng_obj, property) && haskey(linecode, property) + eng_obj[property] = linecode[property] + end + end + end + + nphases = length(eng_obj["f_connections"]) + + math_obj = _map_defaults(eng_obj, "line", name, kron_reduced) + + math_obj["f_bus"] = data_math["lookup"]["bus"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["lookup"]["bus"][eng_obj["t_bus"]] + + math_obj["br_r"] = eng_obj["rmatrix"] * eng_obj["length"] + math_obj["br_x"] = eng_obj["xmatrix"] * eng_obj["length"] + + math_obj["g_fr"] = fill(0.0, nphases, nphases) + math_obj["g_to"] = fill(0.0, nphases, nphases) + + math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cmatrix"] * eng_obj["length"] / 1e9) / 2.0 + math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cmatrix"] * eng_obj["length"] / 1e9) / 2.0 + + math_obj["angmin"] = fill(-60.0, nphases) + math_obj["angmax"] = fill( 60.0, nphases) + + math_obj["transformer"] = false + math_obj["shift"] = zeros(nphases) + math_obj["tap"] = ones(nphases) + + math_obj["switch"] = false + + math_obj["br_status"] = eng_obj["status"] + + math_obj["index"] = length(data_math["branch"])+1 + + data_math["branch"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :component_type => "line", + :from => name, + :to => "$(math_obj["index"])", + :unmap_function => :_map_math2eng_load!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["line"]) + ) + + if !haskey(data_math["lookup"], "line") + data_math["lookup"]["line"] = Dict{Any,Int}() + end + + data_math["lookup"]["line"][name] = math_obj["index"] + + end +end + + +"" +function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "switch") + data_math["switch"] = Dict{String,Any}() + end +end + + +"" +function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + +end + + +"" +function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + if !haskey(data_math, "bus") + data_math["bus"] = Dict{String,Any}() + end + + if !haskey(data_math, "gen") + data_math["gen"] = Dict{String,Any}() + end + + if !haskey(data_math, "branch") + data_math["branch"] = Dict{String,Any}() + end + + sourcebus = data_eng["sourcebus"] + sourcebus_vsource = data_eng["voltage_source"][sourcebus] + + nconductors = data_math["conductors"] + + # TODO fix per unit problem + bus_obj = Dict{String,Any}( + "bus_i" => length(data_math["bus"])+1, + "index" => length(data_math["bus"])+1, + "name" => "_virtual_sourcebus", + "bus_type" => 3, + "vm" => sourcebus_vsource["vm"], + "va" => sourcebus_vsource["va"], + "vmin" => sourcebus_vsource["vm"], + "vmax" => sourcebus_vsource["vm"], + "basekv" => data_math["basekv"] + ) + + data_math["bus"]["$(bus_obj["index"])"] = bus_obj + + gen_obj = Dict{String,Any}( + "gen_bus" => bus_obj["bus_i"], + "name" => "_virtual_sourcebus", + "gen_status" => sourcebus_vsource["status"], + "pg" => fill(0.0, nconductors), + "qg" => fill(0.0, nconductors), + "model" => 2, + "startup" => 0.0, + "shutdown" => 0.0, + "ncost" => 3, + "cost" => [0.0, 1.0, 0.0], + "conn" => "wye", # TODO change name to configuration + "index" => length(data_math["gen"]) + 1, + "source_id" => "vsource._virtual_sourcebus" + ) + + data_math["gen"]["$(gen_obj["index"])"] = gen_obj + + vbase = data_math["basekv"] + sbase = data_math["baseMVA"] + zbase = vbase^2 / sbase / 3 + + branch_obj = Dict{String,Any}( + "name" => "_virtual_sourcebus", + "source_id" => "vsource._virtual_sourcebus", + "f_bus" => bus_obj["bus_i"], + "t_bus" => data_math["lookup"]["bus"][sourcebus], + "angmin" => fill(-60.0, nconductors), + "angmax" => fill( 60.0, nconductors), + "shift" => fill(0.0, nconductors), + "tap" => fill(1.0, nconductors), + "tranformer" => false, + "switch" => false, + "br_status" => 1, + "br_r" => sourcebus_vsource["rmatrix"]./zbase, + "br_x" => sourcebus_vsource["xmatrix"]./zbase, + "g_fr" => zeros(nconductors, nconductors), + "g_to" => zeros(nconductors, nconductors), + "b_fr" => zeros(nconductors, nconductors), + "b_to" => zeros(nconductors, nconductors), + "index" => length(data_math["branch"])+1 + ) + # branch_obj = _create_vbranch(data_math, data_math["lookup"]["bus"][sourcebus], bus_obj["bus_i"]; name="_virtual_sourcebus", br_r=sourcebus_vsource["rs"]/1e3, br_x=sourcebus_vsource["xs"]/1e3) + + data_math["branch"]["$(branch_obj["index"])"] = branch_obj +end + + +""" +This function adds a new branch to the data model and returns its dictionary. +It is virtual in the sense that it does not correspond to a branch in the +network, but is part of the decomposition of the transformer. +""" +function _create_vbranch(data_math::Dict{<:Any,<:Any}, f_bus::Int, t_bus::Int; name::String="", source_id::String="", active_phases::Vector{Int}=[1, 2, 3], kwargs...) + ncnd = data_math["conductors"] + + kwargs = Dict{Symbol,Any}(kwargs) + + vbase = haskey(kwargs, :vbase) ? kwargs[:vbase] : data_math["basekv"] + # TODO assumes per_unit will be flagged + sbase = haskey(kwargs, :sbase) ? kwargs[:sbase] : data_math["baseMVA"] + zbase = vbase^2/sbase + # convert to LN vbase in instead of LL vbase + zbase *= (1/3) + + vbranch = Dict{String, Any}("f_bus"=>f_bus, "t_bus"=>t_bus, "name"=>name) + + vbranch["active_phases"] = active_phases + vbranch["source_id"] = "virtual_branch.$name" + + for k in [:br_r, :br_x, :g_fr, :g_to, :b_fr, :b_to] + if !haskey(kwargs, k) + vbranch[string(k)] = zeros(ncnd, ncnd) + else + if k in [:br_r, :br_x] + vbranch[string(k)] = kwargs[k]./zbase + else + vbranch[string(k)] = kwargs[k].*zbase + end + end + end + + vbranch["angmin"] = -ones(ncnd)*60 + vbranch["angmax"] = ones(ncnd)*60 + + vbranch["rate_a"] = get(kwargs, :rate_a, fill(Inf, length(active_phases))) + + vbranch["shift"] = zeros(ncnd) + vbranch["tap"] = ones(ncnd) + + vbranch["transformer"] = false + vbranch["switch"] = false + vbranch["br_status"] = 1 + + for k in [:rate_a, :rate_b, :rate_c, :c_rating_a, :c_rating_b, :c_rating_c] + if haskey(kwargs, k) + vbranch[string(k)] = kwargs[k] + end + end + + vbranch["index"] = length(data_math["branch"])+1 + + return vbranch +end + + +"" +function _map_math2eng!(data_math) + @assert get(data_math, "data_model", "mathematical") == "mathematical" "Cannot map data to engineering model: provided data is not a mathematical model" + @assert haskey(data_math, "map") "Cannot map data to engineering model: no mapping from mathematical to engineering data model is provided" + + data_eng = Dict{<:Any,<:Any}() + + map_keys = sort(keys(data_math["map"]); reverse=true) + for map in map_keys + # TODO + end - return data_model end +"" function _expand_linecode!(data_model) # expand line codes for (id, line) in data_model["line"] @@ -44,6 +566,7 @@ function _expand_linecode!(data_model) end +"" function _lossy_ground_to_shunt!(data_model) mappings = [] @@ -66,6 +589,7 @@ function _lossy_ground_to_shunt!(data_model) end +"" function _load_to_shunt!(data_model) mappings = [] if haskey(data_model, "load") @@ -104,6 +628,7 @@ function _load_to_shunt!(data_model) end +"" function _capacitor_to_shunt!(data_model) mappings = [] @@ -147,6 +672,7 @@ function _capacitor_to_shunt!(data_model) end +"" function _decompose_voltage_source!(data_model) mappings = [] @@ -202,9 +728,6 @@ end """ - - function decompose_transformer_nw_lossy!(data_model) - Replaces complex transformers with a composition of ideal transformers and lines which model losses. New buses (virtual, no physical meaning) are added. """ @@ -214,8 +737,8 @@ function _decompose_transformer_nw!(data_model) if haskey(data_model, "transformer_nw") for (tr_id, trans) in data_model["transformer_nw"] - vnom = trans["vnom"]*data_model["settings"]["v_var_scalar"] - snom = trans["snom"]*data_model["settings"]["v_var_scalar"] + vnom = trans["vnom"]*data_model["settings"]["kv_kvar_scalar"] + snom = trans["snom"]*data_model["settings"]["kv_kvar_scalar"] nrw = length(trans["bus"]) @@ -230,7 +753,7 @@ function _decompose_transformer_nw!(data_model) b_sh = -(trans["imag"]*snom[1])/vnom[1]^2 # data is measured externally, but we now refer it to the internal side - ratios = vnom/data_model["settings"]["v_var_scalar"] + ratios = vnom/data_model["settings"]["kv_kvar_scalar"] x_sc = x_sc./ratios[1]^2 r_s = r_s./ratios.^2 g_sh = g_sh*ratios[1]^2 @@ -247,7 +770,7 @@ function _decompose_transformer_nw!(data_model) # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] - trans_2wa = add_virtual!(data_model, "transformer_2wa", Dict( + trans_2wa = add_virtual!(data_model, "transformer", Dict( "f_bus" => trans["bus"][w], "t_bus" => trans_t_bus_w[w], "tm_nom" => tm_nom, @@ -329,6 +852,7 @@ function _sc2br_impedance(Zsc) end +"" function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) # precompute the minimal set of buses and lines N = length(r_s) @@ -425,32 +949,41 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) return bus_ids, line_ids, [bus_ids[bus] for bus in tr_t_bus] end + +"" function _alias!(dict, fr, to) if haskey(dict, fr) dict[to] = dict[fr] end end -function _pad_props!(comp, keys, phases_comp, phases_all) - pos = Dict((x,i) for (i,x) in enumerate(phases_all)) - inds = [pos[x] for x in phases_comp] - for prop in keys - if haskey(comp, prop) - if isa(comp[prop], Vector) - tmp = zeros(length(phases_all)) - tmp[inds] = comp[prop] - comp[prop] = tmp - elseif isa(comp[prop], Matrix) - tmp = zeros(length(phases_all), length(phases_all)) - tmp[inds, inds] = comp[prop] - comp[prop] = tmp - else - error("Property is not a vector or matrix!") + +"" +function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; neutral::Int=4, kron_reduced::Bool=true) + if kron_reduced + pos = Dict((x,i) for (i,x) in enumerate(phases)) + inds = [pos[x] for x in connections[connections.!=neutral]] + else + # TODO + end + + for property in properties + if haskey(object, property) + if isa(object[property], Vector) + tmp = zeros(length(phases)) + tmp[inds] = object[property] + object[property] = tmp + elseif isa(object[property], Matrix) + tmp = zeros(length(phases), length(phases)) + tmp[inds, inds] = object[property] + object[property] = tmp end end end end + +"" function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) data_model["conductors"] = 3 data_model["buspairs"] = nothing @@ -474,7 +1007,7 @@ function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) bus = data_model["bus"][string(load["bus"])] @assert(length(bus["grounded"])==1 && bus["grounded"][1]==load["connections"][end]) load["connections"] = load["connections"][1:end-1] - _pad_props!(load, ["pd", "qd"], load["connections"], phases) + _pad_properties!(load, ["pd", "qd"], load["connections"], phases) else # three-phase loads can only be delta-connected #@assert(all(load["connections"].==phases)) @@ -509,7 +1042,7 @@ function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) @assert(all(x in phases for x in br["t_connections"])) @assert(all(br["f_connections"].==br["t_connections"])) - _pad_props!(br, ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to", "s_rating", "c_rating"], br["f_connections"], phases) + _pad_properties!(br, ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to", "s_rating", "c_rating"], br["f_connections"], phases) # rename _alias!(br, "status", "br_status") @@ -528,25 +1061,26 @@ function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) for (_, shunt) in data_model["shunt"] @assert(all(x in phases for x in shunt["connections"])) - _pad_props!(shunt, ["g_sh", "b_sh"], shunt["connections"], phases) + _pad_properties!(shunt, ["g_sh", "b_sh"], shunt["connections"], phases) _alias!(shunt, "bus", "shunt_bus") _alias!(shunt, "g_sh", "gs") _alias!(shunt, "b_sh", "bs") end data_model["dcline"] = Dict() - data_model["transformer"] = data_model["transformer_2wa"] + data_model["transformer"] = data_model["transformer"] data_model["per_unit"] = true - data_model["baseMVA"] = data_model["settings"]["sbase"]*data_model["settings"]["v_var_scalar"]/1E6 + data_model["baseMVA"] = data_model["settings"]["sbase"]*data_model["settings"]["kv_kvar_scalar"]/1E6 data_model["name"] = "IDC" return data_model end -# MAP SOLUTION UP +# MAP SOLUTION UP +"" function solution_unmap!(solution::Dict, data_model::Dict) for (name, data) in reverse(data_model["mappings"]) if name=="decompose_transformer_nw" @@ -590,6 +1124,7 @@ function solution_unmap!(solution::Dict, data_model::Dict) end +"" function transform_solution!(solution, data_model) solution_make_si!(solution, data_model) solution_identify!(solution, data_model) From 555a87d46f58b130139ec67e8972bb9cb61e7a8e Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 09:22:01 -0700 Subject: [PATCH 045/224] REF: _discover_buses FIX: units for _dss2eng_sourcebus! should be in degress, not radians REF: Rename rmatrix etc to rs etc --- src/io/opendss_dm.jl | 132 +++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 74 deletions(-) diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index f594fe3af..8f0c6d69f 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -3,45 +3,36 @@ import LinearAlgebra: isdiag, diag, pinv const _exclude_duplicate_check = ["options", "filename", "circuit"] + const _dss_edge_components = ["line", "transformer", "reactor"] + const _dss_supported_components = ["line", "linecode", "load", "generator", "capacitor", "reactor", "circuit", "transformer", "pvsystem", "storage", "loadshape"] -const _dss_option_dtypes = Dict{String,Type}("defaultbasefreq" => Float64, "voltagebases" => Float64, "tolerance" => Float64) + +const _dss_option_dtypes = Dict{String,Type}( + "defaultbasefreq" => Float64, + "voltagebases" => Float64, + "tolerance" => Float64) "Discovers all of the buses (not separately defined in OpenDSS), from \"lines\"" -function _discover_buses(data_dss::Dict{String,<:Any})::Array - bus_names = [] - buses = [] +function _discover_buses(data_dss::Dict{String,<:Any})::Set + buses = Set([]) for obj_type in _dss_edge_components - if haskey(data_dss, obj_type) - dss_objs = data_dss[obj_type] - for dss_obj in values(dss_objs) - if obj_type == "transformer" - dss_obj = _create_transformer(dss_obj["name"]; _to_sym_keys(dss_obj)...) - for bus in dss_obj["buses"] - name, nodes = _parse_busname(bus) - if !(name in bus_names) - push!(bus_names, name) - push!(buses, (name, nodes)) - end - end - elseif haskey(dss_obj, "bus2") - for key in ["bus1", "bus2"] - name, nodes = _parse_busname(dss_obj[key]) - if !(name in bus_names) - push!(bus_names, name) - push!(buses, (name, nodes)) - end - end + for (name, dss_obj) in get(data_dss, obj_type, Dict{String,Any}()) + if obj_type == "transformer" + dss_obj = _create_transformer(dss_obj["name"]; _to_sym_keys(dss_obj)...) + for bus in dss_obj["buses"] + push!(buses, split(bus, '.'; limit=2)[1]) + end + elseif haskey(dss_obj, "bus2") + for key in ["bus1", "bus2"] + push!(buses, split(dss_obj[key], '.'; limit=2)[1]) end end end end - if length(buses) == 0 - Memento.error(_LOGGER, "data_dss has no edge components!") - else - return buses - end + + return buses end @@ -60,9 +51,9 @@ end "Adds nodes as buses to `data_eng` from `data_dss`" function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) buses = _discover_buses(data_dss) - for (n, (bus, nodes)) in enumerate(buses) - @assert(!(length(bus)>=8 && bus[1:8]=="_virtual"), "Bus $bus: identifiers should not start with _virtual.") + for bus in buses + @assert !startswith(bus, "_virtual") "Bus $bus: identifiers should not start with _virtual" add_bus!(data_eng, bus; status=1, bus_type=1) end @@ -71,7 +62,6 @@ end "Adds sourcebus as a voltage source to `data_eng` from `data_dss`" function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) - # create virtual sourcebus circuit = _create_vsource(get(data_dss["circuit"], "bus1", "sourcebus"), data_dss["circuit"]["name"]; _to_sym_keys(data_dss["circuit"])...) nodes = Array{Bool}([1 1 1 0]) @@ -81,15 +71,10 @@ function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String phases = circuit["phases"] vnom = data_eng["settings"]["set_vbase_val"] - vm = fill(vm_pu, 3)*vnom - va = _wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases]) + vm = fill(vm_pu, phases)*vnom + va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) - add_voltage_source!(data_eng, "sourcebus"; bus="sourcebus", connections=collect(1:phases), vm=vm, va=va, rmatrix=circuit["rmatrix"], xmatrix=circuit["xmatrix"]) -end - - -"Adds voltage sources to `data_eng` from `data_dss`" -function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) + add_voltage_source!(data_eng, "sourcebus"; bus="sourcebus", connections=collect(1:phases), vm=vm, va=va, rs=circuit["rmatrix"], xs=circuit["xmatrix"]) end @@ -119,7 +104,21 @@ function _dss2eng_loadshape!(data_eng::Dict{String,<:Any}, data_dss::Dict{String end -"Adds loads to `data_eng` from `data_dss`" +""" +Adds loads to `data_eng` from `data_dss` + +Constant can still be scaled by other settings, fixed cannot +Note that in the current feature set, fixed therefore equals constant + +1: Constant P and Q, default +2: Constant Z +3: Constant P and quadratic Q +4: Exponential +5: Constant I +6: Constant P and fixed Q +# 7: Constant P and quadratic Q (i.e., fixed reactance) +# 8: ZIP +""" function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, ground_terminal::Int=4) for (name, dss_obj) in get(data_dss, "load", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "load") @@ -127,46 +126,30 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An # parse the model model = defaults["model"] - # some info on OpenDSS load models - ################################## - # Constant can still be scaled by other settings, fixed cannot - # Note that in the current feature set, fixed therefore equals constant - # 1: Constant P and Q, default - if model == 2 - # 2: Constant Z - elseif model == 3 - # 3: Constant P and quadratic Q + + if model == 3 Memento.warn(_LOGGER, "$load_name: load model 3 not supported. Treating as model 1.") model = 1 elseif model == 4 - # 4: Exponential Memento.warn(_LOGGER, "$load_name: load model 4 not supported. Treating as model 1.") model = 1 - elseif model == 5 - # 5: Constant I - #warn(_LOGGER, "$name: load model 5 not supported. Treating as model 1.") - #model = 1 elseif model == 6 - # 6: Constant P and fixed Q Memento.warn(_LOGGER, "$load_name: load model 6 identical to model 1 in current feature set. Treating as model 1.") model = 1 elseif model == 7 - # 7: Constant P and quadratic Q (i.e., fixed reactance) Memento.warn(_LOGGER, "$load_name: load model 7 not supported. Treating as model 1.") model = 1 elseif model == 8 - # 8: ZIP Memento.warn(_LOGGER, "$load_name: load model 8 not supported. Treating as model 1.") model = 1 end - # save adjusted model type to dict, human-readable + model_int2str = Dict(1=>"constant_power", 2=>"constant_impedance", 5=>"constant_current") model = model_int2str[model] nphases = defaults["phases"] conf = defaults["conn"] - # connections bus = _parse_busname(defaults["bus1"])[1] @@ -186,6 +169,7 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An if !haskey(data_eng["bus"][bus], "awaiting_ground") data_eng["bus"][bus]["awaiting_ground"] = [] end + push!(data_eng["bus"][bus]["awaiting_ground"], eng_obj) end @@ -227,13 +211,13 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String nphases = defaults["phases"] - dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") - conn = dyz_map[defaults["conn"]] + conn = defaults["conn"] bus_name = _parse_busname(defaults["bus1"])[1] bus2_name = _parse_busname(defaults["bus2"])[1] + if bus_name!=bus2_name - Memento.error("Capacitor $(name): bus1 and bus2 should connect to the same bus.") + Memento.error(_LOGGER, "Capacitor $(name): bus1 and bus2 should connect to the same bus.") end f_terminals = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) @@ -320,7 +304,7 @@ end Given a vector and a list of elements to find, this method will return a list of the positions of the elements in that vector. """ -function _get_idxs(vec::Array{<:Any,1}, els::Array{<:Any,1}) +function _get_idxs(vec::Vector{<:Any}, els::Vector{<:Any})::Vector{Int} ret = Array{Int, 1}(undef, length(els)) for (i,f) in enumerate(els) for (j,l) in enumerate(vec) @@ -437,9 +421,9 @@ function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, nphases = size(defaults["rmatrix"])[1] - eng_obj["rmatrix"] = reshape(defaults["rmatrix"], nphases, nphases) - eng_obj["xmatrix"] = reshape(defaults["xmatrix"], nphases, nphases) - eng_obj["cmatrix"] = reshape(defaults["cmatrix"], nphases, nphases) + eng_obj["rs"] = reshape(defaults["rmatrix"], nphases, nphases) + eng_obj["xs"] = reshape(defaults["xmatrix"], nphases, nphases) + eng_obj["cs"] = reshape(defaults["cmatrix"], nphases, nphases) if !haskey(data_eng, "linecode") data_eng["linecode"] = Dict{String,Any}() @@ -477,9 +461,9 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["linecode"] = dss_obj["linecode"] end - for key in ["rmatrix", "xmatrix", "cmatrix"] - if haskey(dss_obj, key) - eng_obj[key] = reshape(defaults[key], nphases, nphases) + for (assign, property) in zip(["rs", "xs", "cs"], ["rmatrix", "xmatrix", "cmatrix"]) + if haskey(dss_obj, property) + eng_obj[assign] = reshape(defaults[property], nphases, nphases) end end @@ -911,10 +895,10 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, data_eng["data_model"] = "engineering" # TODO rename fields - data_eng["settings"]["kv_kvar_scalar"] = 1e-3 - data_eng["settings"]["set_vbase_val"] = (defaults["basekv"]/sqrt(3))/data_eng["settings"]["kv_kvar_scalar"] + # data_eng["settings"]["kv_kvar_scalar"] = 1e-3 + data_eng["settings"]["set_vbase_val"] = defaults["basekv"] data_eng["settings"]["set_vbase_bus"] = data_eng["sourcebus"] - data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1/data_eng["settings"]["kv_kvar_scalar"] + data_eng["settings"]["set_sbase_val"] = defaults["basemva"] data_eng["settings"]["basefreq"] = get(data_dss["options"], "defaultbasefreq", 60.0) data_eng["files"] = data_dss["filename"] @@ -944,7 +928,7 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, _dss2eng_pvsystem!(data_eng, data_dss, import_all) _dss2eng_storage!(data_eng, data_dss, import_all) - # _dss2eng_vsource!(data_eng, data_dss, import_all) + _dss2eng_vsource!(data_eng, data_dss, import_all) if bank_transformers _bank_transformers!(data_eng) From 8a06c5356ffa89f05f87f8af5d85e9177e3f5a18 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 09:22:16 -0700 Subject: [PATCH 046/224] DOC: move data_model.md to docs --- docs/src/data_model.md | 193 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 docs/src/data_model.md diff --git a/docs/src/data_model.md b/docs/src/data_model.md new file mode 100644 index 000000000..751cfe81d --- /dev/null +++ b/docs/src/data_model.md @@ -0,0 +1,193 @@ + +# Shared patterns + +- Each component has a unique (amonst components of the same type) identifier `id`. +- Everything is defined in SI units, except when a base is explicitly mentioned in the description. +- The default sometimes only mentions the default element, not the default value (e.g. `true` and not `fill(fill(true, 3), 3)`) + +# Data model + +## Bus + +The data model below allows us to include buses of arbitrary many terminals (i.e., more than the usual four). This would be useful for + +- underground lines with multiple neutrals which are not joined at every bus; +- distribution lines that carry several conventional lines in parallel (see for example the quad circuits in NEVTestCase). + +| name | default | type | description | +| --------- | --------- | ------------- | --------------------------------------------- | +| terminals | [1,2,3,4] | Vector | | +| vm_max | / | Vector | maximum conductor-to-ground voltage magnitude | +| vm_min | / | Vector | minimum conductor-to-ground voltage magnitude | +| vm_cd_max | / | Vector{Tuple} | e.g. [(1,2,210)] means \|U1-U2\|>210 | +| vm_cd_min | / | Vector{Tuple} | e.g. [(1,2,230)] means \|U1-U2\|<230 | +| grounded | [] | Vector | a list of terminals which are grounded | +| rg | [] | Vector | resistance of each defined grounding | +| xg | [] | Vector | reactance of each defined grounding | + +The tricky part is how to encode bounds for these type of buses. The most general is defining a list of three-tupples. Take for example a typical bus in a three-phase, four-wire network, where `terminals=[a,b,c,n]`. Such a bus might have + +- phase-to-neutral bounds `vm_pn_max=250`, `vm_pn_min=210` +- and phase-to-phase bounds `vm_pp_max=440`, `vm_pp_min=360`. + +We can then define this equivalently as + +- `vm_cd_max = [(a,n,250), (b,n,250), (c,n,250), (a,b,440), (b,c,440), (c,a,440)]` +- `vm_cd_min = [(a,n,210), (b,n,210), (c,n,210), (a,b,360), (b,c,360), (c,a,360)]` + +Since this might be confusing for novice users, we also allow the user to define bounds through the following properties. + + +| name | default | type | description | +| --------- | ------- | ------ | --------------------------------------------------------- | +| id | / | | unique identifier | +| phases | [1,2,3] | Vector | | +| neutral | 4 | | maximum conductor-to-ground voltage magnitude | +| vm_pn_max | / | Real | maximum phase-to-neutral voltage magnitude for all phases | +| vm_pn_min | / | Real | minimum phase-to-neutral voltage magnitude for all phases | +| vm_pp_max | / | Real | maximum phase-to-phase voltage magnitude for all phases | +| vm_pp_min | / | Real | minimum phase-to-phase voltage magnitude for all phases | + +## Line + +This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` and `g_to`; when these properties are additionally specified, they overwrite the one supplied through the linecode. + +| name | default | type | description | +| ------------- | ------- | ---- | ------------------------------------------------------------------------ | +| id | / | | unique identifier | +| f_bus | / | | | +| f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | +| t_bus | / | | | +| t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | +| linecode | / | | a linecode | +| rs | / | | series resistance matrix, size of n_conductors x n_conductors | +| xs | / | | series reactance matrix, size of n_conductors x n_conductors | +| g_fr | / | | from-side conductance | +| b_fr | / | | from-side susceptance | +| g_to | / | | to-side conductance | +| b_to | / | | to-side susceptance | +| c_rating | / | | symmetrically applicable current rating | +| s_rating | / | | symmetrically applicable power rating | + +## Linecode + +- Should the linecode also include a `c_rating` and/`s_rating`? + +| name | default | type | description | +| ---- | ------- | ---- | ------------------------------------ | +| id | / | | unique identifier | +| rs | / | | series resistance matrix | +| xs | / | | series reactance matrix n_conductors | +| g_fr | / | | from-side conductance | +| b_fr | / | | from-side susceptance | +| g_to | / | | to-side conductance | +| b_to | / | | to-side susceptance | + +## Shunt + +| name | default | type | description | +| ----------- | ------- | ---- | ---------------------------------------------------------- | +| id | / | | unique identifier | +| bus | / | | | +| connections | / | | | +| g | / | | conductance, size should be \|connections\|x\|connections\ | +| b | / | | susceptance, size should be \|connections\|x\|connections\ | + +## Capacitor + +| name | default | type | description | +| ----------- | ------- | ---- | ---------------------------------------------------------- | +| id | / | | unique identifier | +| bus | / | | | +| connections | / | | | +| qd_ref | / | | conductance, size should be \|connections\|x\|connections\ | +| vnom | / | | conductance, size should be \|connections\|x\|connections\ | + +## Load + +| name | default | type | description | +| ------------- | ------- | ------------ | --------------------------------------------------------------- | +| id | / | | unique identifier | +| bus | / | | | +| connections | / | | | +| configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | +| model | / | | indicates the type of voltage-dependency | + +### `model=constant_power` + +| name | default | type | description | +| ---- | ------- | ------------ | ----------- | +| pd | / | Vector{Real} | | +| qd | / | Vector{Real} | | + +### `model=constant_current/impedance` + +| name | default | type | description | +| ------ | ------- | ------------ | ----------- | +| pd_ref | / | Vector{Real} | | +| qd_ref | / | Vector{Real} | | +| vnom | / | Real | | + +### `model=exponential` + +| name | default | type | description | +| ------ | ------- | ------------ | ----------- | +| pd_ref | / | Vector{Real} | | +| qd_ref | / | Vector{Real} | | +| vnom | / | Real | | +| exp_p | / | Vector{Real} | | +| exp_q | / | Vector{Real} | | + +## Generator + +| name | default | type | description | +| ------------- | ------- | ------------ | --------------------------------------------------------------- | +| id | / | | unique identifier | +| bus | / | | | +| connections | / | | | +| configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | +| pg_min | / | | lower bound on active power generation per phase | +| pg_max | / | | upper bound on active power generation per phase | +| qg_min | / | | lower bound on reactive power generation per phase | +| qg_max | / | | upper bound on reactive power generation per phase | + +## Assymetric, Lossless, Two-Winding (AL2W) Transformer + +These are transformers are assymetric (A), lossless (L) and two-winding (2W). Assymetric refers to the fact that the secondary is always has a `wye` configuration, whilst the primary can be `delta`. + +| name | default | type | description | +| ------------- | ---------------------- | ------------ | ------------------------------------------------------------------------ | +| id | / | | unique identifier | +| n_phases | size(rs)[1] | Int>0 | number of phases | +| f_bus | / | | | +| f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | +| t_bus | / | | | +| t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | +| configuration | / | {wye, delta} | for the from-side; the to-side is always connected in wye | +| tm_nom | / | Real | nominal tap ratio for the transformer | +| tm_max | / | Vector | maximum tap ratio for each phase (base=`tm_nom`) | +| tm_min | / | Vector | minimum tap ratio for each phase (base=`tm_nom`) | +| tm_set | fill(1.0, `n_phases`) | Vector | set tap ratio for each phase (base=`tm_nom`) | +| tm_fix | fill(true, `n_phases`) | Vector | indicates for each phase whether the tap ratio is fixed | + +TODO: add tm stuff + +## Transformer + +These are n-winding, n-phase, lossy transformers. Note that most properties are now Vectors (or Vectors of Vectors), indexed over the windings. + +| name | default | type | description | +| -------------- | ----------- | -------------------- | -------------------------------------------------------------------------------------------------------------- | +| id | / | | unique identifier | +| n_phases | size(rs)[1] | Int>0 | number of phases | +| n_windings | size(rs)[1] | Int>0 | number of windings | +| bus | / | Vector | list of bus for each winding | +| connections | | Vector{Vector} | list of connection for each winding | +| configurations | | Vector{{wye, delta}} | list of configuration for each winding | +| xsc | 0.0 | Vector | list of short-circuit reactances between each pair of windings; enter as a list of the upper-triangle elements | +| rs | 0.0 | Vector | list of the winding resistance for each winding | +| tm_nom | / | Vector{Real} | nominal tap ratio for the transformer | +| tm_max | / | Vector{Vector} | maximum tap ratio for each winding and phase (base=`tm_nom`) | +| tm_min | / | Vector{Vector} | minimum tap ratio for for each winding and phase (base=`tm_nom`) | +| tm_set | 1.0 | Vector{Vector} | set tap ratio for each winding and phase (base=`tm_nom`) | +| tm_fix | true | Vector{Vector} | indicates for each winding and phase whether the tap ratio is fixed | From 3f95e9669c6e02737512330be961e24de355490a Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 09:22:46 -0700 Subject: [PATCH 047/224] FIX: duplicate includes --- src/PowerModelsDistribution.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 37fd8ae8f..744bca09f 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -42,13 +42,10 @@ module PowerModelsDistribution include("io/common_dm.jl") include("io/opendss_dm.jl") - include("io/common.jl") include("io/json.jl") include("io/dss_parse.jl") include("io/dss_structs.jl") - include("io/common_dm.jl") - include("io/opendss_dm.jl") include("io/data_model_components.jl") include("core/data_model_mapping.jl") include("core/data_model_pu.jl") From c41cec79a5a48965b8d04346f39c7120ac5bac5d Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 09:23:14 -0700 Subject: [PATCH 048/224] RM: old io/common.jl --- src/core/data_model_mapping.jl | 44 +++++--- src/io/common.jl | 45 -------- src/io/common_dm.jl | 23 +++- src/io/data_model.md | 193 --------------------------------- 4 files changed, 49 insertions(+), 256 deletions(-) delete mode 100644 src/io/common.jl delete mode 100644 src/io/data_model.md diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index 9891974a2..03a0f9b69 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -3,7 +3,7 @@ import LinearAlgebra const _1to1_maps = Dict{String,Array{String,1}}( "bus" => ["vm", "va", "vmin", "vmax"], - "load" => ["pd", "qd", "model", "configuration", "status"], + "load" => ["model", "configuration", "status"], "capacitor" => ["status"], "shunt_reactor" => ["status"], "generator" => ["configuration", "status"], @@ -32,11 +32,11 @@ const _edge_elements = ["line", "switch", "transformer"] function _map_eng2math(data_eng; kron_reduced::Bool=true) @assert get(data_eng, "data_model", "mathematical") == "engineering" - data_model_make_pu!(data_eng) + # data_model_make_pu!(data_eng) data_math = Dict{String,Any}( "name" => data_eng["name"], - "per_unit" => get(data_eng, "per_unit", false) + # "per_unit" => get(data_eng, "per_unit", false) ) data_math["map"] = Dict{Int,Dict{Symbol,Any}}( @@ -164,10 +164,10 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, math_obj["vm"] = get(eng_obj, "vm", fill(1.0, length(phases))) math_obj["va"] = get(eng_obj, "va", [_wrap_to_180(-rad2deg(2*pi/length(phases)*(i-1))) for i in phases]) - math_obj["vmin"] = fill(NaN, length(phases)) - math_obj["vmax"] = fill(NaN, length(phases)) + math_obj["vmin"] = fill(0.9, length(phases)) + math_obj["vmax"] = fill(1.1, length(phases)) - math_obj["base_kv"] = eng_obj["vbase"] + math_obj["base_kv"] = data_eng["settings"]["set_vbase_val"] math_obj["bus_type"] = eng_obj["status"] == 1 ? 1 : 4 @@ -203,6 +203,8 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) math_obj = _map_defaults(eng_obj, "load", name, kron_reduced) + phases = get(eng_obj, "phases", [1, 2, 3]) + if eng_obj["configuration"] == "wye" bus = data_eng["bus"][eng_obj["bus"]] # TODO add message for failure @@ -212,6 +214,12 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any @assert all(load["connections"] .== phases) end + _pad_properties!(eng_obj, ["pd", "qd"], eng_obj["connections"], phases) + + math_obj["pd"] = eng_obj["pd"] / 1e3 + math_obj["qd"] = eng_obj["qd"] / 1e3 + math_obj["vnom_kv"] = get(eng_obj, "vnom", data_eng["settings"]["set_vbase_val"]/sqrt(3)*1e3) + math_obj["load_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] math_obj["index"] = length(data_math["load"]) + 1 @@ -323,7 +331,7 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any if haskey(eng_obj, "linecode") linecode = data_eng["linecode"][eng_obj["linecode"]] - for property in ["rmatrix", "xmatrix", "cmatrix"] + for property in ["rs", "xs", "cs"] if !haskey(eng_obj, property) && haskey(linecode, property) eng_obj[property] = linecode[property] end @@ -331,20 +339,24 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any end nphases = length(eng_obj["f_connections"]) + nconductors = data_math["conductors"] + + Zbase = (data_math["basekv"] / sqrt(3))^2 * nconductors / (data_math["baseMVA"]) + Zbase = Zbase / 3 math_obj = _map_defaults(eng_obj, "line", name, kron_reduced) math_obj["f_bus"] = data_math["lookup"]["bus"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["lookup"]["bus"][eng_obj["t_bus"]] - math_obj["br_r"] = eng_obj["rmatrix"] * eng_obj["length"] - math_obj["br_x"] = eng_obj["xmatrix"] * eng_obj["length"] + math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] / Zbase + math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] / Zbase math_obj["g_fr"] = fill(0.0, nphases, nphases) math_obj["g_to"] = fill(0.0, nphases, nphases) - math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cmatrix"] * eng_obj["length"] / 1e9) / 2.0 - math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cmatrix"] * eng_obj["length"] / 1e9) / 2.0 + math_obj["b_fr"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0 + math_obj["b_to"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0 math_obj["angmin"] = fill(-60.0, nphases) math_obj["angmax"] = fill( 60.0, nphases) @@ -419,10 +431,10 @@ function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{ "index" => length(data_math["bus"])+1, "name" => "_virtual_sourcebus", "bus_type" => 3, - "vm" => sourcebus_vsource["vm"], + "vm" => sourcebus_vsource["vm"] ./ data_eng["settings"]["set_vbase_val"], "va" => sourcebus_vsource["va"], - "vmin" => sourcebus_vsource["vm"], - "vmax" => sourcebus_vsource["vm"], + "vmin" => sourcebus_vsource["vm"] ./ data_eng["settings"]["set_vbase_val"], + "vmax" => sourcebus_vsource["vm"] ./ data_eng["settings"]["set_vbase_val"], "basekv" => data_math["basekv"] ) @@ -462,8 +474,8 @@ function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{ "tranformer" => false, "switch" => false, "br_status" => 1, - "br_r" => sourcebus_vsource["rmatrix"]./zbase, - "br_x" => sourcebus_vsource["xmatrix"]./zbase, + "br_r" => sourcebus_vsource["rs"]./zbase, + "br_x" => sourcebus_vsource["xs"]./zbase, "g_fr" => zeros(nconductors, nconductors), "g_to" => zeros(nconductors, nconductors), "b_fr" => zeros(nconductors, nconductors), diff --git a/src/io/common.jl b/src/io/common.jl deleted file mode 100644 index 2035e7472..000000000 --- a/src/io/common.jl +++ /dev/null @@ -1,45 +0,0 @@ -""" - parse_file(io) - -Parses the IOStream of a file into a Three-Phase PowerModels data structure. -""" -function parse_file(io::IO; import_all::Bool=false, vmin::Float64=0.9, vmax::Float64=1.1, filetype::AbstractString="json", bank_transformers::Bool=true) - if filetype == "dss" - Memento.warn(_LOGGER, "Not all OpenDSS features are supported, currently only minimal support for lines, loads, generators, and capacitors as shunts. Transformers and reactors as transformer branches are included, but value translation is not fully supported.") - pmd_data = PowerModelsDistribution.parse_opendss(io; import_all=import_all, vmin=vmin, vmax=vmax, bank_transformers=bank_transformers) - elseif filetype == "json" - pmd_data = PowerModelsDistribution.parse_json(io; validate=false) - else - Memento.error(_LOGGER, "only .m and .dss files are supported") - end - - correct_network_data!(pmd_data) - - return pmd_data -end - - -"" -function parse_file(file::String; kwargs...) - pmd_data = open(file) do io - parse_file(io; filetype=split(lowercase(file), '.')[end], kwargs...) - end - return pmd_data -end - - -"" -function correct_network_data!(data::Dict{String,Any}) - _PMs.make_per_unit!(data) - - _PMs.check_connectivity(data) - _PMs.correct_transformer_parameters!(data) - _PMs.correct_voltage_angle_differences!(data) - _PMs.correct_thermal_limits!(data) - _PMs.correct_branch_directions!(data) - _PMs.check_branch_loops(data) - _PMs.correct_bus_types!(data) - _PMs.correct_dcline_limits!(data) - _PMs.correct_cost_functions!(data) - _PMs.standardize_cost_terms!(data) -end diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl index 3bcab5e55..b7646a89e 100644 --- a/src/io/common_dm.jl +++ b/src/io/common_dm.jl @@ -17,9 +17,9 @@ function parse_file_dm(io::IO; data_model::String="engineering", import_all::Boo if data_model == "mathematical" pmd_data = transform_data_model(pmd_data) - end - #correct_network_data!(pmd_data) + correct_network_data!(pmd_data) + end return pmd_data end @@ -48,3 +48,22 @@ function transform_data_model(data::Dict{<:Any,<:Any}) return data end end + + +"" +function correct_network_data!(data::Dict{String,Any}) + @warn "here" data["bus"]["1"] data["branch"]["1"] + _PMs.make_per_unit!(data) + @warn "here" data["bus"]["1"] data["branch"]["1"] + + _PMs.check_connectivity(data) + _PMs.correct_transformer_parameters!(data) + _PMs.correct_voltage_angle_differences!(data) + _PMs.correct_thermal_limits!(data) + _PMs.correct_branch_directions!(data) + _PMs.check_branch_loops(data) + _PMs.correct_bus_types!(data) + _PMs.correct_dcline_limits!(data) + _PMs.correct_cost_functions!(data) + _PMs.standardize_cost_terms!(data) +end diff --git a/src/io/data_model.md b/src/io/data_model.md deleted file mode 100644 index 751cfe81d..000000000 --- a/src/io/data_model.md +++ /dev/null @@ -1,193 +0,0 @@ - -# Shared patterns - -- Each component has a unique (amonst components of the same type) identifier `id`. -- Everything is defined in SI units, except when a base is explicitly mentioned in the description. -- The default sometimes only mentions the default element, not the default value (e.g. `true` and not `fill(fill(true, 3), 3)`) - -# Data model - -## Bus - -The data model below allows us to include buses of arbitrary many terminals (i.e., more than the usual four). This would be useful for - -- underground lines with multiple neutrals which are not joined at every bus; -- distribution lines that carry several conventional lines in parallel (see for example the quad circuits in NEVTestCase). - -| name | default | type | description | -| --------- | --------- | ------------- | --------------------------------------------- | -| terminals | [1,2,3,4] | Vector | | -| vm_max | / | Vector | maximum conductor-to-ground voltage magnitude | -| vm_min | / | Vector | minimum conductor-to-ground voltage magnitude | -| vm_cd_max | / | Vector{Tuple} | e.g. [(1,2,210)] means \|U1-U2\|>210 | -| vm_cd_min | / | Vector{Tuple} | e.g. [(1,2,230)] means \|U1-U2\|<230 | -| grounded | [] | Vector | a list of terminals which are grounded | -| rg | [] | Vector | resistance of each defined grounding | -| xg | [] | Vector | reactance of each defined grounding | - -The tricky part is how to encode bounds for these type of buses. The most general is defining a list of three-tupples. Take for example a typical bus in a three-phase, four-wire network, where `terminals=[a,b,c,n]`. Such a bus might have - -- phase-to-neutral bounds `vm_pn_max=250`, `vm_pn_min=210` -- and phase-to-phase bounds `vm_pp_max=440`, `vm_pp_min=360`. - -We can then define this equivalently as - -- `vm_cd_max = [(a,n,250), (b,n,250), (c,n,250), (a,b,440), (b,c,440), (c,a,440)]` -- `vm_cd_min = [(a,n,210), (b,n,210), (c,n,210), (a,b,360), (b,c,360), (c,a,360)]` - -Since this might be confusing for novice users, we also allow the user to define bounds through the following properties. - - -| name | default | type | description | -| --------- | ------- | ------ | --------------------------------------------------------- | -| id | / | | unique identifier | -| phases | [1,2,3] | Vector | | -| neutral | 4 | | maximum conductor-to-ground voltage magnitude | -| vm_pn_max | / | Real | maximum phase-to-neutral voltage magnitude for all phases | -| vm_pn_min | / | Real | minimum phase-to-neutral voltage magnitude for all phases | -| vm_pp_max | / | Real | maximum phase-to-phase voltage magnitude for all phases | -| vm_pp_min | / | Real | minimum phase-to-phase voltage magnitude for all phases | - -## Line - -This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` and `g_to`; when these properties are additionally specified, they overwrite the one supplied through the linecode. - -| name | default | type | description | -| ------------- | ------- | ---- | ------------------------------------------------------------------------ | -| id | / | | unique identifier | -| f_bus | / | | | -| f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | -| t_bus | / | | | -| t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | -| linecode | / | | a linecode | -| rs | / | | series resistance matrix, size of n_conductors x n_conductors | -| xs | / | | series reactance matrix, size of n_conductors x n_conductors | -| g_fr | / | | from-side conductance | -| b_fr | / | | from-side susceptance | -| g_to | / | | to-side conductance | -| b_to | / | | to-side susceptance | -| c_rating | / | | symmetrically applicable current rating | -| s_rating | / | | symmetrically applicable power rating | - -## Linecode - -- Should the linecode also include a `c_rating` and/`s_rating`? - -| name | default | type | description | -| ---- | ------- | ---- | ------------------------------------ | -| id | / | | unique identifier | -| rs | / | | series resistance matrix | -| xs | / | | series reactance matrix n_conductors | -| g_fr | / | | from-side conductance | -| b_fr | / | | from-side susceptance | -| g_to | / | | to-side conductance | -| b_to | / | | to-side susceptance | - -## Shunt - -| name | default | type | description | -| ----------- | ------- | ---- | ---------------------------------------------------------- | -| id | / | | unique identifier | -| bus | / | | | -| connections | / | | | -| g | / | | conductance, size should be \|connections\|x\|connections\ | -| b | / | | susceptance, size should be \|connections\|x\|connections\ | - -## Capacitor - -| name | default | type | description | -| ----------- | ------- | ---- | ---------------------------------------------------------- | -| id | / | | unique identifier | -| bus | / | | | -| connections | / | | | -| qd_ref | / | | conductance, size should be \|connections\|x\|connections\ | -| vnom | / | | conductance, size should be \|connections\|x\|connections\ | - -## Load - -| name | default | type | description | -| ------------- | ------- | ------------ | --------------------------------------------------------------- | -| id | / | | unique identifier | -| bus | / | | | -| connections | / | | | -| configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | -| model | / | | indicates the type of voltage-dependency | - -### `model=constant_power` - -| name | default | type | description | -| ---- | ------- | ------------ | ----------- | -| pd | / | Vector{Real} | | -| qd | / | Vector{Real} | | - -### `model=constant_current/impedance` - -| name | default | type | description | -| ------ | ------- | ------------ | ----------- | -| pd_ref | / | Vector{Real} | | -| qd_ref | / | Vector{Real} | | -| vnom | / | Real | | - -### `model=exponential` - -| name | default | type | description | -| ------ | ------- | ------------ | ----------- | -| pd_ref | / | Vector{Real} | | -| qd_ref | / | Vector{Real} | | -| vnom | / | Real | | -| exp_p | / | Vector{Real} | | -| exp_q | / | Vector{Real} | | - -## Generator - -| name | default | type | description | -| ------------- | ------- | ------------ | --------------------------------------------------------------- | -| id | / | | unique identifier | -| bus | / | | | -| connections | / | | | -| configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | -| pg_min | / | | lower bound on active power generation per phase | -| pg_max | / | | upper bound on active power generation per phase | -| qg_min | / | | lower bound on reactive power generation per phase | -| qg_max | / | | upper bound on reactive power generation per phase | - -## Assymetric, Lossless, Two-Winding (AL2W) Transformer - -These are transformers are assymetric (A), lossless (L) and two-winding (2W). Assymetric refers to the fact that the secondary is always has a `wye` configuration, whilst the primary can be `delta`. - -| name | default | type | description | -| ------------- | ---------------------- | ------------ | ------------------------------------------------------------------------ | -| id | / | | unique identifier | -| n_phases | size(rs)[1] | Int>0 | number of phases | -| f_bus | / | | | -| f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | -| t_bus | / | | | -| t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | -| configuration | / | {wye, delta} | for the from-side; the to-side is always connected in wye | -| tm_nom | / | Real | nominal tap ratio for the transformer | -| tm_max | / | Vector | maximum tap ratio for each phase (base=`tm_nom`) | -| tm_min | / | Vector | minimum tap ratio for each phase (base=`tm_nom`) | -| tm_set | fill(1.0, `n_phases`) | Vector | set tap ratio for each phase (base=`tm_nom`) | -| tm_fix | fill(true, `n_phases`) | Vector | indicates for each phase whether the tap ratio is fixed | - -TODO: add tm stuff - -## Transformer - -These are n-winding, n-phase, lossy transformers. Note that most properties are now Vectors (or Vectors of Vectors), indexed over the windings. - -| name | default | type | description | -| -------------- | ----------- | -------------------- | -------------------------------------------------------------------------------------------------------------- | -| id | / | | unique identifier | -| n_phases | size(rs)[1] | Int>0 | number of phases | -| n_windings | size(rs)[1] | Int>0 | number of windings | -| bus | / | Vector | list of bus for each winding | -| connections | | Vector{Vector} | list of connection for each winding | -| configurations | | Vector{{wye, delta}} | list of configuration for each winding | -| xsc | 0.0 | Vector | list of short-circuit reactances between each pair of windings; enter as a list of the upper-triangle elements | -| rs | 0.0 | Vector | list of the winding resistance for each winding | -| tm_nom | / | Vector{Real} | nominal tap ratio for the transformer | -| tm_max | / | Vector{Vector} | maximum tap ratio for each winding and phase (base=`tm_nom`) | -| tm_min | / | Vector{Vector} | minimum tap ratio for for each winding and phase (base=`tm_nom`) | -| tm_set | 1.0 | Vector{Vector} | set tap ratio for each winding and phase (base=`tm_nom`) | -| tm_fix | true | Vector{Vector} | indicates for each winding and phase whether the tap ratio is fixed | From a8afec56673af2f46ecd8c30ed69c27e4a26ce11 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 11:23:58 -0700 Subject: [PATCH 049/224] FIX: generators, pvsystems, storage --- src/core/data_model_mapping.jl | 287 ++++++++++++++++++++++++++++----- src/io/opendss_dm.jl | 150 +++++++---------- 2 files changed, 309 insertions(+), 128 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index 03a0f9b69..5817e69eb 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -1,25 +1,35 @@ import LinearAlgebra -const _1to1_maps = Dict{String,Array{String,1}}( +const _1to1_maps = Dict{String,Vector{String}}( "bus" => ["vm", "va", "vmin", "vmax"], "load" => ["model", "configuration", "status"], "capacitor" => ["status"], "shunt_reactor" => ["status"], - "generator" => ["configuration", "status"], - "pvsystem" => ["status"], - "storage" => ["status"], + "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], + "pvsystem" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], + "storage" => ["status", "source_id"], "line" => [], "switch" => [], + "line_reactor" => [], "transformer" => [], - "vsource" => [], + "voltage_source" => [], ) -const _extra_eng_data = Dict{String,Array{String,1}}( +const _extra_eng_data = Dict{String,Vector{String}}( "root" => ["sourcebus", "files", "dss_options", "settings"], "bus" => ["grounded", "neutral", "awaiting_ground", "xg", "phases", "rg", "terminals"], "load" => [], + "capacitor" => [], + "shunt_reactor" => [], + "generator" => ["control_model"], + "pvsystem" => [], + "storage" => [], "line" => ["f_connections", "t_connections", "linecode"], + "switch" => [], + "line_reactor" => [], + "transformer" => [], + "voltage_source" => [], ) @@ -32,11 +42,9 @@ const _edge_elements = ["line", "switch", "transformer"] function _map_eng2math(data_eng; kron_reduced::Bool=true) @assert get(data_eng, "data_model", "mathematical") == "engineering" - # data_model_make_pu!(data_eng) - data_math = Dict{String,Any}( "name" => data_eng["name"], - # "per_unit" => get(data_eng, "per_unit", false) + "per_unit" => get(data_eng, "per_unit", false) ) data_math["map"] = Dict{Int,Dict{Symbol,Any}}( @@ -63,9 +71,9 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) - # _map_eng2math_pvsystem!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_pvsystem!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) - # _map_eng2math_vsource!(data_math, data_eng; kron_reduced=kron_reduced) + # _map_eng2math_vsource!(data_math, data_eng; kron_reduced=kron_reduced) # TODO _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) @@ -74,28 +82,8 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_sourcebus!(data_math, data_eng; kron_reduced=kron_reduced) - # # needs to happen before _expand_linecode, as it might contain a linecode for the internal impedance - # add_mappings!(data_eng, "decompose_voltage_source", _decompose_voltage_source!(data_eng)) - # _expand_linecode!(data_eng) - # # creates shunt of 4x4; disabled for now (incompatible 3-wire kron-reduced) - # #add_mappings!(data_eng, "load_to_shunt", _load_to_shunt!(data_eng)) - # add_mappings!(data_eng, "capacitor_to_shunt", _capacitor_to_shunt!(data_eng)) - # add_mappings!(data_eng, "decompose_transformer_nw", _decompose_transformer_nw!(data_eng)) - # add_mappings!(data_eng, "_lossy_ground_to_shunt", _lossy_ground_to_shunt!(data_eng)) - - # # add low level component types if not present yet - # for comp_type in ["load", "generator", "bus", "line", "shunt", "transformer", "storage", "switch"] - # if !haskey(data_eng, comp_type) - # data_eng[comp_type] = Dict{String, Any}() - # end - # end - data_math["dcline"] = Dict{String,Any}() - # data_math["per_unit"] = false - - # data_model_make_pu!(data_math) - delete!(data_math, "lookup") @@ -153,6 +141,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) + # TODO fix vnom phases = get(eng_obj, "phases", [1, 2, 3]) neutral = get(eng_obj, "neutral", 4) terminals = eng_obj["terminals"] @@ -251,10 +240,46 @@ function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{ end for (name, eng_obj) in get(data_eng, "capacitor", Dict{Any,Dict{String,Any}}()) - math_obj = _map_defaults(eng_obj, "load", name, kron_reduced) + math_obj = Dict{String,Any}( + "status" => eng_obj["status"] + ) + # TODO change to new capacitor shunt calc logic math_obj["shunt_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + nphases = eng_obj["phases"] + + vnom_ln = eng_obj["kv"] + # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) + if eng_obj["phases"] > 1 + vnom_ln = vnom_ln/sqrt(3) + end + # 'kvar' is specified for all phases at once; we want the per-phase one, in MVar + qnom = (eng_obj["kvar"]/1E3)/nphases + # indexing qnom[1] is a dirty fix to support both kvar=[x] and kvar=x + # TODO fix this in a clear way, in dss_structs.jl + b_cap = qnom[1]/vnom_ln^2 + # get the base addmittance, with a LN voltage base + Sbase = 1 # not yet pmd_data["baseMVA"] because this is done in _PMs.make_per_unit + Ybase_ln = Sbase/(data_math["basekv"]/sqrt(3))^2 + # now convent b_cap to per unit + b_cap_pu = b_cap/Ybase_ln + + b = fill(b_cap_pu, nphases) + N = length(b) + + if eng_obj["configuration"] == "wye" + B = LinearAlgebra.diagm(0=>b) + else # shunt["conn"]=="delta" + # create delta transformation matrix Md + Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) + Md[N,1] = -1 + B = Md'*LinearAlgebra.diagm(0=>b)*Md + end + + math_obj["gs"] = fill(0.0, nphases, nphases) + math_obj["bs"] = B + math_obj["index"] = length(data_math["shunt"]) + 1 data_math["shunt"]["$(math_obj["index"])"] = math_obj @@ -263,9 +288,9 @@ function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{ :component_type => "capacitor", :from => name, :to => "$(math_obj["index"])", - :unmap_function => :_map_math2eng_load!, + :unmap_function => :_map_math2eng_capacitor!, :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["load"]) + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["capacitor"]) ) if !haskey(data_math["lookup"], "capacitor") @@ -291,6 +316,56 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ if !haskey(data_math, "gen") data_math["gen"] = Dict{String,Any}() end + + for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) + math_obj = Dict{String,Any}() + + phases = eng_obj["phases"] + + math_obj["gen_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + math_obj["name"] = name + math_obj["gen_status"] = eng_obj["status"] + + math_obj["pg"] = fill(eng_obj["kw"] / 1e3 / phases, phases) + math_obj["qg"] = fill(eng_obj["kvar"] / 1e3 / phases, phases) + math_obj["vg"] = fill(eng_obj["kv"] / data_math["basekv"]) + + math_obj["qmin"] = fill(eng_obj["kvar_min"] / 1e3 / phases, phases) + math_obj["qmax"] = fill(eng_obj["kvar_max"] / 1e3 / phases, phases) + + math_obj["pmax"] = fill(eng_obj["kw"] / 1e3 / phases, phases) + math_obj["pmin"] = fill(0.0, phases) + + math_obj["conn"] = eng_obj["configuration"] + + for key in _1to1_maps["generator"] + math_obj[key] = eng_obj[key] + end + + # if PV generator mode convert attached bus to PV bus + if eng_obj["control_model"] == 3 + data_math["bus"][data_math["lookup"]["bus"][eng_obj["bus"]]]["bus_type"] = 2 + end + + math_obj["index"] = length(data_math["gen"]) + 1 + + data_math["gen"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :component_type => "generator", + :from => name, + :to => "$(math_obj["index"])", + :unmap_function => :_map_math2eng_generator!, + :kron_reduced => kron_reduced, + # :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["generator"]) + ) + + if !haskey(data_math["lookup"], "generator") + data_math["lookup"]["generator"] = Dict{Any,Int}() + end + + data_math["lookup"]["generator"][name] = math_obj["index"] + end end @@ -300,6 +375,50 @@ function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{< data_math["gen"] = Dict{String,Any}() end + for (name, eng_obj) in get(data_eng, "pvsystem", Dict{String,Any}()) + math_obj = Dict{String,Any}() + + phases = eng_obj["phases"] + + math_obj["name"] = name + math_obj["gen_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] + + math_obj["pg"] = fill(eng_obj["kva"] / 1e3 / phases, phases) + math_obj["qg"] = fill(eng_obj["kva"] / 1e3 / phases, phases) + math_obj["vg"] = fill(eng_obj["kv"] / data_math["basekv"], phases) + + math_obj["pmin"] = fill(0.0, phases) + math_obj["pmax"] = fill(eng_obj["kva"] / 1e3 / phases, phases) + + math_obj["qmin"] = fill(-eng_obj["kva"] / 1e3 / phases, phases) + math_obj["qmax"] = fill( eng_obj["kva"] / 1e3 / phases, phases) + + for key in _1to1_maps["pvsystem"] + math_obj[key] = eng_obj[key] + end + + math_obj["conn"] = eng_obj["configuration"] + + math_obj["index"] = length(data_math["gen"]) + 1 + + data_math["gen"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :component_type => "pvsystem", + :from => name, + :to => "$(math_obj["index"])", + :unmap_function => :_map_math2eng_pvsystem!, + :kron_reduced => kron_reduced, + # :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["pvsystem"]) + ) + + if !haskey(data_math["lookup"], "pvsystem") + data_math["lookup"]["pvsystem"] = Dict{Any,Int}() + end + + data_math["lookup"]["pvsystem"][name] = math_obj["index"] + end end @@ -308,6 +427,56 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: if !haskey(data_math, "storage") data_math["storage"] = Dict{String,Any}() end + + for (name, eng_obj) in get(data_eng, "storage", Dict{String,Any}()) + math_obj = Dict{String,Any}() + + phases = eng_obj["phases"] + + math_obj["name"] = name + math_obj["storage_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + + math_obj["energy"] = eng_obj["kwhstored"] / 1e3 + math_obj["energy_rating"] = eng_obj["kwhrated"] / 1e3 + math_obj["charge_rating"] = eng_obj["%charge"] * eng_obj["kwrated"] / 1e3 / 100.0 + math_obj["discharge_rating"] = eng_obj["%discharge"] * eng_obj["kwrated"] / 1e3 / 100.0 + math_obj["charge_efficiency"] = eng_obj["%effcharge"] / 100.0 + math_obj["discharge_efficiency"] = eng_obj["%effdischarge"] / 100.0 + math_obj["thermal_rating"] = fill(eng_obj["kva"] / 1e3 / phases, phases) + math_obj["qmin"] = fill(-eng_obj["kvar"] / 1e3 / phases, phases) + math_obj["qmax"] = fill( eng_obj["kvar"] / 1e3 / phases, phases) + math_obj["r"] = fill(eng_obj["%r"] / 100.0, phases) + math_obj["x"] = fill(eng_obj["%x"] / 100.0, phases) + math_obj["p_loss"] = eng_obj["%idlingkw"] * eng_obj["kwrated"] / 1e3 + math_obj["q_loss"] = eng_obj["%idlingkvar"] * eng_obj["kvar"] / 1e3 + + math_obj["ps"] = fill(0.0, phases) + math_obj["qs"] = fill(0.0, phases) + + for key in _1to1_maps["storage"] + math_obj[key] = eng_obj[key] + end + + math_obj["index"] = length(data_math["storage"]) + 1 + + data_math["storage"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :component_type => "storage", + :from => name, + :to => "$(math_obj["index"])", + :unmap_function => :_map_math2eng_storage!, + :kron_reduced => kron_reduced, + # :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["storage"]) + ) + + if !haskey(data_math["lookup"], "storage") + data_math["lookup"]["storage"] = Dict{Any,Int}() + end + + data_math["lookup"]["storage"][name] = math_obj["index"] + + end end @@ -749,8 +918,8 @@ function _decompose_transformer_nw!(data_model) if haskey(data_model, "transformer_nw") for (tr_id, trans) in data_model["transformer_nw"] - vnom = trans["vnom"]*data_model["settings"]["kv_kvar_scalar"] - snom = trans["snom"]*data_model["settings"]["kv_kvar_scalar"] + vnom = trans["vnom"]*data_model["settings"]["v_var_scalar"] + snom = trans["snom"]*data_model["settings"]["v_var_scalar"] nrw = length(trans["bus"]) @@ -765,7 +934,7 @@ function _decompose_transformer_nw!(data_model) b_sh = -(trans["imag"]*snom[1])/vnom[1]^2 # data is measured externally, but we now refer it to the internal side - ratios = vnom/data_model["settings"]["kv_kvar_scalar"] + ratios = vnom/data_model["settings"]["v_var_scalar"] x_sc = x_sc./ratios[1]^2 r_s = r_s./ratios.^2 g_sh = g_sh*ratios[1]^2 @@ -1083,7 +1252,7 @@ function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) data_model["transformer"] = data_model["transformer"] data_model["per_unit"] = true - data_model["baseMVA"] = data_model["settings"]["sbase"]*data_model["settings"]["kv_kvar_scalar"]/1E6 + data_model["baseMVA"] = data_model["settings"]["sbase"]*data_model["settings"]["v_var_scalar"]/1E6 data_model["name"] = "IDC" @@ -1142,3 +1311,45 @@ function transform_solution!(solution, data_model) solution_identify!(solution, data_model) solution_unmap!(solution, data_model) end + + +""" +Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the +conductors 't_cnds', this method will return a list of conductors 'cnd' and a +matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. +""" +function calc_shunt(f_cnds::Vector{Int}, t_cnds::Vector{Int}, y::Vector{Float64}) + cnds = unique([f_cnds..., t_cnds...]) + e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) + Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) + return (cnds, Y) +end + + + +""" +Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this +method will calculate the reduced addmittance matrix if terminal 'ground' is +grounded. +""" +function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{Float64}, ground::Int) + # TODO add types + if ground in cnds + cndsr = setdiff(cnds, ground) + cndsr_inds = _get_idxs(cnds, cndsr) + Yr = Y[cndsr_inds, cndsr_inds] + return (cndsr, Yr) + else + return cnds, Y + end +end + + +"" +function _rm_floating_cnd(cnds::Vector{Int}, Y::Matrix{Float64}, f::Int) + P = setdiff(cnds, f) + f_inds = _get_idxs(cnds, [f]) + P_inds = _get_idxs(cnds, P) + Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] + return (P,Yrm) +end diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 8f0c6d69f..8749d9d40 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -55,7 +55,13 @@ function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any for bus in buses @assert !startswith(bus, "_virtual") "Bus $bus: identifiers should not start with _virtual" - add_bus!(data_eng, bus; status=1, bus_type=1) + eng_obj = create_bus(status=1, bus_type=1) + + if !haskey(data_eng, "bus") + data_eng["bus"] = Dict{String,Any}() + end + + data_eng["bus"][bus] = eng_obj end end @@ -229,23 +235,23 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String t_terminals = [f_terminals[2:end]..., f_terminals[1]] end - # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) - #TODO figure out for more than 3 phases - vnom_ln = defaults["kv"] - if defaults["phases"] in [2,3] - vnom_ln = vnom_ln/sqrt(3) - end - # 'kvar' is specified for all phases at once; we want the per-phase one, in MVar - qnom = (defaults["kvar"]/1E3)/nphases - b = fill(qnom/vnom_ln^2, nphases) + eng_obj = Dict{String,Any}() - # convert to a shunt matrix - terminals, B = calc_shunt(f_terminals, t_terminals, b) + eng_obj["bus"] = bus_name - # if one terminal is ground (0), reduce shunt addmittance matrix - terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) + eng_obj["configuration"] = conn - eng_obj = create_shunt(status=convert(Int, defaults["enabled"]), bus=bus_name, connections=terminals, g_sh=fill(0.0, size(B)...), b_sh=B) + eng_obj["f_terminals"] = f_terminals + eng_obj["t_terminals"] = t_terminals + + eng_obj["kv"] = defaults["kv"] + eng_obj["kvar"] = defaults["kvar"] + + eng_obj["phases"] = defaults["phases"] + + eng_obj["status"] = convert(Int, defaults["enabled"]) + + eng_obj["source_id"] = "capacitor.$name" if import_all _import_all!(eng_obj, defaults, dss_obj["prop_order"]) @@ -317,50 +323,6 @@ function _get_idxs(vec::Vector{<:Any}, els::Vector{<:Any})::Vector{Int} end -""" -Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the -conductors 't_cnds', this method will return a list of conductors 'cnd' and a -matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. -""" -function calc_shunt(f_cnds, t_cnds, y) - # TODO add types - cnds = unique([f_cnds..., t_cnds...]) - e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) - Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) - return (cnds, Y) -end - - - -""" -Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this -method will calculate the reduced addmittance matrix if terminal 'ground' is -grounded. -""" -function _calc_ground_shunt_admittance_matrix(cnds, Y, ground) - # TODO add types - if ground in cnds - cndsr = setdiff(cnds, ground) - cndsr_inds = _get_idxs(cnds, cndsr) - Yr = Y[cndsr_inds, cndsr_inds] - return (cndsr, Yr) - else - return cnds, Y - end -end - - -"" -function _rm_floating_cnd(cnds, Y, f) - # TODO add types - P = setdiff(cnds, f) - f_inds = _get_idxs(cnds, [f]) - P_inds = _get_idxs(cnds, P) - Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] - return (P,Yrm) -end - - "Adds generators to `data_eng` from `data_dss`" function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "generator", Dict{String,Any}()) @@ -373,16 +335,14 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] - eng_obj["pg"] = defaults["kw"] - eng_obj["qg"] = defaults["kvar"] - eng_obj["vg"] = defaults["kv"] + eng_obj["kw"] = defaults["kw"] + eng_obj["kvar"] = defaults["kvar"] + eng_obj["kv"] = defaults["kv"] - eng_obj["control_model"] = defaults["model"] + eng_obj["kvar_min"] = defaults["minkvar"] + eng_obj["kvar_max"] = defaults["maxkvar"] - # if PV generator mode convert attached bus to PV bus - # if eng_obj["control_model"] == 3 - # data_eng["bus"][eng_obj["bus"]]["bus_type"] = 2 - # end + eng_obj["control_model"] = defaults["model"] eng_obj["model"] = 2 eng_obj["startup"] = 0.0 @@ -390,6 +350,8 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String eng_obj["ncost"] = 3 eng_obj["cost"] = [0.0, 1.0, 0.0] + eng_obj["configuration"] = "wye" + eng_obj["status"] = convert(Int, defaults["enabled"]) eng_obj["source_id"] = "generator.$(name)" @@ -461,10 +423,16 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["linecode"] = dss_obj["linecode"] end - for (assign, property) in zip(["rs", "xs", "cs"], ["rmatrix", "xmatrix", "cmatrix"]) - if haskey(dss_obj, property) - eng_obj[assign] = reshape(defaults[property], nphases, nphases) - end + if any(haskey(dss_obj, key) for key in ["r0", "r1", "rg", "rmatrix"]) || !haskey(dss_obj, "linecode") + eng_obj["rs"] = reshape(defaults["rmatrix"], nphases, nphases) + end + + if any(haskey(dss_obj, key) for key in ["x0", "x1", "xg", "xmatrix"]) || !haskey(dss_obj, "linecode") + eng_obj["xs"] = reshape(defaults["xmatrix"], nphases, nphases) + end + + if any(haskey(dss_obj, key) for key in ["b0", "b1", "c0", "c1", "cmatrix"]) || !haskey(dss_obj, "linecode") + eng_obj["cs"] = reshape(defaults["cmatrix"], nphases, nphases) end eng_obj["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) @@ -692,9 +660,8 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["phases"] = defaults["phases"] - eng_obj["ppv"] = defaults["kva"] - eng_obj["qpv"] = defaults["kva"] - eng_obj["vpv"] = defaults["kv"] + eng_obj["kva"] = defaults["kva"] + eng_obj["kv"] = defaults["kv"] eng_obj["model"] = 2 eng_obj["startup"] = 0.0 @@ -702,6 +669,8 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["ncost"] = 3 eng_obj["cost"] = [0.0, 1.0, 0.0] + eng_obj["configuration"] = "wye" + eng_obj["status"] = convert(Int, defaults["enabled"]) eng_obj["source_id"] = "pvsystem.$(name)" @@ -732,24 +701,23 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< eng_obj["name"] = name eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] - eng_obj["energy"] = defaults["kwhstored"] - eng_obj["energy_rating"] = defaults["kwhrated"] - eng_obj["charge_rating"] = defaults["%charge"] * defaults["kwrated"] / 100.0 - eng_obj["discharge_rating"] = defaults["%discharge"] * defaults["kwrated"] / 100.0 - eng_obj["charge_efficiency"] = defaults["%effcharge"] / 100.0 - eng_obj["discharge_efficiency"] = defaults["%effdischarge"] / 100.0 - eng_obj["thermal_rating"] = defaults["kva"] - eng_obj["qmin"] = -defaults["kvar"] - eng_obj["qmax"] = defaults["kvar"] - eng_obj["r"] = defaults["%r"] / 100.0 - eng_obj["x"] = defaults["%x"] / 100.0 - eng_obj["p_loss"] = defaults["%idlingkw"] * defaults["kwrated"] - eng_obj["q_loss"] = defaults["%idlingkvar"] * defaults["kvar"] + eng_obj["kwhstored"] = defaults["kwhstored"] + eng_obj["kwhrated"] = defaults["kwhrated"] + eng_obj["kwrated"] = defaults["kwrated"] + + eng_obj["%charge"] = defaults["%charge"] + eng_obj["%discharge"] = defaults["%discharge"] + eng_obj["%effcharge"] = defaults["%effcharge"] + eng_obj["%effdischarge"] = defaults["%effdischarge"] + eng_obj["kva"] = defaults["kva"] + eng_obj["kvar"] = defaults["kvar"] + eng_obj["%r"] = defaults["%r"] + eng_obj["%x"] = defaults["%x"] + eng_obj["%idlingkw"] = defaults["%idlingkw"] + eng_obj["%idlingkvar"] = defaults["%idlingkvar"] eng_obj["status"] = convert(Int, defaults["enabled"]) - eng_obj["ps"] = 0.0 - eng_obj["qs"] = 0.0 eng_obj["source_id"] = "storage.$(name)" @@ -895,7 +863,9 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, data_eng["data_model"] = "engineering" # TODO rename fields - # data_eng["settings"]["kv_kvar_scalar"] = 1e-3 + # TODO fix scale factors + data_eng["settings"]["v_var_scalar"] = 1e3 + # data_eng["settings"]["set_vbase_val"] = defaults["basekv"] / sqrt(3) * 1e3 data_eng["settings"]["set_vbase_val"] = defaults["basekv"] data_eng["settings"]["set_vbase_bus"] = data_eng["sourcebus"] data_eng["settings"]["set_sbase_val"] = defaults["basemva"] From c8a418d31a2bf930831cfc73a4e4df76a1822e8b Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 11:24:55 -0700 Subject: [PATCH 050/224] REF: parse_file_dm -> parse_file --- src/io/common_dm.jl | 8 +++----- test/transformer.jl | 26 +++++++++++++------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/io/common_dm.jl b/src/io/common_dm.jl index b7646a89e..5d06ecd04 100644 --- a/src/io/common_dm.jl +++ b/src/io/common_dm.jl @@ -3,7 +3,7 @@ Parses the IOStream of a file into a Three-Phase PowerModels data structure. """ -function parse_file_dm(io::IO; data_model::String="engineering", import_all::Bool=false, filetype::AbstractString="json", bank_transformers::Bool=true) +function parse_file(io::IO; data_model::String="mathematical", import_all::Bool=false, filetype::AbstractString="json", bank_transformers::Bool=true) if filetype == "m" pmd_data = PowerModelsDistribution.parse_matlab(io) elseif filetype == "dss" @@ -26,9 +26,9 @@ end "" -function parse_file_dm(file::String; kwargs...) +function parse_file(file::String; kwargs...) pmd_data = open(file) do io - parse_file_dm(io; filetype=split(lowercase(file), '.')[end], kwargs...) + parse_file(io; filetype=split(lowercase(file), '.')[end], kwargs...) end return pmd_data @@ -52,9 +52,7 @@ end "" function correct_network_data!(data::Dict{String,Any}) - @warn "here" data["bus"]["1"] data["branch"]["1"] _PMs.make_per_unit!(data) - @warn "here" data["bus"]["1"] data["branch"]["1"] _PMs.check_connectivity(data) _PMs.correct_transformer_parameters!(data) diff --git a/test/transformer.jl b/test/transformer.jl index 7d0c0d52f..1b796d3de 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -5,7 +5,7 @@ @testset "test transformer acp pf" begin @testset "2w transformer acp pf yy" begin file = "../test/data/opendss/ut_trans_2w_yy.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) solution_identify!(sol["solution"], pmd_data) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 @@ -14,7 +14,7 @@ @testset "2w transformer acp pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) solution_identify!(sol["solution"], pmd_data) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 @@ -23,7 +23,7 @@ @testset "2w transformer acp pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) solution_identify!(sol["solution"], pmd_data) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 @@ -34,7 +34,7 @@ @testset "test transformer ivr pf" begin @testset "2w transformer ivr pf yy" begin file = "../test/data/opendss/ut_trans_2w_yy.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) solution_identify!(sol["solution"], pmd_data) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 @@ -43,7 +43,7 @@ @testset "2w transformer ivr pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) solution_identify!(sol["solution"], pmd_data) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 @@ -52,7 +52,7 @@ @testset "2w transformer ivr pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) solution_identify!(sol["solution"], pmd_data) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 @@ -63,8 +63,8 @@ # @testset "2w transformer ac pf yy - banked transformers" begin # file = "../test/data/opendss/ut_trans_2w_yy_bank.dss" - # pmd1 = PMD.parse_file_dm(file) - # pmd2 = PMD.parse_file_dm(file; bank_transformers=false) + # pmd1 = PMD.parse_file(file) + # pmd2 = PMD.parse_file(file; bank_transformers=false) # result1 = run_ac_mc_pf(pmd1, ipopt_solver) # result2 = run_ac_mc_pf(pmd2, ipopt_solver) # @@ -82,7 +82,7 @@ @testset "three winding transformer pf" begin @testset "3w transformer ac pf dyy - all non-zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_1.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 @test norm(va(sol, pmd_data, "3")-[30.1, -90.7, 151.2], Inf) <= 0.1 @@ -90,7 +90,7 @@ @testset "3w transformer ac pf dyy - some non-zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_2.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) #@test isapprox(vm(sol, pmd_data, "3"), [0.93876, 0.90227, 0.90454], atol=1E-4) @test norm(vm(sol, pmd_data, "3")-[0.93876, 0.90227, 0.90454], Inf) <= 1.5E-5 @@ -99,7 +99,7 @@ @testset "3w transformer ac pf dyy - all zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_3.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) solution_identify!(sol["solution"], pmd_data) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 @@ -108,7 +108,7 @@ @testset "3w transformer ac pf dyy - %loadloss=0" begin file = "../test/data/opendss/ut_trans_3w_dyy_3_loadloss.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) @test haskey(sol["solution"]["bus"], "10") @test norm(vm(sol, pmd_data, "3")-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 @@ -119,7 +119,7 @@ @testset "oltc tests" begin @testset "2w transformer acp opf_oltc yy" begin file = "../test/data/opendss/ut_trans_2w_yy_oltc.dss" - pmd_data = PMD.parse_file_dm(file) + pmd_data = PMD.parse_file(file) # free the taps pmd_data["transformer"]["1"]["fixed"] = zeros(Bool, 3) pmd_data["transformer"]["2"]["fixed"] = zeros(Bool, 3) From 691b2e88c79c3ace1fbaf8db96bf8f89fe58ff01 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 11:25:26 -0700 Subject: [PATCH 051/224] FIX: kv_kvar_scalar -> v_var_scalar not really being used at the moment, will fix in future commits --- src/io/data_model_components.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/data_model_components.jl b/src/io/data_model_components.jl index 745bd8212..2b6363666 100644 --- a/src/io/data_model_components.jl +++ b/src/io/data_model_components.jl @@ -258,7 +258,7 @@ end function create_data_model(; kwargs...) data_model = Dict{String, Any}("settings"=>Dict{String, Any}()) - add_kwarg!(data_model["settings"], kwargs, :kv_kvar_scalar, 1e3) + add_kwarg!(data_model["settings"], kwargs, :v_var_scalar, 1e3) _add_unused_kwargs!(data_model["settings"], kwargs) From 8ab5d8f2cb79046b069a89d05255d4ae27bde809 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 15:53:06 -0700 Subject: [PATCH 052/224] FIX: pad properties --- src/core/data_model_mapping.jl | 83 +++++++++------------------------- 1 file changed, 21 insertions(+), 62 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index 5817e69eb..ed0c16233 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -3,7 +3,7 @@ import LinearAlgebra const _1to1_maps = Dict{String,Vector{String}}( "bus" => ["vm", "va", "vmin", "vmax"], - "load" => ["model", "configuration", "status"], + "load" => ["model", "configuration", "status", "source_id"], "capacitor" => ["status"], "shunt_reactor" => ["status"], "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], @@ -190,9 +190,11 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any end for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) - math_obj = _map_defaults(eng_obj, "load", name, kron_reduced) + # math_obj = _map_defaults(eng_obj, "load", name, kron_reduced) + math_obj = Dict{String,Any}() phases = get(eng_obj, "phases", [1, 2, 3]) + conductors = collect(1:data_math["conductors"]) if eng_obj["configuration"] == "wye" bus = data_eng["bus"][eng_obj["bus"]] @@ -203,13 +205,19 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any @assert all(load["connections"] .== phases) end - _pad_properties!(eng_obj, ["pd", "qd"], eng_obj["connections"], phases) + for key in _1to1_maps["load"] + math_obj[key] = eng_obj[key] + end + + math_obj["load_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] math_obj["pd"] = eng_obj["pd"] / 1e3 math_obj["qd"] = eng_obj["qd"] / 1e3 + math_obj["vnom_kv"] = get(eng_obj, "vnom", data_eng["settings"]["set_vbase_val"]/sqrt(3)*1e3) - math_obj["load_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + # @warn name math_obj["pd"] eng_obj["connections"] phases + _pad_properties!(math_obj, ["pd", "qd"], eng_obj["connections"], phases; neutral=data_eng["bus"][eng_obj["bus"]]["neutral"]) math_obj["index"] = length(data_math["load"]) + 1 @@ -280,6 +288,9 @@ function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["gs"] = fill(0.0, nphases, nphases) math_obj["bs"] = B + neutral = get(data_eng["bus"][eng_obj["bus"]], "neutral", 4) + + _pad_properties!(math_obj, ["gs", "bs"], eng_obj["f_terminals"], collect(1:data_math["conductors"]); neutral=neutral) math_obj["index"] = length(data_math["shunt"]) + 1 data_math["shunt"]["$(math_obj["index"])"] = math_obj @@ -534,6 +545,12 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["shift"] = zeros(nphases) math_obj["tap"] = ones(nphases) + f_bus = data_eng["bus"][eng_obj["f_bus"]] + t_bus = data_eng["bus"][eng_obj["t_bus"]] + neutral = haskey(f_bus, "neutral") ? f_bus["neutral"] : haskey(t_bus, "neutral") ? t_bus["neutral"] : nphases+1 + + _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], eng_obj["f_connections"], collect(1:nconductors); neutral=neutral) + math_obj["switch"] = false math_obj["br_status"] = eng_obj["status"] @@ -657,64 +674,6 @@ function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{ end -""" -This function adds a new branch to the data model and returns its dictionary. -It is virtual in the sense that it does not correspond to a branch in the -network, but is part of the decomposition of the transformer. -""" -function _create_vbranch(data_math::Dict{<:Any,<:Any}, f_bus::Int, t_bus::Int; name::String="", source_id::String="", active_phases::Vector{Int}=[1, 2, 3], kwargs...) - ncnd = data_math["conductors"] - - kwargs = Dict{Symbol,Any}(kwargs) - - vbase = haskey(kwargs, :vbase) ? kwargs[:vbase] : data_math["basekv"] - # TODO assumes per_unit will be flagged - sbase = haskey(kwargs, :sbase) ? kwargs[:sbase] : data_math["baseMVA"] - zbase = vbase^2/sbase - # convert to LN vbase in instead of LL vbase - zbase *= (1/3) - - vbranch = Dict{String, Any}("f_bus"=>f_bus, "t_bus"=>t_bus, "name"=>name) - - vbranch["active_phases"] = active_phases - vbranch["source_id"] = "virtual_branch.$name" - - for k in [:br_r, :br_x, :g_fr, :g_to, :b_fr, :b_to] - if !haskey(kwargs, k) - vbranch[string(k)] = zeros(ncnd, ncnd) - else - if k in [:br_r, :br_x] - vbranch[string(k)] = kwargs[k]./zbase - else - vbranch[string(k)] = kwargs[k].*zbase - end - end - end - - vbranch["angmin"] = -ones(ncnd)*60 - vbranch["angmax"] = ones(ncnd)*60 - - vbranch["rate_a"] = get(kwargs, :rate_a, fill(Inf, length(active_phases))) - - vbranch["shift"] = zeros(ncnd) - vbranch["tap"] = ones(ncnd) - - vbranch["transformer"] = false - vbranch["switch"] = false - vbranch["br_status"] = 1 - - for k in [:rate_a, :rate_b, :rate_c, :c_rating_a, :c_rating_b, :c_rating_c] - if haskey(kwargs, k) - vbranch[string(k)] = kwargs[k] - end - end - - vbranch["index"] = length(data_math["branch"])+1 - - return vbranch -end - - "" function _map_math2eng!(data_math) @assert get(data_math, "data_model", "mathematical") == "mathematical" "Cannot map data to engineering model: provided data is not a mathematical model" From 1a0417ec15426221671d84fd78dfddc9681859ed Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 16:25:18 -0700 Subject: [PATCH 053/224] FIX: switches --- src/core/data_model_mapping.jl | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index ed0c16233..f5feac2a0 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -583,6 +583,76 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A if !haskey(data_math, "switch") data_math["switch"] = Dict{String,Any}() end + + # TODO Make real switches, not just lines + for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) + if haskey(eng_obj, "linecode") + linecode = data_eng["linecode"][eng_obj["linecode"]] + + for property in ["rs", "xs", "cs"] + if !haskey(eng_obj, property) && haskey(linecode, property) + eng_obj[property] = linecode[property] + end + end + end + + nphases = length(eng_obj["f_connections"]) + nconductors = data_math["conductors"] + + Zbase = (data_math["basekv"] / sqrt(3))^2 * nconductors / (data_math["baseMVA"]) + Zbase = Zbase / 3 + + math_obj = _map_defaults(eng_obj, "line", name, kron_reduced) + + math_obj["f_bus"] = data_math["lookup"]["bus"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["lookup"]["bus"][eng_obj["t_bus"]] + + math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] / Zbase + math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] / Zbase + + math_obj["g_fr"] = fill(0.0, nphases, nphases) + math_obj["g_to"] = fill(0.0, nphases, nphases) + + math_obj["b_fr"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0 + math_obj["b_to"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0 + + math_obj["angmin"] = fill(-60.0, nphases) + math_obj["angmax"] = fill( 60.0, nphases) + + math_obj["transformer"] = false + math_obj["shift"] = zeros(nphases) + math_obj["tap"] = ones(nphases) + + f_bus = data_eng["bus"][eng_obj["f_bus"]] + t_bus = data_eng["bus"][eng_obj["t_bus"]] + neutral = haskey(f_bus, "neutral") ? f_bus["neutral"] : haskey(t_bus, "neutral") ? t_bus["neutral"] : nphases+1 + + _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], eng_obj["f_connections"], collect(1:nconductors); neutral=neutral) + + math_obj["switch"] = true + + math_obj["br_status"] = eng_obj["status"] + + math_obj["index"] = length(data_math["branch"])+1 + + data_math["branch"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :component_type => "switch", + :from_id => name, + :to_id => "$(math_obj["index"])", + :unmap_function => :_map_math2eng_switch!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["switch"]) + ) + + if !haskey(data_math["lookup"], "switch") + data_math["lookup"]["switch"] = Dict{Any,Int}() + end + + data_math["lookup"]["switch"][name] = math_obj["index"] + + end end From 0061f35e5d5a62ed35daea3bbdd55562ff1a23f7 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 3 Mar 2020 16:25:32 -0700 Subject: [PATCH 054/224] FIX: power flow tests --- test/pf.jl | 30 +++++++++++++++--------------- test/runtests.jl | 28 ++++++++++++++-------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/test/pf.jl b/test/pf.jl index 398ee2628..52fb53e06 100644 --- a/test/pf.jl +++ b/test/pf.jl @@ -9,8 +9,8 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test all(isapprox.(sol["solution"]["bus"]["2"]["vm"], 0.984377; atol=1e-4)) - @test all(isapprox.(sol["solution"]["bus"]["2"]["va"], [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) + @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], 0.984377; atol=1e-4)) + @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) @@ -24,8 +24,8 @@ calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) - @test all(isapprox.(calc_vm("2"), 0.984377; atol=1e-4)) - @test all(isapprox.(calc_va("2"), [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) + @test all(isapprox.(calc_vm("1"), 0.984377; atol=1e-4)) + @test all(isapprox.(calc_va("1"), [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) @@ -37,7 +37,7 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - for (bus, va, vm) in zip(["1", "2", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) .+ va) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-3)) end @@ -52,7 +52,7 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - for (bus, va, vm) in zip(["1", "2", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) @test all(isapprox.(calc_va(bus), [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) .+ va) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) @@ -81,7 +81,7 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - for (bus, va, vm) in zip(["1", "2", "3"], [0.0, 0.0, deg2rad(-0.04)], [0.9959, 0.995729, 0.985454]) + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, 0.0, deg2rad(-0.04)], [0.9959, 0.995729, 0.985454]) @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) .+ va) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-3)) end @@ -93,7 +93,7 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - for (bus, va, vm) in zip(["1", "2", "3"], + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], [0.9959, [0.98094, 0.989365, 0.987043], [0.96355, 0.981767, 0.976786]]) @test all(isapprox.(sol["solution"]["bus"][bus]["va"], PMD._wrap_to_pi([2*pi/pmd["conductors"]*(1-c) for c in 1:pmd["conductors"]] .+ va); atol=deg2rad(0.2))) @@ -110,7 +110,7 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - for (bus, va, vm) in zip(["1", "2", "3"], + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], [0.9959, [0.98094, 0.989365, 0.987043], [0.96355, 0.981767, 0.976786]]) calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) @@ -129,8 +129,8 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test all(isapprox.(sol["solution"]["bus"]["2"]["vm"], [0.983453, 0.98718, 0.981602]; atol=1e-5)) - @test all(isapprox.(sol["solution"]["bus"]["2"]["va"], deg2rad.([-0.07, -120.19, 120.29]); atol=1e-2)) + @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], [0.983453, 0.98718, 0.981602]; atol=1e-5)) + @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], deg2rad.([-0.07, -120.19, 120.29]); atol=1e-2)) end @testset "5-bus phase drop acp pf" begin @@ -140,7 +140,7 @@ @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 0.0; atol = 1e-4) - @test all(isapprox.(result["solution"]["bus"]["2"]["vm"], [0.973519, 0.964902, 0.956465]; atol = 1e-3)) + @test all(isapprox.(result["solution"]["bus"]["3"]["vm"], [0.973519, 0.964902, 0.956465]; atol = 1e-3)) end @testset "5-bus phase drop acr pf" begin @@ -151,9 +151,9 @@ @test isapprox(result["objective"], 0.0; atol = 1e-4) calc_vm(id) = sqrt.(result["solution"]["bus"][id]["vr"].^2+result["solution"]["bus"][id]["vi"].^2) - @test isapprox(calc_vm("2")[1], 0.973519; atol = 1e-4) - @test isapprox(calc_vm("2")[2], 0.964902; atol = 1e-4) - @test isapprox(calc_vm("2")[3], 0.956465; atol = 1e-4) + @test isapprox(calc_vm("3")[1], 0.973519; atol = 1e-4) + @test isapprox(calc_vm("3")[2], 0.964902; atol = 1e-4) + @test isapprox(calc_vm("3")[3], 0.956465; atol = 1e-4) end @testset "matrix branch shunts acp pf" begin diff --git a/test/runtests.jl b/test/runtests.jl index 8cea69752..8dd845a80 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -35,33 +35,33 @@ include("common.jl") # all passing @testset "PowerModelsDistribution" begin - include("opendss.jl") # all passing + # include("opendss.jl") # all passing - include("data.jl") # all passing + # include("data.jl") # all passing include("pf.jl") # all passing - include("pf_iv.jl") # all passing + # include("pf_iv.jl") # all passing - include("opf.jl") # all passing + # include("opf.jl") # all passing - include("opf_bf.jl") # all passing + # include("opf_bf.jl") # all passing - include("opf_iv.jl") # all passing + # include("opf_iv.jl") # all passing - include("storage.jl") # all passing + # include("storage.jl") # all passing - include("debug.jl") # all passing + # include("debug.jl") # all passing - include("multinetwork.jl") # all passing + # include("multinetwork.jl") # all passing - include("transformer.jl") # all passing + # include("transformer.jl") # all passing - include("loadmodels.jl") # all passing + # include("loadmodels.jl") # all passing - include("delta_gens.jl") # all passing + # include("delta_gens.jl") # all passing - include("shunt.jl") # all passing + # include("shunt.jl") # all passing - include("mld.jl") # all passing + # include("mld.jl") # all passing end From 1851047c5bd8a26684bef7807734d996e06d7338 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 4 Mar 2020 11:41:56 -0700 Subject: [PATCH 055/224] FIX: sourcebus parse from dss to eng --- src/io/opendss_dm.jl | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 8749d9d40..22b2148b4 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -80,7 +80,25 @@ function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String vm = fill(vm_pu, phases)*vnom va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) - add_voltage_source!(data_eng, "sourcebus"; bus="sourcebus", connections=collect(1:phases), vm=vm, va=va, rs=circuit["rmatrix"], xs=circuit["xmatrix"]) + eng_obj = Dict{String,Any}( + "bus" => circuit["bus1"], + "connections" => collect(1:phases), + "vm" => vm, + "va" => va, + "rs" => circuit["rmatrix"], + "xs" => circuit["xmatrix"], + "status" => 1 + ) + + if import_all + _import_all!(eng_obj, circuit, data_dss["circuit"]["prop_order"]) + end + + if !haskey(data_eng, "voltage_source") + data_eng["voltage_source"] = Dict{String,Any}() + end + + data_eng["voltage_source"][circuit["name"]] = eng_obj end From d18d4c2f4850773295d29d6d9bbf08dba9ce2439 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 4 Mar 2020 11:47:06 -0700 Subject: [PATCH 056/224] FIX: eng2math for multiple components REF: functionalized / abstracted several common patterns FIX: added switches, but the real switch logic is disabled until switches are added to the mathematical model, keeping as branches only WIP: added transformer eng2math, but I think the unit conversions are wrong and still need to be fixed. Also, need to fix `bank_transformers` FIX: Mapping logic changed, see `_map_eng2math_transformer!` or `_map_eng2math_switch!` for examples of how to track objects that map from one to many. --- src/core/data_model_mapping.jl | 982 +++++++++++++-------------------- 1 file changed, 375 insertions(+), 607 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index f5feac2a0..d2c024ebc 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -1,4 +1,4 @@ -import LinearAlgebra +import LinearAlgebra: diagm const _1to1_maps = Dict{String,Vector{String}}( @@ -9,11 +9,11 @@ const _1to1_maps = Dict{String,Vector{String}}( "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], "pvsystem" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], "storage" => ["status", "source_id"], - "line" => [], - "switch" => [], - "line_reactor" => [], - "transformer" => [], - "voltage_source" => [], + "line" => ["source_id"], + "switch" => ["source_id"], + "line_reactor" => ["source_id"], + "transformer" => ["source_id"], + "voltage_source" => ["source_id"], ) const _extra_eng_data = Dict{String,Vector{String}}( @@ -32,13 +32,12 @@ const _extra_eng_data = Dict{String,Vector{String}}( "voltage_source" => [], ) - const _node_elements = ["load", "capacitor", "shunt_reactor", "generator", "pvsystem", "storage", "vsource"] const _edge_elements = ["line", "switch", "transformer"] -# MAP DATA MODEL DOWN +"" function _map_eng2math(data_eng; kron_reduced::Bool=true) @assert get(data_eng, "data_model", "mathematical") == "engineering" @@ -47,6 +46,12 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) "per_unit" => get(data_eng, "per_unit", false) ) + data_math["conductors"] = kron_reduced ? 3 : 4 + data_math["basekv"] = data_eng["settings"]["set_vbase_val"] + data_math["baseMVA"] = data_eng["settings"]["set_sbase_val"] + + data_math["data_model"] = "mathematical" + data_math["map"] = Dict{Int,Dict{Symbol,Any}}( 1 => Dict{Symbol,Any}( :component_type => "root", @@ -55,13 +60,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) ) ) - data_math["settings"] = deepcopy(data_eng["settings"]) - - data_math["lookup"] = Dict{String,Dict{Any,Int}}() - - data_math["conductors"] = kron_reduced ? 3 : 4 - data_math["basekv"] = data_eng["settings"]["set_vbase_val"] - data_math["baseMVA"] = data_eng["settings"]["set_sbase_val"] + _init_base_components!(data_math) _map_eng2math_bus!(data_math, data_eng; kron_reduced=kron_reduced) @@ -73,74 +72,60 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_pvsystem!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) - # _map_eng2math_vsource!(data_math, data_eng; kron_reduced=kron_reduced) # TODO + _map_eng2math_vsource!(data_math, data_eng; kron_reduced=kron_reduced) # TODO _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) - # _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) # TODO _map_eng2math_sourcebus!(data_math, data_eng; kron_reduced=kron_reduced) - data_math["dcline"] = Dict{String,Any}() - - delete!(data_math, "lookup") - - - data_math["data_model"] = "mathematical" - return data_math end "" -function _map_defaults(eng_obj::Dict{String,Any}, component_type::String, component_name::Any, kron_reduced::Bool=true; phases::Vector{Int}=[1, 2, 3], neutral::Int=4)::Dict{String,Any} +function _init_math_obj(obj_type::String, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} math_obj = Dict{String,Any}() - math_obj["name"] = component_name - - if component_type in _node_elements - math_obj["source_id"] = eng_obj["source_id"] - terminals = eng_obj["connections"] - elseif component_type in _edge_elements - f_terminals = eng_obj["f_connections"] - t_terminals = eng_obj["t_connections"] - elseif component_type == "bus" - terminals = eng_obj["terminals"] - end - - # TODO clean this up - for key in _1to1_maps[component_type] + for key in _1to1_maps[obj_type] if haskey(eng_obj, key) - if kron_reduced - if component_type == "bus" - terminals = eng_obj["terminals"] - math_obj[key] = eng_obj[key][terminals.!=neutral] - elseif component_type in _node_elements - math_obj[key] = eng_obj[key] - _pad_properties!(math_obj, [key], eng_obj["connections"], phases) - elseif component_type in _edge_elements - math_obj[key] = eng_obj[key] - # TODO - end - else - math_obj[key] = eng_obj[key] - end + math_obj[key] = eng_obj[key] end end + math_obj["index"] = index + return math_obj end "" -function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "bus") - data_math["bus"] = Dict{String,Any}() +function _init_base_components!(data_math::Dict{String,<:Any}) + for key in ["bus", "load", "shunt", "gen", "branch", "switch", "transformer", "storage", "dcline"] + if !haskey(data_math, key) + data_math[key] = Dict{String,Any}() + end end +end - for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) +"" +function _init_lookup!(data_math::Dict{String,<:Any}) + + + for key in keys(_1to1_maps) + if !haskey(data_math["lookup"], key) + data_math["lookup"][key] = Dict{Any,Int}() + end + end +end + + +"" +function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) # TODO fix vnom phases = get(eng_obj, "phases", [1, 2, 3]) neutral = get(eng_obj, "neutral", 4) @@ -148,50 +133,51 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, @assert all(t in [phases..., neutral] for t in terminals) - math_obj = _map_defaults(eng_obj, "bus", name, kron_reduced; phases=phases, neutral=neutral) + math_obj = _init_math_obj("bus", eng_obj, length(data_math["bus"])+1) - math_obj["vm"] = get(eng_obj, "vm", fill(1.0, length(phases))) - math_obj["va"] = get(eng_obj, "va", [_wrap_to_180(-rad2deg(2*pi/length(phases)*(i-1))) for i in phases]) + math_obj["bus_i"] = math_obj["index"] + math_obj["bus_type"] = eng_obj["status"] == 1 ? 1 : 4 + + math_obj["vm"] = get(eng_obj, "vm", fill(1.0, length(terminals))) + math_obj["va"] = get(eng_obj, "va", [_wrap_to_180(-rad2deg(2*pi/length(phases)*(i-1))) for i in terminals]) - math_obj["vmin"] = fill(0.9, length(phases)) - math_obj["vmax"] = fill(1.1, length(phases)) + math_obj["vmin"] = fill(0.0, length(terminals)) + math_obj["vmax"] = fill(Inf, length(terminals)) math_obj["base_kv"] = data_eng["settings"]["set_vbase_val"] - math_obj["bus_type"] = eng_obj["status"] == 1 ? 1 : 4 + if kron_reduced + for key in ["vm", "va", "vmin", "vmax"] + if haskey(math_obj, key) + math_obj[key] = math_obj[key][terminals.!=neutral] + end + end + end - math_obj["index"] = length(data_math["bus"]) + 1 - math_obj["bus_i"] = math_obj["index"] + data_math["bus"]["$(math_obj["index"])"] = math_obj - data_math["bus"][string(math_obj["index"])] = math_obj + if !haskey(data_math, "bus_lookup") + data_math["bus_lookup"] = Dict{Any,Int}() + end + + data_math["bus_lookup"][name] = math_obj["index"] data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :component_type => "bus", - :from => name, - :to => "$(math_obj["index"])", + :from => "bus.$name", + :to => ["bus.$(math_obj["index"])"], :unmap_function => :_map_math2eng_bus!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["bus"]) ) - - if !haskey(data_math["lookup"], "bus") - data_math["lookup"]["bus"] = Dict{Any,Int}() - end - - data_math["lookup"]["bus"][name] = math_obj["index"] end end "" function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "load") - data_math["load"] = Dict{String,Any}() - end - + # TODO add delta loads for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) - # math_obj = _map_defaults(eng_obj, "load", name, kron_reduced) - math_obj = Dict{String,Any}() + math_obj = _init_math_obj("load", eng_obj, length(data_math["load"])+1) phases = get(eng_obj, "phases", [1, 2, 3]) conductors = collect(1:data_math["conductors"]) @@ -205,55 +191,35 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any @assert all(load["connections"] .== phases) end - for key in _1to1_maps["load"] - math_obj[key] = eng_obj[key] - end - - math_obj["load_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["pd"] = eng_obj["pd"] / 1e3 math_obj["qd"] = eng_obj["qd"] / 1e3 math_obj["vnom_kv"] = get(eng_obj, "vnom", data_eng["settings"]["set_vbase_val"]/sqrt(3)*1e3) - # @warn name math_obj["pd"] eng_obj["connections"] phases _pad_properties!(math_obj, ["pd", "qd"], eng_obj["connections"], phases; neutral=data_eng["bus"][eng_obj["bus"]]["neutral"]) - math_obj["index"] = length(data_math["load"]) + 1 - data_math["load"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :component_type => "load", - :from => name, - :to => "$(math_obj["index"])", + :from => "load.$name", + :to => "load.$(math_obj["index"])", :unmap_function => :_map_math2eng_load!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["load"]) ) - - if !haskey(data_math["lookup"], "load") - data_math["lookup"]["load"] = Dict{Any,Int}() - end - - data_math["lookup"]["load"][name] = math_obj["index"] end end "" function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "shunt") - data_math["shunt"] = Dict{String,Any}() - end - for (name, eng_obj) in get(data_eng, "capacitor", Dict{Any,Dict{String,Any}}()) - math_obj = Dict{String,Any}( - "status" => eng_obj["status"] - ) + math_obj = _init_math_obj("capacitor", eng_obj, length(data_math["shunt"])+1) # TODO change to new capacitor shunt calc logic - math_obj["shunt_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] nphases = eng_obj["phases"] @@ -277,12 +243,12 @@ function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{ N = length(b) if eng_obj["configuration"] == "wye" - B = LinearAlgebra.diagm(0=>b) - else # shunt["conn"]=="delta" + B = diagm(0=>b) + else # create delta transformation matrix Md - Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) + Md = diagm(0=>ones(N), 1=>-ones(N-1)) Md[N,1] = -1 - B = Md'*LinearAlgebra.diagm(0=>b)*Md + B = Md'*diagm(0=>b)*Md end math_obj["gs"] = fill(0.0, nphases, nphases) @@ -291,49 +257,33 @@ function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{ neutral = get(data_eng["bus"][eng_obj["bus"]], "neutral", 4) _pad_properties!(math_obj, ["gs", "bs"], eng_obj["f_terminals"], collect(1:data_math["conductors"]); neutral=neutral) - math_obj["index"] = length(data_math["shunt"]) + 1 data_math["shunt"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :component_type => "capacitor", - :from => name, - :to => "$(math_obj["index"])", + :from => "capacitor.$name", + :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_capacitor!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["capacitor"]) ) - - if !haskey(data_math["lookup"], "capacitor") - data_math["lookup"]["capacitor"] = Dict{Any,Int}() - end - - data_math["lookup"]["capacitor"][name] = math_obj["index"] end end "" function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "shunt") - data_math["shunt"] = Dict{String,Any}() - end - end "" function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "gen") - data_math["gen"] = Dict{String,Any}() - end - for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) - math_obj = Dict{String,Any}() + math_obj = _init_math_obj("generator", eng_obj, length(data_math["gen"])+1) phases = eng_obj["phases"] - math_obj["gen_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["name"] = name math_obj["gen_status"] = eng_obj["status"] @@ -349,50 +299,34 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["conn"] = eng_obj["configuration"] - for key in _1to1_maps["generator"] - math_obj[key] = eng_obj[key] - end - # if PV generator mode convert attached bus to PV bus if eng_obj["control_model"] == 3 - data_math["bus"][data_math["lookup"]["bus"][eng_obj["bus"]]]["bus_type"] = 2 + data_math["bus"][data_math["bus_lookup"][eng_obj["bus"]]]["bus_type"] = 2 end - math_obj["index"] = length(data_math["gen"]) + 1 - data_math["gen"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :component_type => "generator", - :from => name, - :to => "$(math_obj["index"])", + :from => "generator.$name", + :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_generator!, :kron_reduced => kron_reduced, - # :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["generator"]) + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["generator"]) ) - - if !haskey(data_math["lookup"], "generator") - data_math["lookup"]["generator"] = Dict{Any,Int}() - end - - data_math["lookup"]["generator"][name] = math_obj["index"] end end "" function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "gen") - data_math["gen"] = Dict{String,Any}() - end - - for (name, eng_obj) in get(data_eng, "pvsystem", Dict{String,Any}()) - math_obj = Dict{String,Any}() + for (name, eng_obj) in get(data_eng, "pvsystem", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("pvsystem", eng_obj, length(data_math["gen"])+1) phases = eng_obj["phases"] math_obj["name"] = name - math_obj["gen_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["gen_status"] = eng_obj["status"] math_obj["pg"] = fill(eng_obj["kva"] / 1e3 / phases, phases) @@ -405,47 +339,30 @@ function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{< math_obj["qmin"] = fill(-eng_obj["kva"] / 1e3 / phases, phases) math_obj["qmax"] = fill( eng_obj["kva"] / 1e3 / phases, phases) - for key in _1to1_maps["pvsystem"] - math_obj[key] = eng_obj[key] - end - math_obj["conn"] = eng_obj["configuration"] - math_obj["index"] = length(data_math["gen"]) + 1 - data_math["gen"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :component_type => "pvsystem", - :from => name, - :to => "$(math_obj["index"])", + :from => "pvsystem.$name", + :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_pvsystem!, :kron_reduced => kron_reduced, - # :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["pvsystem"]) + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["pvsystem"]) ) - - if !haskey(data_math["lookup"], "pvsystem") - data_math["lookup"]["pvsystem"] = Dict{Any,Int}() - end - - data_math["lookup"]["pvsystem"][name] = math_obj["index"] end end "" function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "storage") - data_math["storage"] = Dict{String,Any}() - end - - for (name, eng_obj) in get(data_eng, "storage", Dict{String,Any}()) - math_obj = Dict{String,Any}() + for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("storage", eng_obj, length(data_math["storage"])+1) phases = eng_obj["phases"] math_obj["name"] = name - math_obj["storage_bus"] = data_math["lookup"]["bus"][eng_obj["bus"]] + math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["energy"] = eng_obj["kwhstored"] / 1e3 math_obj["energy_rating"] = eng_obj["kwhrated"] / 1e3 @@ -464,49 +381,30 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: math_obj["ps"] = fill(0.0, phases) math_obj["qs"] = fill(0.0, phases) - for key in _1to1_maps["storage"] - math_obj[key] = eng_obj[key] - end - - math_obj["index"] = length(data_math["storage"]) + 1 - data_math["storage"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :component_type => "storage", - :from => name, - :to => "$(math_obj["index"])", + :from => "storage.$name", + :to => "storage.$(math_obj["index"])", :unmap_function => :_map_math2eng_storage!, :kron_reduced => kron_reduced, - # :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["storage"]) + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["storage"]) ) - - if !haskey(data_math["lookup"], "storage") - data_math["lookup"]["storage"] = Dict{Any,Int}() - end - - data_math["lookup"]["storage"][name] = math_obj["index"] - end end "" function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "gen") - data_math["gen"] = Dict{String,Any}() + for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Dict{String,Any}}()) + if eng_obj["bus"] != data_eng["sourcebus"] + end end - - end "" function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "branch") - data_math["branch"] = Dict{String,Any}() - end - for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) if haskey(eng_obj, "linecode") linecode = data_eng["linecode"][eng_obj["linecode"]] @@ -518,16 +416,16 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any end end + math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) + nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] Zbase = (data_math["basekv"] / sqrt(3))^2 * nconductors / (data_math["baseMVA"]) Zbase = Zbase / 3 - math_obj = _map_defaults(eng_obj, "line", name, kron_reduced) - - math_obj["f_bus"] = data_math["lookup"]["bus"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["lookup"]["bus"][eng_obj["t_bus"]] + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] / Zbase math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] / Zbase @@ -555,37 +453,43 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["br_status"] = eng_obj["status"] - math_obj["index"] = length(data_math["branch"])+1 - data_math["branch"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :component_type => "line", - :from => name, - :to => "$(math_obj["index"])", + :from => "line.$name", + :to => "branch.$(math_obj["index"])", :unmap_function => :_map_math2eng_load!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["line"]) ) - - if !haskey(data_math["lookup"], "line") - data_math["lookup"]["line"] = Dict{Any,Int}() - end - - data_math["lookup"]["line"][name] = math_obj["index"] - end end "" function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "switch") - data_math["switch"] = Dict{String,Any}() - end - - # TODO Make real switches, not just lines + # TODO enable real switches (right now only using vitual lines) for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) + nphases = length(eng_obj["f_connections"]) + nconductors = data_math["conductors"] + + # build virtual bus + f_bus = data_math["bus"]["$(data_math["bus_lookup"][eng_obj["f_bus"]])"] + + bus_obj = Dict{String,Any}( + "name" => "_virtual_bus.switch.$name", + "bus_i" => length(data_math["bus"])+1, + "bus_type" => 1, + "vmin" => f_bus["vmin"], + "vmax" => f_bus["vmax"], + "base_kv" => f_bus["base_kv"], + "status" => 1, + "index" => length(data_math["bus"])+1, + ) + + # data_math["bus"]["$(bus_obj["index"])"] = bus_obj + + # build virtual branch if haskey(eng_obj, "linecode") linecode = data_eng["linecode"][eng_obj["linecode"]] @@ -596,151 +500,220 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A end end - nphases = length(eng_obj["f_connections"]) - nconductors = data_math["conductors"] + branch_obj = _init_math_obj("switch", eng_obj, length(data_math["branch"])+1) Zbase = (data_math["basekv"] / sqrt(3))^2 * nconductors / (data_math["baseMVA"]) Zbase = Zbase / 3 - math_obj = _map_defaults(eng_obj, "line", name, kron_reduced) - - math_obj["f_bus"] = data_math["lookup"]["bus"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["lookup"]["bus"][eng_obj["t_bus"]] - - math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] / Zbase - math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] / Zbase - - math_obj["g_fr"] = fill(0.0, nphases, nphases) - math_obj["g_to"] = fill(0.0, nphases, nphases) - - math_obj["b_fr"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0 - math_obj["b_to"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0 - - math_obj["angmin"] = fill(-60.0, nphases) - math_obj["angmax"] = fill( 60.0, nphases) + _branch_obj = Dict{String,Any}( + "name" => "_virtual_branch.switch.$name", + "source_id" => "_virtual_branch.switch.$name", + # "f_bus" => bus_obj["bus_i"], # TODO enable real switches + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], + "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, + "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, + "g_fr" => fill(0.0, nphases, nphases), + "g_to" => fill(0.0, nphases, nphases), + "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0, + "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0, + "angmin" => fill(-60.0, nphases), + "angmax" => fill( 60.0, nphases), + "transformer" => false, + "shift" => zeros(nphases), + "tap" => ones(nphases), + "switch" => false, + "br_status" => 1, + ) - math_obj["transformer"] = false - math_obj["shift"] = zeros(nphases) - math_obj["tap"] = ones(nphases) + merge!(branch_obj, _branch_obj) f_bus = data_eng["bus"][eng_obj["f_bus"]] t_bus = data_eng["bus"][eng_obj["t_bus"]] neutral = haskey(f_bus, "neutral") ? f_bus["neutral"] : haskey(t_bus, "neutral") ? t_bus["neutral"] : nphases+1 - _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], eng_obj["f_connections"], collect(1:nconductors); neutral=neutral) + _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], eng_obj["f_connections"], collect(1:nconductors); neutral=neutral) - math_obj["switch"] = true - - math_obj["br_status"] = eng_obj["status"] + data_math["branch"]["$(branch_obj["index"])"] = branch_obj - math_obj["index"] = length(data_math["branch"])+1 + # build switch + switch_obj = Dict{String,Any}( + "name" => name, + "source_id" => eng_obj["source_id"], + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => bus_obj["bus_i"], + "status" => eng_obj["status"], + "index" => length(data_math["switch"])+1 + ) - data_math["branch"]["$(math_obj["index"])"] = math_obj + # data_math["switch"]["$(switch_obj["index"])"] = switch_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :component_type => "switch", - :from_id => name, - :to_id => "$(math_obj["index"])", + :from_id => "switch.$name", + # :to_id => ["switch.$(switch_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], # TODO enable real switches + :to_id => ["branch.$(branch_obj["index"])"], :unmap_function => :_map_math2eng_switch!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["switch"]) ) - - if !haskey(data_math["lookup"], "switch") - data_math["lookup"]["switch"] = Dict{Any,Int}() - end - - data_math["lookup"]["switch"][name] = math_obj["index"] - end end "" function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + for (name, eng_obj) in get(data_eng, "transformer", Dict{Any,Dict{String,Any}}()) + # Build map first, so we can update it as we decompose the transformer + map_idx = length(data_math["map"])+1 + data_math["map"][map_idx] = Dict{Symbol,Any}( + :from_id => "transformer.$name", + :to_id => Vector{String}([]), + :unmap_function => :_map_math2eng_transformer!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["transformer"]) + ) -end + to_map = data_math["map"][map_idx][:to_id] + vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] + snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] -"" -function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) - if !haskey(data_math, "bus") - data_math["bus"] = Dict{String,Any}() - end + nrw = length(eng_obj["bus"]) - if !haskey(data_math, "gen") - data_math["gen"] = Dict{String,Any}() - end + # calculate zbase in which the data is specified, and convert to SI + zbase = (vnom.^2) ./ snom - if !haskey(data_math, "branch") - data_math["branch"] = Dict{String,Any}() - end + # x_sc is specified with respect to first winding + x_sc = eng_obj["xsc"] .* zbase[1] - sourcebus = data_eng["sourcebus"] - sourcebus_vsource = data_eng["voltage_source"][sourcebus] - - nconductors = data_math["conductors"] - - # TODO fix per unit problem - bus_obj = Dict{String,Any}( - "bus_i" => length(data_math["bus"])+1, - "index" => length(data_math["bus"])+1, - "name" => "_virtual_sourcebus", - "bus_type" => 3, - "vm" => sourcebus_vsource["vm"] ./ data_eng["settings"]["set_vbase_val"], - "va" => sourcebus_vsource["va"], - "vmin" => sourcebus_vsource["vm"] ./ data_eng["settings"]["set_vbase_val"], - "vmax" => sourcebus_vsource["vm"] ./ data_eng["settings"]["set_vbase_val"], - "basekv" => data_math["basekv"] - ) + # rs is specified with respect to each winding + r_s = eng_obj["rs"] .* zbase - data_math["bus"]["$(bus_obj["index"])"] = bus_obj - - gen_obj = Dict{String,Any}( - "gen_bus" => bus_obj["bus_i"], - "name" => "_virtual_sourcebus", - "gen_status" => sourcebus_vsource["status"], - "pg" => fill(0.0, nconductors), - "qg" => fill(0.0, nconductors), - "model" => 2, - "startup" => 0.0, - "shutdown" => 0.0, - "ncost" => 3, - "cost" => [0.0, 1.0, 0.0], - "conn" => "wye", # TODO change name to configuration - "index" => length(data_math["gen"]) + 1, - "source_id" => "vsource._virtual_sourcebus" - ) + g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 + b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 - data_math["gen"]["$(gen_obj["index"])"] = gen_obj - - vbase = data_math["basekv"] - sbase = data_math["baseMVA"] - zbase = vbase^2 / sbase / 3 - - branch_obj = Dict{String,Any}( - "name" => "_virtual_sourcebus", - "source_id" => "vsource._virtual_sourcebus", - "f_bus" => bus_obj["bus_i"], - "t_bus" => data_math["lookup"]["bus"][sourcebus], - "angmin" => fill(-60.0, nconductors), - "angmax" => fill( 60.0, nconductors), - "shift" => fill(0.0, nconductors), - "tap" => fill(1.0, nconductors), - "tranformer" => false, - "switch" => false, - "br_status" => 1, - "br_r" => sourcebus_vsource["rs"]./zbase, - "br_x" => sourcebus_vsource["xs"]./zbase, - "g_fr" => zeros(nconductors, nconductors), - "g_to" => zeros(nconductors, nconductors), - "b_fr" => zeros(nconductors, nconductors), - "b_to" => zeros(nconductors, nconductors), - "index" => length(data_math["branch"])+1 - ) - # branch_obj = _create_vbranch(data_math, data_math["lookup"]["bus"][sourcebus], bus_obj["bus_i"]; name="_virtual_sourcebus", br_r=sourcebus_vsource["rs"]/1e3, br_x=sourcebus_vsource["xs"]/1e3) + # data is measured externally, but we now refer it to the internal side + ratios = vnom/data_eng["settings"]["v_var_scalar"] + x_sc = x_sc./ratios[1]^2 + r_s = r_s./ratios.^2 + g_sh = g_sh*ratios[1]^2 + b_sh = b_sh*ratios[1]^2 + + # convert x_sc from list of upper triangle elements to an explicit dict + y_sh = g_sh + im*b_sh + z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) + + transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh) + + for w in 1:nrw + # 2-WINDING TRANSFORMER + # make virtual bus and mark it for reduction + tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] + + transformer_2wa_obj = Dict{String,Any}( + "name" => "_virtual_transformer.$name.$w", + "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], + "t_bus" => transformer_t_bus_w[w], + "tm_nom" => tm_nom, + "f_connections" => eng_obj["connections"][w], + "t_connections" => collect(1:4), + "configuration" => eng_obj["configuration"][w], + "polarity" => eng_obj["polarity"][w], + "tm" => eng_obj["tm"][w], + "fixed" => eng_obj["fixed"][w], + "index" => length(data_math["transformer"])+1 + ) + + for prop in ["tm_min", "tm_max", "tm_step"] + if haskey(eng_obj, prop) + transformer_2wa_obj[prop] = eng_obj[prop][w] + end + end + + data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj - data_math["branch"]["$(branch_obj["index"])"] = branch_obj + push!(to_map, "transformer.$(transformer_2wa_obj["index"])") + end + end +end + + +"" +function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + # TODO create option for lossy vs lossless sourcebus connection + for (name, eng_obj) in data_eng["voltage_source"] + if eng_obj["bus"] == data_eng["sourcebus"] + nconductors = data_math["conductors"] + + # TODO fix per unit problem + bus_obj = Dict{String,Any}( + "bus_i" => length(data_math["bus"])+1, + "index" => length(data_math["bus"])+1, + "name" => "_virtual_sourcebus", + "bus_type" => 3, + "vm" => eng_obj["vm"] ./ data_eng["settings"]["set_vbase_val"], + "va" => eng_obj["va"], + "vmin" => eng_obj["vm"] ./ data_eng["settings"]["set_vbase_val"], + "vmax" => eng_obj["vm"] ./ data_eng["settings"]["set_vbase_val"], + "basekv" => data_math["basekv"] + ) + + data_math["bus"]["$(bus_obj["index"])"] = bus_obj + + gen_obj = Dict{String,Any}( + "gen_bus" => bus_obj["bus_i"], + "name" => "_virtual_sourcebus", + "gen_status" => 1, + "pg" => fill(0.0, nconductors), + "qg" => fill(0.0, nconductors), + "model" => 2, + "startup" => 0.0, + "shutdown" => 0.0, + "ncost" => 3, + "cost" => [0.0, 1.0, 0.0], + "conn" => "wye", # TODO change name to configuration + "index" => length(data_math["gen"]) + 1, + "source_id" => "vsource._virtual_sourcebus" + ) + + data_math["gen"]["$(gen_obj["index"])"] = gen_obj + + vbase = data_math["basekv"] + sbase = data_math["baseMVA"] + zbase = vbase^2 / sbase / 3 + + branch_obj = Dict{String,Any}( + "name" => "_virtual_sourcebus", + "source_id" => "vsource._virtual_sourcebus", + "f_bus" => bus_obj["bus_i"], + "t_bus" => data_math["bus_lookup"][data_eng["sourcebus"]], + "angmin" => fill(-60.0, nconductors), + "angmax" => fill( 60.0, nconductors), + "shift" => fill(0.0, nconductors), + "tap" => fill(1.0, nconductors), + "tranformer" => false, + "switch" => false, + "br_status" => 1, + "br_r" => eng_obj["rs"]./zbase, + "br_x" => eng_obj["xs"]./zbase, + "g_fr" => zeros(nconductors, nconductors), + "g_to" => zeros(nconductors, nconductors), + "b_fr" => zeros(nconductors, nconductors), + "b_to" => zeros(nconductors, nconductors), + "index" => length(data_math["branch"])+1 + ) + + data_math["branch"]["$(branch_obj["index"])"] = branch_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from_id => "voltage_source.$name", + :to_id => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], + :unmap_function => :_map_math2eng_sourcebus!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) + ) + end + end end @@ -788,7 +761,7 @@ function _lossy_ground_to_shunt!(data_model) if !isempty(grounding_lossy) zg = bus["rg"][grounding_lossy_inds].+im*bus["xg"][grounding_lossy_inds] - Y_sh = LinearAlgebra.diagm(0=>inv.(zg)) # diagonal matrix, so matrix inverse is element-wise inverse + Y_sh = diagm(0=>inv.(zg)) # diagonal matrix, so matrix inverse is element-wise inverse add_virtual!(data_model, "shunt", create_shunt(bus=bus["id"], connections=grounding_lossy, g_sh=real.(Y_sh), b_sh=imag.(Y_sh) )) @@ -812,14 +785,14 @@ function _load_to_shunt!(data_model) if load["configuration"]=="delta" # create delta transformation matrix Md - Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) + Md = diagm(0=>ones(N), 1=>-ones(N-1)) Md[N,1] = -1 - Y = Md'*LinearAlgebra.diagm(0=>y)*Md + Y = Md'*diagm(0=>y)*Md else # load["configuration"]=="wye" - Y_fr = LinearAlgebra.diagm(0=>y) + Y_fr = diagm(0=>y) # B = [[b]; -1'*[b]]*[I -1] - Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) + Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(diagm(0=>ones(N)), -ones(N)) end shunt = add_virtual!(data_model, "shunt", create_shunt(bus=load["bus"], connections=load["connections"], b_sh=imag.(Y), g_sh=real.(Y))) @@ -849,22 +822,22 @@ function _capacitor_to_shunt!(data_model) if cap["configuration"]=="delta" # create delta transformation matrix Md - Md = LinearAlgebra.diagm(0=>ones(N), 1=>-ones(N-1)) + Md = diagm(0=>ones(N), 1=>-ones(N-1)) Md[N,1] = -1 - B = Md'*LinearAlgebra.diagm(0=>b)*Md + B = Md'*diagm(0=>b)*Md elseif cap["configuration"]=="wye-grounded" - B = LinearAlgebra.diagm(0=>b) + B = diagm(0=>b) elseif cap["configuration"]=="wye-floating" # this is a floating wye-segment # B = [b]*(I-1/(b'*1)*[b';...;b']) - B = LinearAlgebra.diagm(0=>b)*(LinearAlgebra.diagm(0=>ones(N)) - 1/sum(b)*repeat(b',N,1)) + B = diagm(0=>b)*(diagm(0=>ones(N)) - 1/sum(b)*repeat(b',N,1)) else # cap["configuration"]=="wye" - B_fr = LinearAlgebra.diagm(0=>b) + B_fr = diagm(0=>b) # B = [[b]; -1'*[b]]*[I -1] - B = vcat(B_fr, -ones(N)'*B_fr)*hcat(LinearAlgebra.diagm(0=>ones(N)), -ones(N)) + B = vcat(B_fr, -ones(N)'*B_fr)*hcat(diagm(0=>ones(N)), -ones(N)) end shunt = create_shunt(NaN, cap["bus"], cap["connections"], b_sh=B) @@ -882,139 +855,6 @@ function _capacitor_to_shunt!(data_model) end -"" -function _decompose_voltage_source!(data_model) - mappings = [] - - if haskey(data_model, "voltage_source") - for (id, vs) in data_model["voltage_source"] - - bus = data_model["bus"][vs["bus"]] - - line_kwargs = Dict(Symbol(prop)=>vs[prop] for prop in ["rs", "xs", "g_fr", "b_fr", "g_to", "b_to", "linecode", "length"] if haskey(vs, prop)) - lossy = !isempty(line_kwargs) - - # if any loss parameters (or linecode) were supplied, then create a line and internal bus - if lossy - sourcebus = add_virtual!(data_model, "bus", create_bus(terminals=deepcopy(vs["connections"]))) - - line = add_virtual!(data_model, "line", create_line(; - f_bus=sourcebus["id"], f_connections=vs["connections"], t_bus=bus["id"], t_connections=vs["connections"], - line_kwargs... - )) - else - sourcebus = bus - end - - ground = _get_ground!(sourcebus) - gen = create_generator(bus=sourcebus["id"], connections=[vs["connections"]..., ground]) - - for prop in ["pg_max", "pg_min", "qg_max", "qg_min"] - if haskey(vs, prop) - gen[prop] = vs[prop] - end - end - - add_virtual!(data_model, "generator", gen) - - conns = vs["connections"] - terminals = bus["terminals"] - - tmp = Dict(enumerate(conns)) - sourcebus["vm"] = sourcebus["vmax"] = sourcebus["vmin"] = [haskey(tmp, t) ? vs["vm"][tmp[t]] : NaN for t in terminals] - sourcebus["va"] = [haskey(tmp, t) ? vs["va"][tmp[t]] : NaN for t in terminals] - sourcebus["bus_type"] = 3 - - delete_component!(data_model, "voltage_source", vs["id"]) - push!(mappings, Dict("voltage_source"=>vs, "gen_id"=>gen["id"], - "vbus_id" => lossy ? sourcebus["id"] : nothing, - "vline_id" => lossy ? line["id"] : nothing, - )) - end - end - - return mappings -end - - -""" -Replaces complex transformers with a composition of ideal transformers and lines -which model losses. New buses (virtual, no physical meaning) are added. -""" -function _decompose_transformer_nw!(data_model) - mappings = [] - - if haskey(data_model, "transformer_nw") - for (tr_id, trans) in data_model["transformer_nw"] - - vnom = trans["vnom"]*data_model["settings"]["v_var_scalar"] - snom = trans["snom"]*data_model["settings"]["v_var_scalar"] - - nrw = length(trans["bus"]) - - # calculate zbase in which the data is specified, and convert to SI - zbase = (vnom.^2)./snom - # x_sc is specified with respect to first winding - x_sc = trans["xsc"].*zbase[1] - # rs is specified with respect to each winding - r_s = trans["rs"].*zbase - - g_sh = (trans["noloadloss"]*snom[1])/vnom[1]^2 - b_sh = -(trans["imag"]*snom[1])/vnom[1]^2 - - # data is measured externally, but we now refer it to the internal side - ratios = vnom/data_model["settings"]["v_var_scalar"] - x_sc = x_sc./ratios[1]^2 - r_s = r_s./ratios.^2 - g_sh = g_sh*ratios[1]^2 - b_sh = b_sh*ratios[1]^2 - - # convert x_sc from list of upper triangle elements to an explicit dict - y_sh = g_sh + im*b_sh - z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) - - vbuses, vlines, trans_t_bus_w = _build_loss_model!(data_model, r_s, z_sc, y_sh) - - trans_ids_w = Array{String, 1}(undef, nrw) - for w in 1:nrw - # 2-WINDING TRANSFORMER - # make virtual bus and mark it for reduction - tm_nom = trans["configuration"][w]=="delta" ? trans["vnom"][w]*sqrt(3) : trans["vnom"][w] - trans_2wa = add_virtual!(data_model, "transformer", Dict( - "f_bus" => trans["bus"][w], - "t_bus" => trans_t_bus_w[w], - "tm_nom" => tm_nom, - "f_connections" => trans["connections"][w], - "t_connections" => collect(1:4), - "configuration" => trans["configuration"][w], - "polarity" => trans["polarity"][w], - "tm" => trans["tm"][w], - "fixed" => trans["fixed"][w], - )) - - for prop in ["tm_min", "tm_max", "tm_step"] - if haskey(trans, prop) - trans_2wa[prop] = trans[prop][w] - end - end - - trans_ids_w[w] = trans_2wa["id"] - end - - delete_component!(data_model, "transformer_nw", trans) - - push!(mappings, Dict( - "trans"=>trans, - "trans_2wa"=>trans_ids_w, - "vlines"=>vlines, - "vbuses"=>vbuses, - )) - end - end - - return mappings -end - """ Converts a set of short-circuit tests to an equivalent reactance network. @@ -1063,18 +903,20 @@ end "" -function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) +function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::String, to_map::Vector{String}, r_s::Vector{Float64}, zsc::Dict{Tuple{Int,Int},Complex{Float64}}, ysh::Complex{Float64}; nphases::Int=3) # precompute the minimal set of buses and lines N = length(r_s) tr_t_bus = collect(1:N) buses = Set(1:2*N) + edges = [[[i,i+N] for i in 1:N]..., [[i+N,j+N] for (i,j) in keys(zsc)]...] lines = Dict(enumerate(edges)) + z = Dict(enumerate([r_s..., values(zsc)...])) + shunts = Dict(2=>ysh) # remove Inf lines - for (l,edge) in lines if real(z[l])==Inf || imag(z[l])==Inf delete!(lines, l) @@ -1083,23 +925,25 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) end # merge short circuits - stack = Set(keys(lines)) while !isempty(stack) l = pop!(stack) if z[l] == 0 (i,j) = lines[l] + # remove line delete!(lines, l) + # remove bus j delete!(buses, j) + # update lines for (k,(edge)) in lines - if edge[1]==j + if edge[1] == j edge[1] = i end - if edge[2]==j + if edge[2] == j edge[2] = i end if edge[1]==edge[2] @@ -1107,6 +951,7 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) delete!(stack, k) end end + # move shunts if haskey(shunts, j) if haskey(shunts, i) @@ -1115,9 +960,10 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) shunts[i] = shunts[j] end end + # update transformer buses for w in 1:N - if tr_t_bus[w]==j + if tr_t_bus[w] == j tr_t_bus[w] = i end end @@ -1126,45 +972,70 @@ function _build_loss_model!(data_model, r_s, zsc, ysh; n_phases=3) bus_ids = Dict() for bus in buses - bus_ids[bus] = add_virtual_get_id!(data_model, "bus", create_bus(id="")) + bus_obj = Dict{String,Any}( + "name" => "_virtual_bus.transformer.$(transformer_name)_$(bus)", + "bus_i" => length(data_math["bus"])+1, + "vm" => fill(1.0, nphases), + "va" => fill(0.0, nphases), + "vmin" => fill(0.0, nphases), + "vmax" => fill(Inf, nphases), + "base_kv" => 1.0, + "bus_type" => 1, + "status" => 1, + "index" => length(data_math["bus"])+1, + ) + + data_math["bus"]["$(bus_obj["index"])"] = bus_obj + + bus_ids[bus] = bus_obj["bus_i"] + + push!(to_map, "bus.$(bus_obj["index"])") end - line_ids = Dict() + + for (l,(i,j)) in lines # merge the shunts into the shunts of the pi model of the line g_fr = b_fr = g_to = b_to = 0 + if haskey(shunts, i) g_fr = real(shunts[i]) b_fr = imag(shunts[i]) delete!(shunts, i) end + if haskey(shunts, j) g_fr = real(shunts[j]) b_fr = imag(shunts[j]) delete!(shunts, j) end - line_ids[l] = add_virtual_get_id!(data_model, "line", Dict( - "status"=>1, - "f_bus"=>bus_ids[i], "t_bus"=>bus_ids[j], - "f_connections"=>collect(1:n_phases), - "t_connections"=>collect(1:n_phases), - "rs"=>LinearAlgebra.diagm(0=>fill(real(z[l]), n_phases)), - "xs"=>LinearAlgebra.diagm(0=>fill(imag(z[l]), n_phases)), - "g_fr"=>LinearAlgebra.diagm(0=>fill(g_fr, n_phases)), - "b_fr"=>LinearAlgebra.diagm(0=>fill(b_fr, n_phases)), - "g_to"=>LinearAlgebra.diagm(0=>fill(g_to, n_phases)), - "b_to"=>LinearAlgebra.diagm(0=>fill(b_to, n_phases)), - )) - end - return bus_ids, line_ids, [bus_ids[bus] for bus in tr_t_bus] -end + branch_obj = Dict{String,Any}( + "name" => "_virtual_branch.transformer.$(transformer_name)_$(l)", + "source_id" => "_virtual_branch.transformer.$(transformer_name)_$(l)", + "index" => length(data_math["branch"])+1, + "br_status"=>1, + "f_bus"=>bus_ids[i], + "t_bus"=>bus_ids[j], + "f_connections"=>collect(1:nphases), + "t_connections"=>collect(1:nphases), + "br_r" => diagm(0=>fill(real(z[l]), nphases)), + "br_x" => diagm(0=>fill(imag(z[l]), nphases)), + "g_fr" => diagm(0=>fill(g_fr, nphases)), + "b_fr" => diagm(0=>fill(b_fr, nphases)), + "g_to" => diagm(0=>fill(g_to, nphases)), + "b_to" => diagm(0=>fill(b_to, nphases)), + "angmin" => fill(-60.0, nphases), + "angmax" => fill( 60.0, nphases), + "shift" => zeros(nphases), + "tap" => ones(nphases) + ) + data_math["branch"]["$(branch_obj["index"])"] = branch_obj -"" -function _alias!(dict, fr, to) - if haskey(dict, fr) - dict[to] = dict[fr] + push!(to_map, "branch.$(branch_obj["index"])") end + + return [bus_ids[bus] for bus in tr_t_bus] end @@ -1193,102 +1064,6 @@ function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, end -"" -function data_model_make_compatible_v8!(data_model; phases=[1, 2, 3], neutral=4) - data_model["conductors"] = 3 - data_model["buspairs"] = nothing - for (_, bus) in data_model["bus"] - bus["bus_i"] = bus["index"] - terminals = bus["terminals"] - @assert(all(t in [phases..., neutral] for t in terminals)) - for prop in ["vm", "va", "vmin", "vmax"] - if haskey(bus, prop) - if length(bus[prop])==4 - val = bus[prop] - bus[prop] = val[terminals.!=neutral] - end - end - end - end - - for (_, load) in data_model["load"] - # remove neutral - if load["configuration"]=="wye" - bus = data_model["bus"][string(load["bus"])] - @assert(length(bus["grounded"])==1 && bus["grounded"][1]==load["connections"][end]) - load["connections"] = load["connections"][1:end-1] - _pad_properties!(load, ["pd", "qd"], load["connections"], phases) - else - # three-phase loads can only be delta-connected - #@assert(all(load["connections"].==phases)) - end - _alias!(load, "bus", "load_bus") - end - - data_model["gen"] = data_model["generator"] - - # has to be three-phase - for (_, gen) in data_model["gen"] - if gen["configuration"]=="wye" - @assert(all(gen["connections"].==[phases..., neutral])) - else - @assert(all(gen["connections"].==phases)) - end - - _alias!(gen, "status", "gen_status") - _alias!(gen, "bus", "gen_bus") - _alias!(gen, "pg_min", "pmin") - _alias!(gen, "qg_min", "qmin") - _alias!(gen, "pg_max", "pmax") - _alias!(gen, "qg_max", "qmax") - _alias!(gen, "configuration", "conn") - - gen["model"] = 2 - end - - data_model["branch"] = data_model["line"] - for (_, br) in data_model["branch"] - @assert(all(x in phases for x in br["f_connections"])) - @assert(all(x in phases for x in br["t_connections"])) - @assert(all(br["f_connections"].==br["t_connections"])) - - _pad_properties!(br, ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to", "s_rating", "c_rating"], br["f_connections"], phases) - - # rename - _alias!(br, "status", "br_status") - _alias!(br, "rs", "br_r") - _alias!(br, "xs", "br_x") - - br["tap"] = 1.0 - br["shift"] = 0 - - if !haskey(br, "angmin") - N = size(br["br_r"])[1] - br["angmin"] = fill(-pi/2, N) - br["angmax"] = fill(pi/2, N) - end - end - - for (_, shunt) in data_model["shunt"] - @assert(all(x in phases for x in shunt["connections"])) - _pad_properties!(shunt, ["g_sh", "b_sh"], shunt["connections"], phases) - _alias!(shunt, "bus", "shunt_bus") - _alias!(shunt, "g_sh", "gs") - _alias!(shunt, "b_sh", "bs") - end - - data_model["dcline"] = Dict() - data_model["transformer"] = data_model["transformer"] - - data_model["per_unit"] = true - data_model["baseMVA"] = data_model["settings"]["sbase"]*data_model["settings"]["v_var_scalar"]/1E6 - data_model["name"] = "IDC" - - - return data_model -end - - # MAP SOLUTION UP "" function solution_unmap!(solution::Dict, data_model::Dict) @@ -1334,13 +1109,6 @@ function solution_unmap!(solution::Dict, data_model::Dict) end -"" -function transform_solution!(solution, data_model) - solution_make_si!(solution, data_model) - solution_identify!(solution, data_model) - solution_unmap!(solution, data_model) -end - """ Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the From ae5bcfaca14f3d87abdcc32b808d79d5ff6e351b Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 4 Mar 2020 12:10:31 -0700 Subject: [PATCH 057/224] FIX: line / linecode properties changed properties on lines and linecodes from cs to g_fr, b_fr, g_to, b_to to be more extensible in the future --- src/core/data_model_mapping.jl | 20 ++++++++++---------- src/io/opendss_dm.jl | 11 +++++++++-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index d2c024ebc..2a97fcf16 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -409,7 +409,7 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any if haskey(eng_obj, "linecode") linecode = data_eng["linecode"][eng_obj["linecode"]] - for property in ["rs", "xs", "cs"] + for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] if !haskey(eng_obj, property) && haskey(linecode, property) eng_obj[property] = linecode[property] end @@ -430,11 +430,11 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] / Zbase math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] / Zbase - math_obj["g_fr"] = fill(0.0, nphases, nphases) - math_obj["g_to"] = fill(0.0, nphases, nphases) + math_obj["g_fr"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) + math_obj["g_to"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) - math_obj["b_fr"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0 - math_obj["b_to"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0 + math_obj["b_fr"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) + math_obj["b_to"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) math_obj["angmin"] = fill(-60.0, nphases) math_obj["angmax"] = fill( 60.0, nphases) @@ -493,7 +493,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A if haskey(eng_obj, "linecode") linecode = data_eng["linecode"][eng_obj["linecode"]] - for property in ["rs", "xs", "cs"] + for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] if !haskey(eng_obj, property) && haskey(linecode, property) eng_obj[property] = linecode[property] end @@ -513,10 +513,10 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, - "g_fr" => fill(0.0, nphases, nphases), - "g_to" => fill(0.0, nphases, nphases), - "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0, - "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["cs"] * eng_obj["length"] / 1e9) / 2.0, + "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9), + "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9), + "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9), + "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9), "angmin" => fill(-60.0, nphases), "angmax" => fill( 60.0, nphases), "transformer" => false, diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 22b2148b4..39f4656dd 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -403,7 +403,11 @@ function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["rs"] = reshape(defaults["rmatrix"], nphases, nphases) eng_obj["xs"] = reshape(defaults["xmatrix"], nphases, nphases) - eng_obj["cs"] = reshape(defaults["cmatrix"], nphases, nphases) + + eng_obj["b_fr"] = reshape(defaults["cmatrix"], nphases, nphases) ./ 2.0 + eng_obj["b_to"] = reshape(defaults["cmatrix"], nphases, nphases) ./ 2.0 + eng_obj["g_fr"] = fill(0.0, nphases, nphases) + eng_obj["g_to"] = fill(0.0, nphases, nphases) if !haskey(data_eng, "linecode") data_eng["linecode"] = Dict{String,Any}() @@ -450,7 +454,10 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An end if any(haskey(dss_obj, key) for key in ["b0", "b1", "c0", "c1", "cmatrix"]) || !haskey(dss_obj, "linecode") - eng_obj["cs"] = reshape(defaults["cmatrix"], nphases, nphases) + eng_obj["b_fr"] = defaults["cmatrix"] ./ 2.0 + eng_obj["b_to"] = defaults["cmatrix"] ./ 2.0 + eng_obj["g_fr"] = fill(0.0, nphases, nphases) + eng_obj["g_to"] = fill(0.0, nphases, nphases) end eng_obj["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) From d93cbb131fbb5cc5f0ff55a9bd56c22b236475b5 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 4 Mar 2020 13:02:48 -0700 Subject: [PATCH 058/224] FIX: shunt reactor --- src/core/data_model_mapping.jl | 28 +++++++++++++++++-- src/io/opendss_dm.jl | 49 +++++++++++++--------------------- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/core/data_model_mapping.jl b/src/core/data_model_mapping.jl index 2a97fcf16..ec2349a86 100644 --- a/src/core/data_model_mapping.jl +++ b/src/core/data_model_mapping.jl @@ -5,7 +5,7 @@ const _1to1_maps = Dict{String,Vector{String}}( "bus" => ["vm", "va", "vmin", "vmax"], "load" => ["model", "configuration", "status", "source_id"], "capacitor" => ["status"], - "shunt_reactor" => ["status"], + "shunt_reactor" => ["status", "source_id"], "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], "pvsystem" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], "storage" => ["status", "source_id"], @@ -219,6 +219,7 @@ function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj = _init_math_obj("capacitor", eng_obj, length(data_math["shunt"])+1) # TODO change to new capacitor shunt calc logic + math_obj["name"] = name math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] nphases = eng_obj["phases"] @@ -273,6 +274,30 @@ end "" function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) + for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt_reactor", eng_obj, length(data_math["shunt"])+1) + + nphases = eng_obj["phases"] + + Zbase = (data_math["basekv"] / sqrt(3.0))^2 * nphases / data_math["baseMVA"] # Use single-phase base impedance for each phase + Gcap = Zbase * sum(eng_obj["kvar"]) / (nphases * 1e3 * (data_math["basekv"] / sqrt(3.0))^2) + + math_obj["name"] = name + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + + math_obj["gs"] = fill(0.0, nphases, nphases) + math_obj["bs"] = diagm(0=>fill(Gcap, nphases)) + + data_math["shunt"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => "shunt_reactor.$name", + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_shunt_reactor!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["shunt_reactor"]) + ) + end end @@ -307,7 +332,6 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ data_math["gen"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :component_type => "generator", :from => "generator.$name", :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_generator!, diff --git a/src/io/opendss_dm.jl b/src/io/opendss_dm.jl index 39f4656dd..ce712e08b 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss_dm.jl @@ -286,41 +286,30 @@ end "Adds shunt reactors to `data_eng` from `data_dss`" function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - #TODO revisit this in the future - # for (name, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) - # if !haskey(dss_obj, "bus2") - # _apply_like!(dss_obj, data_dss, "reactor") - # defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) - - # eng_obj = Dict{String,Any}() - - # nphases = defaults["phases"] - # name, nodes = _parse_busname(defaults["bus1"]) - - # Zbase = (data_eng["basekv"] / sqrt(3.0))^2 * nphases / data_eng["baseMVA"] # Use single-phase base impedance for each phase - # Gcap = Zbase * sum(defaults["kvar"]) / (nphases * 1e3 * (data_eng["basekv"] / sqrt(3.0))^2) + for (name, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) + if !haskey(dss_obj, "bus2") + _apply_like!(dss_obj, data_dss, "reactor") + defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) - # eng_obj["shunt_bus"] = find_bus(name, data_eng) - # eng_obj["name"] = name - # eng_obj["gs"] = _parse_array(0.0, nodes, nphases) # TODO: - # eng_obj["bs"] = _parse_array(Gcap, nodes, nconductors) - # eng_obj["status"] = convert(Int, defaults["enabled"]) - # eng_obj["index"] = length(data_eng["shunt"]) + 1 + eng_obj = Dict{String,Any}() - # eng_obj["active_phases"] = [n for n in 1:nconductors if nodes[n] > 0] - # eng_obj["source_id"] = "reactor.$(name)" + eng_obj["phases"] = defaults["phases"] + eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] + eng_obj["kvar"] = defaults["kvar"] + eng_obj["status"] = convert(Int, defaults["enabled"]) + eng_obj["source_id"] = "reactor.$name" - # if import_all - # _import_all!(eng_obj, defaults, dss_obj["prop_order"]) - # end + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end - # if !haskey(data_eng, "shunt_reactor") - # data_eng["shunt_reactor"] = Dict{String,Any}() - # end + if !haskey(data_eng, "shunt_reactor") + data_eng["shunt_reactor"] = Dict{String,Any}() + end - # data_eng["shunt_reactor"][name] = eng_obj - # end - # end + data_eng["shunt_reactor"][name] = eng_obj + end + end end From bcf48efcea740c8a7240b88cf54128e9aa49f8d3 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 4 Mar 2020 15:34:25 -0700 Subject: [PATCH 059/224] REF: file names & function locations created data_model folder and moved following files src/io/data_model_components.jl -> src/data_model/components.jl src/io/data_model_test.jl -> src/data_model/data_model_test.jl src/core/data_model_mapping.jl -> src/data_model/eng2math.jl src/core/data_model_pu.jl -> src/data_model/units.jl src/io/data_model_util.jl -> src/data_model/utils.jl src/io/common_dm.jl -> src/io/common.jl src/io/opendss_dm.jl -> src/io/opendss.jl --- src/PowerModelsDistribution.jl | 17 +- .../components.jl} | 0 src/{io => data_model}/data_model_test.jl | 0 .../eng2math.jl} | 0 src/data_model/math2eng.jl | 0 .../data_model_pu.jl => data_model/units.jl} | 0 .../utils.jl} | 0 src/io/{common_dm.jl => common.jl} | 0 src/io/{opendss_dm.jl => opendss.jl} | 399 +++--------------- 9 files changed, 69 insertions(+), 347 deletions(-) rename src/{io/data_model_components.jl => data_model/components.jl} (100%) rename src/{io => data_model}/data_model_test.jl (100%) rename src/{core/data_model_mapping.jl => data_model/eng2math.jl} (100%) create mode 100644 src/data_model/math2eng.jl rename src/{core/data_model_pu.jl => data_model/units.jl} (100%) rename src/{io/data_model_util.jl => data_model/utils.jl} (100%) rename src/io/{common_dm.jl => common.jl} (100%) rename src/io/{opendss_dm.jl => opendss.jl} (71%) diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 744bca09f..49c001acc 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -39,17 +39,18 @@ module PowerModelsDistribution include("core/constraint_template.jl") include("core/relaxation_scheme.jl") - include("io/common_dm.jl") - include("io/opendss_dm.jl") - - include("io/json.jl") + include("io/utils.jl") include("io/dss_parse.jl") include("io/dss_structs.jl") + include("io/opendss.jl") + include("io/json.jl") + include("io/common.jl") - include("io/data_model_components.jl") - include("core/data_model_mapping.jl") - include("core/data_model_pu.jl") - include("io/data_model_util.jl") + include("data_model/utils.jl") + include("data_model/components.jl") + include("data_model/eng2math.jl") + include("data_model/math2eng.jl") + include("data_model/units.jl") include("prob/mld.jl") include("prob/opf.jl") diff --git a/src/io/data_model_components.jl b/src/data_model/components.jl similarity index 100% rename from src/io/data_model_components.jl rename to src/data_model/components.jl diff --git a/src/io/data_model_test.jl b/src/data_model/data_model_test.jl similarity index 100% rename from src/io/data_model_test.jl rename to src/data_model/data_model_test.jl diff --git a/src/core/data_model_mapping.jl b/src/data_model/eng2math.jl similarity index 100% rename from src/core/data_model_mapping.jl rename to src/data_model/eng2math.jl diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/data_model_pu.jl b/src/data_model/units.jl similarity index 100% rename from src/core/data_model_pu.jl rename to src/data_model/units.jl diff --git a/src/io/data_model_util.jl b/src/data_model/utils.jl similarity index 100% rename from src/io/data_model_util.jl rename to src/data_model/utils.jl diff --git a/src/io/common_dm.jl b/src/io/common.jl similarity index 100% rename from src/io/common_dm.jl rename to src/io/common.jl diff --git a/src/io/opendss_dm.jl b/src/io/opendss.jl similarity index 71% rename from src/io/opendss_dm.jl rename to src/io/opendss.jl index ce712e08b..4715e4b90 100644 --- a/src/io/opendss_dm.jl +++ b/src/io/opendss.jl @@ -1,39 +1,5 @@ # OpenDSS parser -import LinearAlgebra: isdiag, diag, pinv - - -const _exclude_duplicate_check = ["options", "filename", "circuit"] - -const _dss_edge_components = ["line", "transformer", "reactor"] - -const _dss_supported_components = ["line", "linecode", "load", "generator", "capacitor", "reactor", "circuit", "transformer", "pvsystem", "storage", "loadshape"] - -const _dss_option_dtypes = Dict{String,Type}( - "defaultbasefreq" => Float64, - "voltagebases" => Float64, - "tolerance" => Float64) - - -"Discovers all of the buses (not separately defined in OpenDSS), from \"lines\"" -function _discover_buses(data_dss::Dict{String,<:Any})::Set - buses = Set([]) - for obj_type in _dss_edge_components - for (name, dss_obj) in get(data_dss, obj_type, Dict{String,Any}()) - if obj_type == "transformer" - dss_obj = _create_transformer(dss_obj["name"]; _to_sym_keys(dss_obj)...) - for bus in dss_obj["buses"] - push!(buses, split(bus, '.'; limit=2)[1]) - end - elseif haskey(dss_obj, "bus2") - for key in ["bus1", "bus2"] - push!(buses, split(dss_obj[key], '.'; limit=2)[1]) - end - end - end - end - - return buses -end +import LinearAlgebra: diagm "Parses buscoords [lon,lat] (if present) into their respective buses" @@ -66,47 +32,11 @@ function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any end -"Adds sourcebus as a voltage source to `data_eng` from `data_dss`" -function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) - circuit = _create_vsource(get(data_dss["circuit"], "bus1", "sourcebus"), data_dss["circuit"]["name"]; _to_sym_keys(data_dss["circuit"])...) - - nodes = Array{Bool}([1 1 1 0]) - ph1_ang = circuit["angle"] - vm_pu = circuit["pu"] - - phases = circuit["phases"] - vnom = data_eng["settings"]["set_vbase_val"] - - vm = fill(vm_pu, phases)*vnom - va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) - - eng_obj = Dict{String,Any}( - "bus" => circuit["bus1"], - "connections" => collect(1:phases), - "vm" => vm, - "va" => va, - "rs" => circuit["rmatrix"], - "xs" => circuit["xmatrix"], - "status" => 1 - ) - - if import_all - _import_all!(eng_obj, circuit, data_dss["circuit"]["prop_order"]) - end - - if !haskey(data_eng, "voltage_source") - data_eng["voltage_source"] = Dict{String,Any}() - end - - data_eng["voltage_source"][circuit["name"]] = eng_obj -end - - "Adds loadshapes to `data_eng` from `data_dss`" function _dss2eng_loadshape!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) for (name, dss_obj) in get(data_dss, "loadshape", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "loadshape") - defaults = _apply_ordered_properties(_create_loadshape(name; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_loadshape(name; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -146,7 +76,7 @@ Note that in the current feature set, fixed therefore equals constant function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, ground_terminal::Int=4) for (name, dss_obj) in get(data_dss, "load", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "load") - defaults = _apply_ordered_properties(_create_load(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_load(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) # parse the model model = defaults["model"] @@ -231,7 +161,7 @@ end function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "capacitor", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "capacitor") - defaults = _apply_ordered_properties(_create_capacitor(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_capacitor(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] @@ -289,7 +219,7 @@ function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{St for (name, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) if !haskey(dss_obj, "bus2") _apply_like!(dss_obj, data_dss, "reactor") - defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -313,28 +243,13 @@ function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{St end -""" -Given a vector and a list of elements to find, this method will return a list -of the positions of the elements in that vector. -""" -function _get_idxs(vec::Vector{<:Any}, els::Vector{<:Any})::Vector{Int} - ret = Array{Int, 1}(undef, length(els)) - for (i,f) in enumerate(els) - for (j,l) in enumerate(vec) - if f==l - ret[i] = j - end - end - end - return ret -end "Adds generators to `data_eng` from `data_dss`" function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "generator", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "generator") - defaults = _apply_ordered_properties(_create_generator(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_generator(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -376,6 +291,20 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String end +"Adds vsources to `data_eng` from `data_dss`" +function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + for (name, dss_obj) in get(data_dss, "vsource", Dict{<:Any,<:Any}()) + eng_obj = Dict{String,Any}() + + if !haskey(data_eng, "vsource") + data_eng["vsource"] = Dict{String,Dict{String,Any}}() + end + + data_eng["vsource"][name] = eng_obj + end +end + + "Adds lines to `data_eng` from `data_dss`" function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "linecode", Dict{String,Any}()) @@ -384,7 +313,7 @@ function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, dss_obj["units"] = get(dss_obj, "units", "none") dss_obj["circuit_basefreq"] = data_eng["settings"]["basefreq"] - defaults = _apply_ordered_properties(_create_linecode(name; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_linecode(name; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -417,7 +346,7 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An dss_obj["circuit_basefreq"] = data_eng["settings"]["basefreq"] end - defaults = _apply_ordered_properties(_create_line(dss_obj["bus1"], dss_obj["bus2"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_line(dss_obj["bus1"], dss_obj["bus2"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -477,19 +406,6 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An end -"" -function _barrel_roll(x::Vector, shift) - N = length(x) - if shift < 0 - shift = shift + ceil(Int, shift/N)*N - end - - shift = mod(shift, N) - - return x[[(i-1+shift)%N+1 for i in 1:N]] -end - - "Adds transformers to `data_eng` from `data_dss`" function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) if !haskey(data_eng, "transformer") @@ -498,7 +414,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri for (name, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "transformer") - defaults = _apply_ordered_properties(_create_transformer(dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_transformer(dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] nrw = defaults["windings"] @@ -607,7 +523,7 @@ function _dss2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{Str if haskey(dss_obj, "bus2") Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating reactor.$name like line") _apply_like!(dss_obj, data_dss, "reactor") - defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], name, dss_obj["bus2"]; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], name, dss_obj["bus2"]; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -626,7 +542,7 @@ function _dss2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{Str eng_obj["b_to"] = fill(0.0, nphases) for key in ["g_fr", "g_to", "b_fr", "b_to"] - eng_obj[key] = LinearAlgebra.diagm(0=>eng_obj[key]) + eng_obj[key] = diagm(0=>eng_obj[key]) end eng_obj["c_rating_a"] = defaults["normamps"] @@ -665,7 +581,7 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, Memento.warn(_LOGGER, "Converting PVSystem \"$(dss_obj["name"])\" into generator with limits determined by OpenDSS property 'kVA'") _apply_like!(dss_obj, data_dss, "pvsystem") - defaults = _apply_ordered_properties(_create_pvsystem(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_pvsystem(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -706,7 +622,7 @@ end function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "storage", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "storage") - defaults = _apply_ordered_properties(_create_storage(dss_obj["bus1"], dss_obj["name"]; _to_sym_keys(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_storage(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -748,111 +664,51 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< end -"Adds vsources to `data_eng` from `data_dss`" -function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "vsource", Dict{<:Any,<:Any}()) - eng_obj = Dict{String,Any}() - - if !haskey(data_eng, "vsource") - data_eng["vsource"] = Dict{String,Dict{String,Any}}() - end - - data_eng["vsource"][name] = eng_obj - end -end - - -"This function appends a component to a component dictionary of a pmd data model" -function _push_dict_ret_key!(dict::Dict{String,<:Any}, v::Dict{String,<:Any}; assume_no_gaps::Bool=false) - if isempty(dict) - k = 1 - elseif assume_no_gaps - k = length(keys(dict))+1 - else - k = maximum([parse(Int, x) for x in keys(dict)])+1 - end - - dict[string(k)] = v - v["index"] = k - return k -end +"Adds sourcebus as a voltage source to `data_eng` from `data_dss`" +function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) + circuit = _create_vsource(get(data_dss["circuit"], "bus1", "sourcebus"), data_dss["circuit"]["name"]; _to_kwargs(data_dss["circuit"])...) + nodes = Array{Bool}([1 1 1 0]) + ph1_ang = circuit["angle"] + vm_pu = circuit["pu"] -"Creates a virtual branch between the `virtual_sourcebus` and `sourcebus` with the impedance given by `circuit`" -function _create_sourcebus_vbranch_dm!(data_eng::Dict{String,<:Any}, circuit::Dict{String,<:Any}) - #TODO convert to pu - rs = circuit["rmatrix"] - xs = circuit["xmatrix"] + phases = circuit["phases"] + vnom = data_eng["settings"]["set_vbase_val"] - N = size(rs)[1] + vm = fill(vm_pu, phases)*vnom + va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) - create_line(data_eng, id="_virtual_source_imp", - f_bus="_virtual_sourcebus", t_bus="sourcebus", - f_connections=collect(1:N), t_connections=collect(1:N), - rs=rs, xs=xs + eng_obj = Dict{String,Any}( + "bus" => circuit["bus1"], + "connections" => collect(1:phases), + "vm" => vm, + "va" => va, + "rs" => circuit["rmatrix"], + "xs" => circuit["xmatrix"], + "status" => 1 ) - #vbranch = _create_vbranch!(data_eng, sourcebus, vsourcebus; name="sourcebus_vbranch", br_r=br_r, br_x=br_x) -end + if import_all + _import_all!(eng_obj, circuit, data_dss["circuit"]["prop_order"]) + end -"Combines transformers with 'bank' keyword into a single transformer" -function _bank_transformers!(data_eng::Dict{String,<:Any}) - banks = Dict{String,Array{String,1}}() - for (name, transformer) in get(data_eng, "transformer", Dict{String,Any}()) - if haskey(transformer, "bank") - if !haskey(banks, transformer["bank"]) - banks[transformer["bank"]] = Array{String,1}([name]) - else - push!(banks[transformer["bank"]], name) - end - end + if !haskey(data_eng, "voltage_source") + data_eng["voltage_source"] = Dict{String,Any}() end - banked = Dict{String,Any}() - for (bank, names) in banks - transformers = [data_eng["transformer"][name] for name in names] + data_eng["voltage_source"][circuit["name"]] = eng_obj +end - total_phases = sum(Int[transformer["nphases"] for transformer in transformers]) - # TODO - if !haskey(banked, bank) - banked[bank] = deepcopy(transformers[1]) - end - end - for (bank, names) in banks - if haskey(banked, bank) - for name in names - delete!(data_eng["transformer"], name) - end - end - data_eng["transformer"][bank] = banked[bank] - end -end -""" - parse_options(options) +"Parses a DSS file into a PowerModels usable format" +function parse_opendss_dm(io::IOStream; import_all::Bool=false, bank_transformers::Bool=true)::Dict + data_dss = parse_dss(io) -Parses options defined with the `set` command in OpenDSS. -""" -function parse_options(options::Dict{String,<:Any}) - out = Dict{String,Any}() - - for (option, dtype) in _dss_option_dtypes - if haskey(options, option) - value = options[option] - if _isa_array(value) - out[option] = _parse_array(dtype, value) - elseif _isa_matrix(value) - out[option] = _parse_matrix(dtype, value) - else - out[option] = parse(dtype, value) - end - end - end - return out + return parse_opendss_dm(data_dss; import_all=import_all) end @@ -860,9 +716,9 @@ end function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, bank_transformers::Bool=true)::Dict{String,Any} data_eng = create_data_model() - parse_dss_with_dtypes!(data_dss, _dss_supported_components) + # parse_dss_with_dtypes!(data_dss, _dss_supported_components) - data_dss["options"] = parse_options(get(data_dss, "options", Dict{String,Any}())) + data_dss["options"] = _parse_options(get(data_dss, "options", Dict{String,Any}())) if import_all data_eng["dss_options"] = data_dss["options"] @@ -870,7 +726,7 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, if haskey(data_dss, "circuit") circuit = data_dss["circuit"] - defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), circuit["name"]; _to_sym_keys(circuit)...) + defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), circuit["name"]; _to_kwargs(circuit)...) data_eng["name"] = circuit["name"] data_eng["sourcebus"] = defaults["bus1"] @@ -896,7 +752,7 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, _dss2eng_linecode!(data_eng, data_dss, import_all) _dss2eng_line!(data_eng, data_dss, import_all) - # _dss2eng_xfrmcode!(data_eng, data_dss, import_all) + # _dss2eng_xfrmcode!(data_eng, data_dss, import_all) # TODO _dss2eng_transformer!(data_eng, data_dss, import_all) _dss2eng_line_reactor!(data_eng, data_dss, import_all) @@ -922,138 +778,3 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, return data_eng end - - -"" -function _discover_terminals!(data_eng::Dict{String,<:Any}) - terminals = Dict{String, Set{Int}}([(name, Set{Int}()) for (name,bus) in data_eng["bus"]]) - - for (_,dss_obj) in data_eng["line"] - # ignore 0 terminal - push!(terminals[dss_obj["f_bus"]], setdiff(dss_obj["f_connections"], [0])...) - push!(terminals[dss_obj["t_bus"]], setdiff(dss_obj["t_connections"], [0])...) - end - - if haskey(data_eng, "transformer") - for (_,tr) in data_eng["transformer"] - for w in 1:length(tr["bus"]) - # ignore 0 terminal - push!(terminals[tr["bus"][w]], setdiff(tr["connections"][w], [0])...) - end - end - end - - for (id, bus) in data_eng["bus"] - data_eng["bus"][id]["terminals"] = sort(collect(terminals[id])) - end - - # identify neutrals and propagate along cables - bus_neutral = _find_neutrals(data_eng) - - for (id,bus) in data_eng["bus"] - if haskey(bus, "awaiting_ground") || haskey(bus_neutral, id) - # this bus will need a neutral - if haskey(bus_neutral, id) - neutral = bus_neutral[id] - else - neutral = maximum(bus["terminals"])+1 - push!(bus["terminals"], neutral) - end - bus["neutral"] = neutral - if haskey(bus, "awaiting_ground") - bus["grounded"] = [neutral] - bus["rg"] = [0.0] - bus["xg"] = [0.0] - for comp in bus["awaiting_ground"] - if eltype(comp["connections"])<:Array - for w in 1:length(comp["connections"]) - if comp["bus"][w]==id - comp["connections"][w] .+= (comp["connections"][w].==0)*neutral - end - end - else - comp["connections"] .+= (comp["connections"].==0)*neutral - end - # @show comp["connections"] - end - #delete!(bus, "awaiting_ground") - end - end - phases = haskey(bus, "neutral") ? setdiff(bus["terminals"], bus["neutral"]) : bus["terminals"] - bus["phases"] = phases - end -end - - -"" -function _find_neutrals(data_eng::Dict{String,<:Any}) - vertices = [(id, t) for (id, bus) in data_eng["bus"] for t in bus["terminals"]] - neutrals = [] - edges = Set([((dss_obj["f_bus"], dss_obj["f_connections"][c]),(dss_obj["t_bus"], dss_obj["t_connections"][c])) for (id, dss_obj) in data_eng["line"] for c in 1:length(dss_obj["f_connections"])]) - - bus_neutrals = [(id,bus["neutral"]) for (id,bus) in data_eng["bus"] if haskey(bus, "neutral")] - trans_neutrals = [] - for (_, tr) in data_eng["transformer"] - for w in 1:length(tr["connections"]) - if tr["configuration"][w] == "wye" - push!(trans_neutrals, (tr["bus"][w], tr["connections"][w][end])) - end - end - end - load_neutrals = [(dss_obj["bus"],dss_obj["connections"][end]) for (_,dss_obj) in get(data_eng, "load", Dict{String,Any}()) if dss_obj["configuration"]=="wye"] - neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) - neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) - stack = deepcopy(neutrals) - while !isempty(stack) - vertex = pop!(stack) - candidates_t = [((f,t), t) for (f,t) in edges if f==vertex] - candidates_f = [((f,t), f) for (f,t) in edges if t==vertex] - for (edge,next) in [candidates_t..., candidates_f...] - delete!(edges, edge) - push!(stack, next) - push!(neutrals, next) - end - end - bus_neutral = Dict{String, Int}() - for (bus,t) in neutrals - bus_neutral[bus] = t - end - return bus_neutral -end - - -"Parses a DSS file into a PowerModels usable format" -function parse_opendss_dm(io::IOStream; import_all::Bool=false, bank_transformers::Bool=true)::Dict - data_dss = parse_dss(io) - - return parse_opendss_dm(data_dss; import_all=import_all) -end - - -"Returns an ordered list of defined conductors. If ground=false, will omit any `0`" -function _get_conductors_ordered_dm(busname::AbstractString; default::Array=[], check_length::Bool=true, pad_ground::Bool=false)::Array - parts = split(busname, '.'; limit=2) - ret = [] - if length(parts)==2 - conds_str = split(parts[2], '.') - ret = [parse(Int, i) for i in conds_str] - else - return default - end - - if pad_ground && length(ret)==length(default)-1 - ret = [ret..., 0] - end - - if check_length && length(default)!=length(ret) - # TODO - Memento.warn(_LOGGER, "An incorrect number of nodes was specified on $(parts[1]); |$(parts[2])|!=$(length(default)).") - end - return ret -end - - -"" -function _import_all!(component::Dict{String,<:Any}, defaults::Dict{String,<:Any}, prop_order::Array{<:AbstractString,1}) - component["dss"] = Dict{String,Any}((key, defaults[key]) for key in prop_order) -end From 2aca64e0567cb2fa679a678f878970dd9b543ab2 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 4 Mar 2020 15:37:05 -0700 Subject: [PATCH 060/224] REF: refactored dss parser Moved some helper functions to a utils.jl file Updated parser logic to automatically parse the known data types in dss structures instead of leaving them as strings. This will make it easier for users to directly use the raw dss format and to quickly add new component constructor functions --- src/io/dss_parse.jl | 924 ++++++++++-------------------------------- src/io/dss_structs.jl | 858 +++++++++++++++++++-------------------- src/io/opendss.jl | 4 - src/io/utils.jl | 692 +++++++++++++++++++++++++++++++ test/opendss.jl | 8 +- test/transformer.jl | 2 +- 6 files changed, 1344 insertions(+), 1144 deletions(-) create mode 100644 src/io/utils.jl diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 6190158a0..ba7c47e66 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -1,451 +1,210 @@ -"Squares `x`, for parsing Reverse Polish Notation" -function _sqr(x::Float64) - return x * x -end - - -_double_operators = Dict("+" => +, "-" => -, "*" => *, "/" => /, "^" => ^, "atan2" => (x, y) -> rad2deg(atan2(y, x))) - -_single_operators = Dict("sqr" => _sqr, "sqrt" => sqrt, "inv" => inv, "ln" => log, - "exp" => exp, "log10" => log10, "sin" => sind, "cos" => cosd, - "tan" => tand, "asin" => asind, "acos" => acosd, "atan" => atand) - -const _array_delimiters = ['\"', '\'', '[', '{', '(', ']', '}', ')'] - -"parses Reverse Polish Notation `expr`" -function _parse_rpn(expr::AbstractString, dtype::Type=Float64) - clean_expr = strip(expr, _array_delimiters) - - if occursin("rollup", clean_expr) || occursin("rolldn", clean_expr) || occursin("swap", clean_expr) - Memento.warn(_LOGGER, "_parse_rpn does not support \"rollup\", \"rolldn\", or \"swap\", leaving as String") - return expr - end - - stack = [] - split_expr = occursin(",", clean_expr) ? split(clean_expr, ',') : split(clean_expr) - - for item in split_expr - try - if haskey(_double_operators, item) - b = pop!(stack) - a = pop!(stack) - push!(stack, _double_operators[item](a, b)) - elseif haskey(_single_operators, item) - push!(stack, _single_operators[item](pop!(stack))) - else - if item == "pi" - push!(stack, pi) - else - push!(stack, parse(dtype, item)) - end - end - catch error - if isa(error, ArgumentError) - Memento.warn(_LOGGER, "\"$expr\" is not valid Reverse Polish Notation, leaving as String") - return expr - end - end - end - if length(stack) > 1 - Memento.warn(_LOGGER, "\"$expr\" is not valid Reverse Polish Notation, leaving as String") - return expr - else - return stack[1] - end -end - - -"detects if `expr` is Reverse Polish Notation expression" -function _isa_rpn(expr::AbstractString)::Bool - expr = split(strip(expr, _array_delimiters)) - op_keys = keys(merge(_double_operators, _single_operators)) - for item in expr - if item in op_keys - return true - end - end - return false -end - - -"parses connection \"conn\" specification reducing to wye or delta" -function _parse_conn(conn::String)::String - if conn in ["wye", "y", "ln"] - return "wye" - elseif conn in ["delta", "ll"] - return "delta" - else - Memento.warn(_LOGGER, "Unsupported connection $conn, defaulting to \"wye\"") - return "wye" - end -end - - -"checks is a string is a connection by checking the values" -function _isa_conn(expr::AbstractString)::Bool - if expr in ["wye", "y", "ln", "delta", "ll"] - return true - else - return false - end -end - - -""" - _get_prop_name(obj_type) - -Returns the property names in order for a given component type `obj_type`. -""" -function _get_prop_name(obj_type::AbstractString)::Array - linecode = ["nphases", "r1", "x1", "r0", "x0", "c1", "c0", "units", - "rmatrix", "xmatrix", "cmatrix", "basefreq", "normamps", - "emergamps", "faultrate", "pctperm", "repair", "kron", - "rg", "xg", "rho", "neutral", "b1", "b0", "like"] - - linegeometry = ["nconds", "nphases", "cond", "wire", "x", "h", "units", - "normamps", "emergamps", "reduce", "spacing", "wires", - "cncable", "tscable", "cncables", "tscables", "like"] - - linespacing = ["nconds", "nphases", "x", "h", "units"] - - loadshape = ["npts", "interval", "minterval", "sinterval", "pmult", - "qmult", "hour", "mean", "stddev", "csvfile", "sngfile", - "pqcsvfile", "action", "useactual", "pmax", "qmax", - "pbase", "like"] - - growthshape = ["npts", "year", "mult", "csvfile", "sngfile", "dblfile", - "like"] - - tcc_curve = ["npts", "c_array", "t_array", "like"] - - cndata = [] - - tsdata = [] - - wiredata = ["rdc", "rac", "runits", "gmrac", "gmrunits", "radius", - "radunits", "normamps", "emergamps", "diam", "like"] - - xfmrcode = [] - - vsource = ["bus1", "bus2", "basekv", "pu", "angle", "frequency", "phases", - "mvasc3", "mvasc1", "x1r1", "x0r0", "isc3", "isc1", "r1", "x1", - "r0", "x0", "scantype", "sequence", "spectrum", "z1", "z2", "z0", - "puz1", "puz2", "puz0", "basemva", "basefreq", "like", "enabled"] - - isource = ["phases", "bus1", "amps", "angle", "frequency", "scantype", - "sequence", "spectrum", "basefreq", "enabled", "like"] - - fault = ["phases", "bus1", "bus2", "r", "gmatrix", "minamps", "ontime", - "pctperm", "temporary", "%stddev", "normamps", "emergamps", - "basefreq", "faultrate", "repair", "enabled", "like"] - - capacitor = ["bus1", "bus2", "phases", "kvar", "kv", "conn", "cmatrix", - "cuf", "r", "xl", "harm", "numsteps", "states", "normamps", - "emergamps", "faultrate", "pctperm", "basefreq", "enabled", - "like"] - - line = ["bus1", "bus2", "linecode", "length", "phases", - "r1", "x1", "r0", "x0", "c1", "c0", "b1", - "b0", "normamps", "emergamps", "faultrate", "pctperm", - "repair", "basefreq", "rmatrix", "xmatrix", "cmatrix", - "switch", "rg", "xg", "rho", "geometry", "earthmodel", - "units", "enabled", "like"] - - reactor = ["phases", "bus1", "bus2", "kv", "kvar", "conn", "parallel", - "r", "rmatrix", "rp", "x", "xmatrix", "z", "z1", "z2", "z0", - "rcurve", "lcurve", "lmh", "normamps", "emergamps", "repair", - "faultrate", "pctperm", "basefreq", "enabled", "like"] - - transformer = ["phases", "windings", "wdg", "bus", "conn", "kv", "kva", - "tap", "%r", "rneut", "xneut", "buses", "conns", "kvs", - "kvas", "taps", "%rs", "xhl", "xlt", "xht", "xscarray", - "thermal", "n", "m", "flrise", "hsrise", "%loadloss", - "%noloadloss", "%imag", "ppm_antifloat", "normhkva", - "emerghkva", "sub", "maxtap", "mintap", "numtaps", - "subname", "bank", "xfmrcode", "xrconst", "leadlag", - "faultrate", "basefreq", "enabled", "like"] - - gictransformer = ["basefreq", "bush", "busnh", "busnx", "busx", - "emergamps", "enabled", "phases", "r1", "r2", "type", - "mva", "kvll1", "kvll2", "%r1", "%r2", "k", "varcurve", - "like", "normamps", "emergamps", "pctperm", "repair"] - - gicline = ["angle", "bus1", "bus2", "c", "ee", "en", "frequency", "lat1", - "lat2", "lon1", "lon2", "phases", "r", "volts", "x", "like", - "basefreq", "enabled", "spectrum"] - - load = ["phases", "bus1", "kv", "kw", "pf", "model", - "yearly", "daily", "duty", "growth", "conn", "kvar", - "rneut", "xneut", "status", "class", "vminpu", "vmaxpu", - "vminnorm", "vminemerg", "xfkva", "allocationfactor", - "kva", "%mean", "%stddev", "cvrwatts", "cvrvars", "kwh", - "kwhdays", "cfactor", "cvrcurve", "numcust", "spectrum", - "zipv", "%seriesrl", "relweight", "vlowpu", "puxharm", - "xrharm", "spectrum", "basefreq", "enabled", "like"] - - generator = ["bus1", "phases", "kv", "kw", "pf", "model", "yearly", - "daily", "duty", "dispvalue", "conn", "kvar", "rneut", - "xneut", "status", "class", "vpu", "maxkvar", "minkvar", - "pvfactor", "debugtrace", "vminpu", "vmaxpu", "forceon", - "kva", "mva", "xd", "xdp", "xdpp", "h", "d", "usermodel", - "userdata", "shaftmodel", "shaftdata", "dutystart", "balanced", - "xrdp", "spectrum", "basefreq", "enabled", "like"] - - indmach012 = ["phases", "bus1", "kv", "kw", "pf", "conn", "kva", "h", - "d", "purs", "puxs", "purr", "puxr", "puxm", "slip", - "maxslip", "slipoption", "spectrum", "enabled"] - - storage = ["phases", "bus1", "%charge", "%discharge", "%effcharge", "%idlingkvar", - "idlingkw", "%r", "%reserve", "%stored", "%x", "basefreq", - "chargetrigger", "class", "conn", "daily", "yearly", "debugtrace", - "dischargetrigger", "dispmode", "duty", "dynadata", "dynadll", "enabled", - "kv", "kva", "kvar", "kw", "kwhrated", "kwhstored", "kwrated", "like", - "model", "pf", "spectrum", "state", "timechargetrig", "userdata", - "usermodel", "vmaxpu", "vminpu", "yearly"] - - capcontrol = ["element", "capacitor", "type", "ctphase", "ctratio", "deadtime", - "delay", "delayoff", "eventlog", "offsetting", "onsetting", - "ptphase", "ptratio", "terminal", "vbus", "vmax", "vmin", - "voltoverride", "enabled"] - - regcontrol = ["transformer", "winding", "vreg", "band", "delay", "ptratio", - "ctprim", "r", "x", "pthase", "tapwinding", "bus", - "remoteptratio", "debugtrace", "eventlog", "inversetime", - "maxtapchange", "revband", "revdelay", "reversible", - "revneutral", "revr", "revthreshold", "revvreg", "revx", - "tapdelay", "tapnum", "vlimit", "ldc_z", "rev_z", "cogen", - "enabled"] - - energymeter = ["element", "terminal", "action", "clear", "save", "take", - "option", "kwnorm", "kwemerg", "peakcurrent", "zonelist", - "zonelist", "zonelist", "localonly", "mask", "losses", - "linelosses", "xfmrlosses", "seqlosses", "3phaselosses", - "vbaselosses", "basefreq", "enabled", "like"] - - pvsystem = ["phases", "bus1", "kv", "irradiance", "pmpp", "temperature", - "pf", "conn", "kvar", "kva", "%cutin", "%cutout", - "effcurve", "p-tcurve", "%r", "%x", "model", "vminpu", - "vmaxpu", "yearly", "daily", "duty", "tyearly", "tduty", - "class", "usermodel", "userdata", "debugtrace", "spectrum"] - - obj_types = Dict{String, Array}("linecode" => linecode, - "linegeometry" => linegeometry, - "linespacing" =>linespacing, - "loadshape" => loadshape, - "growthshape" => growthshape, - "tcc_curve" => tcc_curve, - "cndata" => cndata, - "tsdata" => tsdata, - "wiredata" => wiredata, - "xfmrcode" => xfmrcode, - "vsource" => vsource, - "isource" => isource, - "fault" => fault, - "capacitor" => capacitor, - "line" => line, - "reactor" => reactor, - "transformer" => transformer, - "gictransformer" => gictransformer, - "gicline" => gicline, - "load" => load, - "generator" => generator, - "indmach012" => indmach012, - "storage" => storage, - "capcontrol" => capcontrol, - "regcontrol" => regcontrol, - "energymeter" => energymeter, - "pvsystem" => pvsystem, - "circuit" => vsource - ) - - return obj_types[obj_type] -end - - -""" - _parse_matrix(dtype, data) - -Parses a OpenDSS style triangular matrix string `data` into a two dimensional -array of type `dtype`. Matrix strings are capped by either parenthesis or -brackets, rows are separated by "|", and columns are separated by spaces. -""" -function _parse_matrix(dtype::Type, data::AbstractString)::Array - rows = [] - for line in split(strip(data, _array_delimiters), '|') - cols = [] - for item in split(line) - push!(cols, parse(dtype, item)) - end - push!(rows, cols) - end - - nphases = maximum([length(row) for row in rows]) - - if dtype == AbstractString || dtype == String - matrix = fill("", nphases, nphases) - elseif dtype == Char - matrix = fill(' ', nphases, nphases) - else - matrix = zeros(dtype, nphases, nphases) - end - - if length(rows) == 1 - for i in 1:nphases - matrix[i, i] = rows[1][1] - end - elseif all([length(row) for row in rows] .== [i for i in 1:nphases]) - for (i, row) in enumerate(rows) - for (j, col) in enumerate(row) - matrix[i, j] = matrix[j, i] = col - end - end - elseif all([length(row) for row in rows] .== nphases) - for (i, row) in enumerate(rows) - for (j, col) in enumerate(row) - matrix[i, j] = col - end - end - end - - return matrix -end - - -"parse matrices according to active nodes" -function _parse_matrix(data::Array{T}, nodes::Array{Bool}, nph::Int=3, fill_val=0.0)::Array where T - mat = fill(fill_val, (nph, nph)) - idxs = findall(nodes[1:nph]) - - for i in 1:size(idxs)[1] - mat[idxs[i], idxs[i]] = data[i, i] - for j in 1:i-1 - mat[idxs[i],idxs[j]] = mat[idxs[j],idxs[i]] = data[i, j] - end - end - - return mat -end - - -"checks if `data` is an opendss-style matrix string" -function _isa_matrix(data::AbstractString)::Bool - if occursin("|", data) - return true - else - return false - end -end - - -"Reorders a `matrix` based on the order that phases are listed in on the from- (`pof`) and to-sides (`pot`)" -function _reorder_matrix(matrix, phase_order) - mat = zeros(size(matrix)) - for (i, n) in zip(sort(phase_order), phase_order) - for (j, m) in zip(sort(phase_order), phase_order) - mat[i, j] = matrix[n, m] - end - end - return mat -end - - -""" - _parse_array(dtype, data) - -Parses a OpenDSS style array string `data` into a one dimensional array of type -`dtype`. Array strings are capped by either brackets, single quotes, or double -quotes, and elements are separated by spaces. -""" -function _parse_array(dtype::Type, data::AbstractString) - if occursin(",", data) - split_char = ',' - else - split_char = ' ' - end - - if _isa_rpn(data) - matches = collect((m.match for m = eachmatch(Regex(string("[",join(_array_delimiters, '\\'),"]")), data, overlap=false))) - if length(matches) == 2 - if dtype == String - return data - else - return _parse_rpn(data, dtype) - end - - else - elements = _parse_properties(data[2:end-1]) - end - else - elements = split(strip(data, _array_delimiters), split_char) - elements = [strip(el) for el in elements if strip(el) != ""] - end - - if dtype == String || dtype == AbstractString || dtype == Char - array = [] - for el in elements - push!(array, el) - end - else - array = zeros(dtype, length(elements)) - for (i, el) in enumerate(elements) - if _isa_rpn(data) - array[i] = _parse_rpn(el, dtype) - else - array[i] = parse(dtype, el) - end - end - end - - return array -end - - -"parse matrices according to active nodes" -function _parse_array(data, nodes::Array{Bool}, nph::Int=3, fill_val=0.0)::Array - mat = fill(fill_val, nph) - idxs = findall(nodes[1:nph]) - - if length(data) == 1 && nph > 1 - for i in idxs - mat[i] = data[1] - end - else - for i in 1:length(idxs) - mat[idxs[i]] = data[i] - end - end - - return mat -end - - -"checks if `data` is an opendss-style array string" -function _isa_array(data::AbstractString)::Bool - clean_data = strip(data) - if !occursin("|", clean_data) - if occursin(",", clean_data) - return true - elseif startswith(clean_data, "[") && endswith(clean_data, "]") - return true - elseif startswith(clean_data, "\"") && endswith(clean_data, "\"") - return true - elseif startswith(clean_data, "\'") && endswith(clean_data, "\'") - return true - elseif startswith(clean_data, "(") && endswith(clean_data, ")") - return true - elseif startswith(clean_data, "{") && endswith(clean_data, "}") - return true - else - return false - end - else - return false - end -end +const _linecode_properties = Vector{String}([ + "nphases", "r1", "x1", "r0","x0", "c1", "c0", "units", "rmatrix", + "xmatrix", "cmatrix", "basefreq", "normamps", "emergamps", "faultrate", + "pctperm", "repair", "kron", "rg", "xg", "rho", "neutral", "b1", "b0", + "like" +]) + +const _linegeometry_properties = Vector{String}([ + "nconds", "nphases", "cond", "wire", "x", "h", "units", "normamps", + "emergamps", "reduce", "spacing", "wires", "cncable", "tscable", + "cncables", "tscables", "like" +]) + +const _linespacing_properties = Vector{String}([ + "nconds", "nphases", "x", "h", "units" +]) + +const _loadshape_properties = Vector{String}([ + "npts", "interval", "minterval", "sinterval", "pmult", "qmult", "hour", + "mean", "stddev", "csvfile", "sngfile", "pqcsvfile", "action", "useactual", + "pmax", "qmax", "pbase", "like" +]) + +const _growthshape_properties = Vector{String}([ + "npts", "year", "mult", "csvfile", "sngfile", "dblfile", "like" +]) + +const _tcc_curve_properties = Vector{String}([ + "npts", "c_array", "t_array", "like" +]) + +const _cndata_properties = Vector{String}([]) + +const _tsdata_properties = Vector{String}([]) + +const _wiredata_properties = Vector{String}([ + "rdc", "rac", "runits", "gmrac", "gmrunits", "radius", "radunits", + "normamps", "emergamps", "diam", "like" +]) + +const _xfmrcode_properties = Vector{String}([]) + +const _vsource_properties = Vector{String}([ + "bus1", "bus2", "basekv", "pu", "angle", "frequency", "phases", "mvasc3", + "mvasc1", "x1r1", "x0r0", "isc3", "isc1", "r1", "x1", "r0", "x0", + "scantype", "sequence", "spectrum", "z1", "z2", "z0", "puz1", "puz2", + "puz0", "basemva", "basefreq", "like", "enabled" +]) + +const _isource_properties = Vector{String}([ + "phases", "bus1", "amps", "angle", "frequency", "scantype", "sequence", + "spectrum", "basefreq", "enabled", "like" +]) + +const _fault_properties = Vector{String}([ + "phases", "bus1", "bus2", "r", "gmatrix", "minamps", "ontime", "pctperm", + "temporary", "%stddev", "normamps", "emergamps", "basefreq", "faultrate", + "repair", "enabled", "like" +]) + +const _capacitor_properties = Vector{String}([ + "bus1", "bus2", "phases", "kvar", "kv", "conn", "cmatrix", "cuf", "r", + "xl", "harm", "numsteps", "states", "normamps", "emergamps", "faultrate", + "pctperm", "basefreq", "enabled", "like" +]) + +const _line_properties = Vector{String}([ + "bus1", "bus2", "linecode", "length", "phases", "r1", "x1", "r0", "x0", + "c1", "c0", "b1", "b0", "normamps", "emergamps", "faultrate", "pctperm", + "repair", "basefreq", "rmatrix", "xmatrix", "cmatrix", "switch", "rg", + "xg", "rho", "geometry", "earthmodel", "units", "enabled", "like" +]) + +const _reactor_properties = Vector{String}([ + "phases", "bus1", "bus2", "kv", "kvar", "conn", "parallel", "r", "rmatrix", + "rp", "x", "xmatrix", "z", "z1", "z2", "z0", "rcurve", "lcurve", "lmh", + "normamps", "emergamps", "repair", "faultrate", "pctperm", "basefreq", + "enabled", "like" +]) + +const _transformer_properties = Vector{String}([ + "phases", "windings", "wdg", "bus", "conn", "kv", "kva", "tap", "%r", + "rneut", "xneut", "buses", "conns", "kvs", "kvas", "taps", "%rs", "xhl", + "xlt", "xht", "xscarray", "thermal", "n", "m", "flrise", "hsrise", + "%loadloss", "%noloadloss", "%imag", "ppm_antifloat", "normhkva", + "emerghkva", "sub", "maxtap", "mintap", "numtaps", "subname", "bank", + "xfmrcode", "xrconst", "leadlag", "faultrate", "basefreq", "enabled", + "like" +]) + +const _gictransformer_properties = Vector{String}([ + "basefreq", "bush", "busnh", "busnx", "busx", "emergamps", "enabled", + "phases", "r1", "r2", "type", "mva", "kvll1", "kvll2", "%r1", "%r2", "k", + "varcurve", "like", "normamps", "emergamps", "pctperm", "repair" +]) + +const _gicline_properties = Vector{String}([ + "angle", "bus1", "bus2", "c", "ee", "en", "frequency", "lat1", "lat2", + "lon1", "lon2", "phases", "r", "volts", "x", "like", "basefreq", + "enabled", "spectrum" +]) + +const _load_properties = Vector{String}([ + "phases", "bus1", "kv", "kw", "pf", "model", "yearly", "daily", "duty", + "growth", "conn", "kvar", "rneut", "xneut", "status", "class", "vminpu", + "vmaxpu", "vminnorm", "vminemerg", "xfkva", "allocationfactor", "kva", + "%mean", "%stddev", "cvrwatts", "cvrvars", "kwh", "kwhdays", "cfactor", + "cvrcurve", "numcust", "spectrum", "zipv", "%seriesrl", "relweight", + "vlowpu", "puxharm", "xrharm", "spectrum", "basefreq", "enabled", "like" +]) + +const _generator_properties = Vector{String}([ + "bus1", "phases", "kv", "kw", "pf", "model", "yearly", "daily", "duty", + "dispvalue", "conn", "kvar", "rneut", "xneut", "status", "class", "vpu", + "maxkvar", "minkvar", "pvfactor", "debugtrace", "vminpu", "vmaxpu", + "forceon", "kva", "mva", "xd", "xdp", "xdpp", "h", "d", "usermodel", + "userdata", "shaftmodel", "shaftdata", "dutystart", "balanced", "xrdp", + "spectrum", "basefreq", "enabled", "like" +]) + +const _indmach012_properties = Vector{String}([ + "phases", "bus1", "kv", "kw", "pf", "conn", "kva", "h", "d", "purs", + "puxs", "purr", "puxr", "puxm", "slip", "maxslip", "slipoption", + "spectrum", "enabled" +]) + +const _storage_properties = Vector{String}([ + "phases", "bus1", "%charge", "%discharge", "%effcharge", "%idlingkvar", + "idlingkw", "%r", "%reserve", "%stored", "%x", "basefreq", "chargetrigger", + "class", "conn", "daily", "yearly", "debugtrace", "dischargetrigger", + "dispmode", "duty", "dynadata", "dynadll", "enabled", "kv", "kva", "kvar", + "kw", "kwhrated", "kwhstored", "kwrated", "like", "model", "pf", + "spectrum", "state", "timechargetrig", "userdata", "usermodel", "vmaxpu", + "vminpu", "yearly" +]) + +const _capcontrol_properties = Vector{String}([ + "element", "capacitor", "type", "ctphase", "ctratio", "deadtime", "delay", + "delayoff", "eventlog", "offsetting", "onsetting", "ptphase", "ptratio", + "terminal", "vbus", "vmax", "vmin", "voltoverride", "enabled" +]) + +const _regcontrol_properties = Vector{String}([ + "transformer", "winding", "vreg", "band", "delay", "ptratio", "ctprim", + "r", "x", "pthase", "tapwinding", "bus", "remoteptratio", "debugtrace", + "eventlog", "inversetime", "maxtapchange", "revband", "revdelay", + "reversible", "revneutral", "revr", "revthreshold", "revvreg", "revx", + "tapdelay", "tapnum", "vlimit", "ldc_z", "rev_z", "cogen", "enabled" +]) + +const _energymeter_properties = Vector{String}([ + "element", "terminal", "action", "clear", "save", "take", "option", + "kwnorm", "kwemerg", "peakcurrent", "zonelist", "zonelist", "zonelist", + "localonly", "mask", "losses", "linelosses", "xfmrlosses", "seqlosses", + "3phaselosses", "vbaselosses", "basefreq", "enabled", "like" +]) + +const _pvsystem_properties = Vector{String}([ + "phases", "bus1", "kv", "irradiance", "pmpp", "temperature", "pf", + "conn", "kvar", "kva", "%cutin", "%cutout", "effcurve", "p-tcurve", "%r", + "%x", "model", "vminpu", "vmaxpu", "yearly", "daily", "duty", "tyearly", + "tduty", "class", "usermodel", "userdata", "debugtrace", "spectrum" +]) + +const _relay_properties = Vector{String}([]) + +const _recloser_properties = Vector{String}([]) + +const _fuse_properties = Vector{String}([]) + +const _dss_object_properties = Dict{String,Vector{String}}( + "linecode" => _linecode_properties, + "linegeometry" => _linegeometry_properties, + "linespacing" => _linespacing_properties, + "loadshape" => _loadshape_properties, + "growthshape" => _growthshape_properties, + "tcc_curve" => _tcc_curve_properties, + "cndata" => _cndata_properties, + "tsdata" => _tsdata_properties, + "wiredata" => _wiredata_properties, + "xfmrcode" => _xfmrcode_properties, + "vsource" => _vsource_properties, + "circuit" => _vsource_properties, # alias circuit to vsource + "isource" => _isource_properties, + "fault" => _fault_properties, + "capacitor" => _capacitor_properties, + "line" => _line_properties, + "reactor" => _reactor_properties, + "transformer" => _transformer_properties, + "gictransformer" => _gictransformer_properties, + "gicline" => _gicline_properties, + "load" => _load_properties, + "generator" => _generator_properties, + "indmach012" => _indmach012_properties, + "storage" => _storage_properties, + "capcontrol" => _capacitor_properties, + "regcontrol" => _regcontrol_properties, + "energymeter" => _energymeter_properties, + "pvsystem" => _pvsystem_properties, + "relay" => _relay_properties, + "recloser" => _reactor_properties, + "fuse" => _fuse_properties +) "parses single column load profile files" -function _parse_loadshape_csv(path::AbstractString, type::AbstractString; header::Bool=false, column::Int=1, interval::Bool=false) +function _parse_loadshape_csv_file(path::AbstractString, type::AbstractString; header::Bool=false, column::Int=1, interval::Bool=false) open(path, "r") do f lines = readlines(f) if header @@ -493,7 +252,7 @@ end "parses sng and dbl precision loadshape binary files" -function _parse_binary(path::AbstractString, precision::Type; npts::Union{Int,Nothing}=nothing, interval::Bool=false) +function _parse_binary_file(path::AbstractString, precision::Type; npts::Union{Int,Nothing}=nothing, interval::Bool=false) open(path, "r") do f if npts === nothing data = precision[] @@ -530,15 +289,15 @@ end "parses csv or binary loadshape files" function _parse_loadshape_file(path::AbstractString, type::AbstractString, npts::Union{Int,Nothing}; header::Bool=false, interval::Bool=false, column::Int=1) if type in ["csvfile", "mult", "pqcsvfile"] - return _parse_loadshape_csv(path, type; header=header, column=column, interval=interval) + return _parse_loadshape_csv_file(path, type; header=header, column=column, interval=interval) elseif type in ["sngfile", "dblfile"] - return _parse_binary(path, Dict("sngfile" => Float32, "dblfile" => Float64)[type]; npts=npts, interval=interval) + return _parse_binary_file(path, Dict("sngfile" => Float32, "dblfile" => Float64)[type]; npts=npts, interval=interval) end end "parses pmult and qmult entries on loadshapes" -function _parse_mult(mult_string::AbstractString; path::AbstractString="", npts::Union{Int,Nothing}=nothing) +function _parse_mult_parameter(mult_string::AbstractString; path::AbstractString="", npts::Union{Int,Nothing}=nothing) if !occursin("=", mult_string) return mult_string else @@ -573,7 +332,7 @@ function _parse_loadshape!(current_obj::Dict{String,Any}; path::AbstractString=" for prop in current_obj["prop_order"] if prop in ["pmult", "qmult"] - current_obj[prop] = _parse_mult(current_obj[prop]; path=path, npts=npts) + current_obj[prop] = _parse_mult_parameter(current_obj[prop]; path=path, npts=npts) elseif prop in ["csvfile", "pqcsvfile", "sngfile", "dblfile"] full_path = path == "" ? current_obj[prop] : join([path, current_obj[prop]], '/') data = _parse_loadshape_file(full_path, prop, parse(Int, get(current_obj, "npts", "1")); interval=interval, header=false) @@ -619,13 +378,11 @@ end """ - _parse_buscoords(file) - Parses a Bus Coordinate `file`, in either "dat" or "csv" formats, where in "dat", columns are separated by spaces, and in "csv" by commas. File expected to contain "bus,x,y" on each line. """ -function _parse_buscoords(file::AbstractString)::Dict{String,Any} +function _parse_buscoords_file(file::AbstractString)::Dict{String,Any} file_str = read(open(file), String) regex = r",\s*" if endswith(lowercase(file), "csv") || endswith(lowercase(file), "dss") @@ -645,8 +402,6 @@ end """ - _parse_properties(properties) - Parses a string of `properties` of a component type, character by character into an array with each element containing (if present) the property name, "=", and the property value. @@ -708,8 +463,6 @@ end """ - _add_component!(data_dss, obj_type_name, object) - Adds a component of type `obj_type_name` with properties given by `object` to the existing `data_dss` structure. If a component of the same type has already been added to `data_dss`, the new component is appeneded to the existing array @@ -732,8 +485,6 @@ end """ - _add_property(object, key, value) - Adds a property to an existing component properties dictionary `object` given the `key` and `value` of the property. If a property of the same name already exists inside `object`, the original value is converted to an array, and the @@ -769,14 +520,11 @@ end """ - _parse_component(component, properies, object=Dict{String,Any}()) - Parses a `component` with `properties` into a `object`. If `object` is not defined, an empty dictionary will be used. Assumes that unnamed properties are given in order, but named properties can be given anywhere. """ function _parse_component(component::AbstractString, properties::AbstractString, object::Dict{String,<:Any}=Dict{String,Any}(); path::AbstractString="") - Memento.debug(_LOGGER, "Properties: $properties") obj_type, name = split(component, '.'; limit=2) if !haskey(object, "name") @@ -784,9 +532,8 @@ function _parse_component(component::AbstractString, properties::AbstractString, end property_array = _parse_properties(properties) - Memento.debug(_LOGGER, "property_array: $property_array") - property_names = _get_prop_name(obj_type) + property_names = _dss_object_properties[obj_type] property_idx = 1 for (n, property) in enumerate(property_array) @@ -820,7 +567,7 @@ function _parse_component(component::AbstractString, properties::AbstractString, key, value = split(property, '='; limit=2) if occursin(r"\(\s*(sng|dbl)*file=(.+)\)", value) - value = _parse_mult(value; path=path) + value = _parse_mult_parameter(value; path=path) end _add_property(object, key, value) @@ -831,8 +578,6 @@ end """ - _merge_dss!(dss_prime, dss_to_add) - Merges two (partially) parsed OpenDSS files to the same dictionary `dss_prime`. Used in cases where files are referenced via the "compile" or "redirect" OpenDSS commands inside the originating file. @@ -849,8 +594,6 @@ end """ - _parse_line(elements, current_obj=Dict{String,Any}()) - Parses an already separated line given by `elements` (an array) of an OpenDSS file into `current_obj`. If not defined, `current_obj` is an empty dictionary. """ @@ -880,8 +623,6 @@ end """ - _assign_property!(data_dss, obj_type, obj_name, property_name, property_value) - Assigns a property with name `property_name` and value `property_value` to the component of type `obj_type` named `obj_name` in `data_dss`. """ @@ -898,9 +639,16 @@ function _assign_property!(data_dss::Dict, obj_type::AbstractString, obj_name::A end -""" - parse_dss(filename) +"" +function parse_dss(filename::AbstractString)::Dict + data_dss = open(filename) do io + parse_dss(io) + end + return data_dss +end + +""" Parses a OpenDSS file given by `filename` into a Dict{Array{Dict}}. Only supports components and options, but not commands, e.g. "plot" or "solve". Will also parse files defined inside of the originating DSS file via the @@ -925,7 +673,6 @@ function parse_dss(io::IOStream)::Dict{String,Any} for (n, line) in enumerate(stripped_lines) real_line_num = findall(lines .== line)[1] - Memento.debug(_LOGGER, "LINE $real_line_num: $line") line = _strip_comments(line) if startswith(strip(line), '~') @@ -961,7 +708,6 @@ function parse_dss(io::IOStream)::Dict{String,Any} continue elseif cmd == "set" - Memento.debug(_LOGGER, "set command: $line_elements") properties = _parse_properties(join(line_elements[2:end], " ")) if length(line_elements) == 2 property, value = split(lowercase(line_elements[2]), '='; limit=2) @@ -979,8 +725,7 @@ function parse_dss(io::IOStream)::Dict{String,Any} elseif cmd == "buscoords" file = line_elements[2] full_path = path == "" ? file : join([path, file], '/') - Memento.debug(_LOGGER, "Buscoords path: $full_path") - data_dss["buscoords"] = _parse_buscoords(full_path) + data_dss["buscoords"] = _parse_buscoords_file(full_path) elseif cmd == "new" current_obj_type, current_obj = _parse_line([lowercase(line_element) for line_element in line_elements]; path=path) @@ -1012,7 +757,6 @@ function parse_dss(io::IOStream)::Dict{String,Any} end end - Memento.debug(_LOGGER, "size current_obj: $(length(current_obj))") if n < nlines && startswith(strip(stripped_lines[n + 1]), '~') continue elseif length(current_obj) > 0 @@ -1024,228 +768,10 @@ function parse_dss(io::IOStream)::Dict{String,Any} end Memento.info(_LOGGER, "Done parsing $filename") - return data_dss -end - - -"" -function parse_dss(filename::AbstractString)::Dict - data_dss = open(filename) do io - parse_dss(io) - end - return data_dss -end - - -"parses the raw dss values into their expected data types" -function _parse_element_with_dtype(dtype, element) - if _isa_matrix(element) - out = _parse_matrix(eltype(dtype), element) - elseif _isa_array(element) - out = _parse_array(eltype(dtype), element) - elseif dtype <: Bool - if element in ["n", "no"] - element = "false" - elseif element in ["y", "yes"] - element = "true" - end - out = parse(dtype, element) - elseif _isa_rpn(element) - out = _parse_rpn(element) - elseif dtype == String - out = element - else - if _isa_conn(element) - out = _parse_conn(element) - else - try - out = parse(dtype, element) - catch - Memento.warn(_LOGGER, "cannot parse $element as $dtype, leaving as String.") - out = element - end - end - end - - return out -end - - -""" - parse_dss_with_dtypes!(data_dss, to_parse) - -Parses the data in keys defined by `to_parse` in `data_dss` using types given by -the default properties from the `get_prop_default` function. -""" -function parse_dss_with_dtypes!(data_dss::Dict{String,<:Any}, to_parse::Array{String}=[]) - for obj_type in to_parse - if haskey(data_dss, obj_type) - Memento.debug(_LOGGER, "type: $obj_type") - dtypes = _get_dtypes(obj_type) - if obj_type == "circuit" - _parse_obj_dtypes!(obj_type, data_dss[obj_type], dtypes) - else - for object in values(data_dss[obj_type]) - _parse_obj_dtypes!(obj_type, object, dtypes) - end - end - end - end -end + parse_dss_with_dtypes!(data_dss) -"" -function _parse_obj_dtypes!(obj_type, object, dtypes) - for (k, v) in object - if haskey(dtypes, k) - Memento.debug(_LOGGER, "key: $k") - if isa(v, Array) - arrout = [] - for el in v - if isa(v, AbstractString) - push!(arrout, _parse_element_with_dtype(dtypes[k], el)) - else - push!(arrout, el) - end - end - object[k] = arrout - elseif isa(v, AbstractString) - object[k] = _parse_element_with_dtype(dtypes[k], v) - else - Memento.error(_LOGGER, "dtype unknown $obj_type, $k, $v") - end - end - end -end - + parse_dss_options!(data_dss) -""" - _parse_busname(busname) - -Parses busnames as defined in OpenDSS, e.g. "primary.1.2.3.0". -""" -function _parse_busname(busname::AbstractString) - parts = split(busname, '.'; limit=2) - name = parts[1] - elements = "1.2.3" - - if length(parts) >= 2 - name, elements = split(busname, '.'; limit=2) - end - - nodes = Array{Bool}([0 0 0 0]) - - for num in 1:3 - if occursin("$num", elements) - nodes[num] = true - end - end - - if occursin("0", elements) || sum(nodes[1:3]) == 1 - nodes[4] = true - end - - return name, nodes -end - - -""" - _get_conductors_ordered(busname; neutral=true) - -Returns an ordered list of defined conductors. If ground=false, will omit any `0` -""" -function _get_conductors_ordered(busname::AbstractString; neutral::Bool=true, nconductors::Int=3)::Array - parts = split(busname, '.'; limit=2) - ret = [] - if length(parts)==2 - conductors_string = split(parts[2], '.') - if neutral - ret = [parse(Int, i) for i in conductors_string] - else - ret = [parse(Int, i) for i in conductors_string if i != "0"] - end - else - ret = collect(1:nconductors) - end - - return ret -end - - -"converts Dict{String,Any} to Dict{Symbol,Any} for passing as kwargs" -function _to_sym_keys(data::Dict{String,Any})::Dict{Symbol,Any} - return Dict{Symbol,Any}((Symbol(k), v) for (k, v) in data) -end - - -"" -function _apply_ordered_properties(defaults::Dict{String,<:Any}, raw_dss::Dict{String,<:Any}; code_dict::Dict{String,<:Any}=Dict{String,Any}()) - _defaults = deepcopy(defaults) - - for prop in filter(p->p!="like", raw_dss["prop_order"]) - if prop in ["linecode", "loadshape"] - merge!(defaults, code_dict) - else - defaults[prop] = _defaults[prop] - end - end - - return defaults -end - - -"applies `like` to component" -function _apply_like!(raw_dss, data_dss, comp_type) - links = ["like"] - if any(link in raw_dss["prop_order"] for link in links) - new_prop_order = [] - raw_dss_copy = deepcopy(raw_dss) - - for prop in raw_dss["prop_order"] - push!(new_prop_order, prop) - - if prop in get(_like_exclusions, comp_type, []) || prop in _like_exclusions["all"] - continue - end - - if prop in links - linked_dss = get(get(data_dss, comp_type, Dict{String,Any}()), raw_dss[prop], Dict{String,Any}()) - if isempty(linked_dss) - Memento.warn(_LOGGER, "$comp_type.$(raw_dss["name"]): $prop=$(raw_dss[prop]) cannot be found") - else - for linked_prop in linked_dss["prop_order"] - if linked_prop in get(_like_exclusions, comp_type, []) || linked_prop in _like_exclusions["all"] - continue - end - - push!(new_prop_order, linked_prop) - if linked_prop in links - _apply_like!(linked_dss, data_dss, comp_type) - else - raw_dss[linked_prop] = deepcopy(linked_dss[linked_prop]) - end - end - end - else - raw_dss[prop] = deepcopy(raw_dss_copy[prop]) - end - end - - final_prop_order = [] - while !isempty(new_prop_order) - prop = popfirst!(new_prop_order) - if !(prop in new_prop_order) - push!(final_prop_order, prop) - end - end - raw_dss["prop_order"] = final_prop_order - end + return data_dss end - - -"properties that should be excluded from being overwritten during the application of `like`" -const _like_exclusions = Dict{String,Array}("all" => ["name", "bus1", "bus2", "phases", "nphases", "enabled"], - "line" => ["switch"], - "transformer" => ["bank", "bus", "bus_2", "bus_3", "buses", "windings", "wdg", "wdg_2", "wdg_3"], - "linegeometry" => ["nconds"] - ) diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index f93b48a2f..13fb1b7bb 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -3,26 +3,25 @@ import LinearAlgebra: diagm import Statistics: mean, std -_convert_to_meters = Dict{String,Any}("mi" => 1609.3, - "km" => 1000.0, - "kft" => 304.8, - "m" => 1.0, - "ft" => 0.3048, - "in" => 0.0254, - "cm" => 0.01, - "mm" => 0.001, - "none" => 1.0 - ) +const _convert_to_meters = Dict{String,Float64}( + "mi" => 1609.3, + "km" => 1000.0, + "kft" => 304.8, + "m" => 1.0, + "ft" => 0.3048, + "in" => 0.0254, + "cm" => 0.01, + "mm" => 0.001, + "none" => 1.0 +) """ Creates a Dict{String,Any} containing all of the properties of a Linecode. See OpenDSS documentation for valid fields and ways to specify the different -properties. DEPRECIATED: Calculation all done inside of _create_line() due to Rg, -Xg. Merge linecode values into line kwargs values BEFORE calling _create_line(). -This is now mainly used for parsing linecode dicts into correct data types. +properties. """ -function _create_linecode(name::AbstractString=""; kwargs...) +function _create_linecode(name::AbstractString=""; kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) phases = get(kwargs, :nphases, 3) circuit_basefreq = get(kwargs, :circuit_basefreq, 60.0) @@ -77,8 +76,6 @@ function _create_linecode(name::AbstractString=""; kwargs...) xmatrix = get(kwargs, :xmatrix, imag(Z)) cmatrix = get(kwargs, :cmatrix, imag(Yc) / (2 * pi * basefreq)) - # TODO: Rg, Xg cannot be handled at the LineCode level! - units = get(kwargs, :units, "none") return Dict{String,Any}("name" => name, @@ -115,7 +112,7 @@ Creates a Dict{String,Any} containing all of the properties for a Line. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_line(bus1="", bus2="", name::AbstractString=""; kwargs...) +function _create_line(bus1="", bus2="", name::AbstractString=""; kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) phases = get(kwargs, :phases, 3) circuit_basefreq = get(kwargs, :circuit_basefreq, 60.0) @@ -194,43 +191,44 @@ function _create_line(bus1="", bus2="", name::AbstractString=""; kwargs...) xmatrix .-= xgmod xmatrix .*= lenmult * (basefreq / circuit_basefreq) - return Dict{String,Any}("name" => name, - "bus1" => bus1, - "bus2" => bus2, - "linecode" => get(kwargs, :linecode, ""), - "length" => len, - "phases" => phases, - "r1" => r1 / _convert_to_meters[units], - "x1" => x1 / _convert_to_meters[units], - "r0" => r0 / _convert_to_meters[units], - "x0" => x0 / _convert_to_meters[units], - "c1" => c1 / _convert_to_meters[units], - "c0" => c0 / _convert_to_meters[units], - "rmatrix" => rmatrix / _convert_to_meters[units], - "xmatrix" => xmatrix / _convert_to_meters[units], - "cmatrix" => cmatrix / _convert_to_meters[units], - "switch" => get(kwargs, :switch, false), - "rg" => rg / _convert_to_meters[units], - "xg" => xg / _convert_to_meters[units], - "rho" => get(kwargs, :rho, 100), - "geometry" => get(kwargs, :geometry, ""), - "units" => "m", - "spacing" => get(kwargs, :spacing, ""), - "wires" => get(kwargs, :wires, ""), - "earthmodel" => get(kwargs, :earthmodel, ""), - "cncables" => get(kwargs, :cncables, ""), - "tscables" => get(kwargs, :tscables, ""), - "b1" => b1 / _convert_to_meters[units], - "b0" => b0 / _convert_to_meters[units], - # Inherited Properties - "normamps" => get(kwargs, :normamps, 400.0), - "emergamps" => get(kwargs, :emergamps, 600.0), - "faultrate" => get(kwargs, :faultrate, 0.1), - "pctperm" => get(kwargs, :pctperm, 20.0), - "repair" => get(kwargs, :repair, 3.0), - "basefreq" => basefreq, - "enabled" => get(kwargs, :enabled, true), - ) + Dict{String,Any}( + "name" => name, + "bus1" => bus1, + "bus2" => bus2, + "linecode" => get(kwargs, :linecode, ""), + "length" => len, + "phases" => phases, + "r1" => r1 / _convert_to_meters[units], + "x1" => x1 / _convert_to_meters[units], + "r0" => r0 / _convert_to_meters[units], + "x0" => x0 / _convert_to_meters[units], + "c1" => c1 / _convert_to_meters[units], + "c0" => c0 / _convert_to_meters[units], + "rmatrix" => rmatrix / _convert_to_meters[units], + "xmatrix" => xmatrix / _convert_to_meters[units], + "cmatrix" => cmatrix / _convert_to_meters[units], + "switch" => get(kwargs, :switch, false), + "rg" => rg / _convert_to_meters[units], + "xg" => xg / _convert_to_meters[units], + "rho" => get(kwargs, :rho, 100), + "geometry" => get(kwargs, :geometry, ""), + "units" => "m", + "spacing" => get(kwargs, :spacing, ""), + "wires" => get(kwargs, :wires, ""), + "earthmodel" => get(kwargs, :earthmodel, ""), + "cncables" => get(kwargs, :cncables, ""), + "tscables" => get(kwargs, :tscables, ""), + "b1" => b1 / _convert_to_meters[units], + "b0" => b0 / _convert_to_meters[units], + # Inherited Properties + "normamps" => get(kwargs, :normamps, 400.0), + "emergamps" => get(kwargs, :emergamps, 600.0), + "faultrate" => get(kwargs, :faultrate, 0.1), + "pctperm" => get(kwargs, :pctperm, 20.0), + "repair" => get(kwargs, :repair, 3.0), + "basefreq" => basefreq, + "enabled" => get(kwargs, :enabled, true), + ) end @@ -239,7 +237,7 @@ Creates a Dict{String,Any} containing all of the expected properties for a Load. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_load(bus1="", name::AbstractString=""; kwargs...) +function _create_load(bus1="", name::AbstractString=""; kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) kv = get(kwargs, :kv, 12.47) kw = get(kwargs, :kw, 10.0) @@ -269,52 +267,51 @@ function _create_load(bus1="", name::AbstractString=""; kwargs...) # TODO: yearly, daily, duty, growth, model # TODO: ZIPV (7 coefficient array, depends on model keyword) - load = Dict{String,Any}("name" => name, - "phases" => get(kwargs, :phases, 3), - "bus1" => bus1, - "kv" => kv, - "kw" => kw, - "pf" => pf, - "model" => get(kwargs, :model, 1), - "yearly" => get(kwargs, :yearly, get(kwargs, :daily, [1.0, 1.0])), - "daily" => get(kwargs, :daily, [1.0, 1.0]), - "duty" => get(kwargs, :duty, ""), - "growth" => get(kwargs, :growth, ""), - "conn" => get(kwargs, :conn, "wye"), - "kvar" => kvar, - "rneut" => get(kwargs, :rneut, -1.0), - "xneut" => get(kwargs, :xneut, 0.0), - "status" => get(kwargs, :status, "variable"), - "class" => get(kwargs, :class, 1), - "vminpu" => get(kwargs, :vminpu, 0.95), - "vmaxpu" => get(kwargs, :vmaxpu, 1.05), - "vminnorm" => get(kwargs, :vminnorm, 0.0), - "vminemerg" => get(kwargs, :vminemerg, 0.0), - "xfkva" => get(kwargs, :xfkva, 0.0), - "allocationfactor" => get(kwargs, :allocationfactor, 0.5), - "kva" => kva, - "%mean" => get(kwargs, Symbol("%mean"), 0.5), - "%stddev" => get(kwargs, Symbol("%stddev"), 0.1), - "cvrwatts" => get(kwargs, :cvrwatts, 1.0), - "cvrvars" => get(kwargs, :cvrvars, 2.0), - "kwh" => get(kwargs, :kwh, 0.0), - "kwhdays" => get(kwargs, :kwhdays, 30.0), - "cfactor" => get(kwargs, :cfactor, 4.0), - "cvrcurve" => get(kwargs, :cvrcurve, ""), - "numcust" => get(kwargs, :numcust, 1), - "zipv" => get(kwargs, :zipv, ""), - "%seriesrl" => get(kwargs, "%seriesrl", 0.5), - "relweight" => get(kwargs, :relweight, 1.0), - "vlowpu" => get(kwargs, :vlowpu, 0.5), - "puxharm" => get(kwargs, :puxharm, 0.0), - "xrharm" => get(kwargs, :xrharm, 6.0), - # Inherited Properties - "spectrum" => get(kwargs, :spectrum, "defaultload"), - "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) - ) - - return load + Dict{String,Any}( + "name" => name, + "phases" => get(kwargs, :phases, 3), + "bus1" => bus1, + "kv" => kv, + "kw" => kw, + "pf" => pf, + "model" => get(kwargs, :model, 1), + "yearly" => get(kwargs, :yearly, get(kwargs, :daily, Vector{Float64}([1.0, 1.0]))), + "daily" => get(kwargs, :daily, Vector{Float64}([1.0, 1.0])), + "duty" => get(kwargs, :duty, ""), + "growth" => get(kwargs, :growth, ""), + "conn" => get(kwargs, :conn, "wye"), + "kvar" => kvar, + "rneut" => get(kwargs, :rneut, -1.0), + "xneut" => get(kwargs, :xneut, 0.0), + "status" => get(kwargs, :status, "variable"), + "class" => get(kwargs, :class, 1), + "vminpu" => get(kwargs, :vminpu, 0.95), + "vmaxpu" => get(kwargs, :vmaxpu, 1.05), + "vminnorm" => get(kwargs, :vminnorm, 0.0), + "vminemerg" => get(kwargs, :vminemerg, 0.0), + "xfkva" => get(kwargs, :xfkva, 0.0), + "allocationfactor" => get(kwargs, :allocationfactor, 0.5), + "kva" => kva, + "%mean" => get(kwargs, Symbol("%mean"), 0.5), + "%stddev" => get(kwargs, Symbol("%stddev"), 0.1), + "cvrwatts" => get(kwargs, :cvrwatts, 1.0), + "cvrvars" => get(kwargs, :cvrvars, 2.0), + "kwh" => get(kwargs, :kwh, 0.0), + "kwhdays" => get(kwargs, :kwhdays, 30.0), + "cfactor" => get(kwargs, :cfactor, 4.0), + "cvrcurve" => get(kwargs, :cvrcurve, ""), + "numcust" => get(kwargs, :numcust, 1), + "zipv" => get(kwargs, :zipv, ""), + "%seriesrl" => get(kwargs, "%seriesrl", 0.5), + "relweight" => get(kwargs, :relweight, 1.0), + "vlowpu" => get(kwargs, :vlowpu, 0.5), + "puxharm" => get(kwargs, :puxharm, 0.0), + "xrharm" => get(kwargs, :xrharm, 6.0), + # Inherited Properties + "spectrum" => get(kwargs, :spectrum, "defaultload"), + "basefreq" => get(kwargs, :basefreq, 60.0), + "enabled" => get(kwargs, :enabled, true) + ) end @@ -323,7 +320,7 @@ Creates a Dict{String,Any} containing all of the expected properties for a Generator. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_generator(bus1="", name::AbstractString=""; kwargs...) +function _create_generator(bus1="", name::AbstractString=""; kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) conn = get(kwargs, :conn, "wye") @@ -333,51 +330,52 @@ function _create_generator(bus1="", name::AbstractString=""; kwargs...) kvarmax = get(kwargs, :maxkvar, kvar * 2.0) kvarmin = get(kwargs, :minkvar, -kvarmax) - return Dict{String,Any}("name" => name, - "phases" => get(kwargs, :phases, 3), - "bus1" => bus1, - "kv" => get(kwargs, :kv, 12.47), - "kw" => kw, - "pf" => get(kwargs, :pf, 0.80), - "model" => get(kwargs, :model, 1), - "yearly" => get(kwargs, :yearly, get(kwargs, :daily, [0.0, 0.0])), - "daily" => get(kwargs, :daily, [0.0, 0.0]), - "duty" => get(kwargs, "duty", ""), - "dispmode" => get(kwargs, :dispmode, "default"), - "disvalue" => get(kwargs, :dispvalue, 0.0), - "conn" => conn, - "kvar" => kvar, - "rneut" => get(kwargs, :rneut, 0.0), - "xneut" => get(kwargs, :xneut, 0.0), - "status" => get(kwargs, :status, "variable"), - "class" => get(kwargs, :class, 1), - "vpu" => get(kwargs, :vpu, 1.0), - "maxkvar" => kvarmax, - "minkvar" => kvarmin, - "pvfactor" => get(kwargs, :pvfactor, 0.1), - "debugtrace" => get(kwargs, :debugtrace, false), - "vminpu" => get(kwargs, :vminpu, 0.9), - "vmaxpu" => get(kwargs, :vmaxpu, 1.10), - "forceon" => get(kwargs, :forceon, false), - "kva" => kva, - "mva" => kva * 0.001, - "xd" => get(kwargs, :xd, 1.0), - "xdp" => get(kwargs, :xdp, 0.28), - "xdpp" => get(kwargs, :xdpp, 0.20), - "h" => get(kwargs, :h, 1.0), - "d" => get(kwargs, :d, 1.0), - "usermodel" => get(kwargs, :usermodel, ""), - "userdata" => get(kwargs, :userdata, ""), - "shaftmodel" => get(kwargs, :shaftmodel, ""), - "shaftdata" => get(kwargs, :shaftdata, ""), - "dutystart" => get(kwargs, :dutystart, 0.0), - "balanced" => get(kwargs, :balanced, false), - "xrdp" => get(kwargs, :xrdp, 20.0), - # Inherited Properties - "spectrum" => get(kwargs, :spectrum, "defaultgen"), - "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) - ) + Dict{String,Any}( + "name" => name, + "phases" => get(kwargs, :phases, 3), + "bus1" => bus1, + "kv" => get(kwargs, :kv, 12.47), + "kw" => kw, + "pf" => get(kwargs, :pf, 0.80), + "model" => get(kwargs, :model, 1), + "yearly" => get(kwargs, :yearly, get(kwargs, :daily, Vector{Float64}([0.0, 0.0]))), + "daily" => get(kwargs, :daily, Vector{Float64}([0.0, 0.0])), + "duty" => get(kwargs, "duty", ""), + "dispmode" => get(kwargs, :dispmode, "default"), + "disvalue" => get(kwargs, :dispvalue, 0.0), + "conn" => conn, + "kvar" => kvar, + "rneut" => get(kwargs, :rneut, 0.0), + "xneut" => get(kwargs, :xneut, 0.0), + "status" => get(kwargs, :status, "variable"), + "class" => get(kwargs, :class, 1), + "vpu" => get(kwargs, :vpu, 1.0), + "maxkvar" => kvarmax, + "minkvar" => kvarmin, + "pvfactor" => get(kwargs, :pvfactor, 0.1), + "debugtrace" => get(kwargs, :debugtrace, false), + "vminpu" => get(kwargs, :vminpu, 0.9), + "vmaxpu" => get(kwargs, :vmaxpu, 1.10), + "forceon" => get(kwargs, :forceon, false), + "kva" => kva, + "mva" => kva * 0.001, + "xd" => get(kwargs, :xd, 1.0), + "xdp" => get(kwargs, :xdp, 0.28), + "xdpp" => get(kwargs, :xdpp, 0.20), + "h" => get(kwargs, :h, 1.0), + "d" => get(kwargs, :d, 1.0), + "usermodel" => get(kwargs, :usermodel, ""), + "userdata" => get(kwargs, :userdata, ""), + "shaftmodel" => get(kwargs, :shaftmodel, ""), + "shaftdata" => get(kwargs, :shaftdata, ""), + "dutystart" => get(kwargs, :dutystart, 0.0), + "balanced" => get(kwargs, :balanced, false), + "xrdp" => get(kwargs, :xrdp, 20.0), + # Inherited Properties + "spectrum" => get(kwargs, :spectrum, "defaultgen"), + "basefreq" => get(kwargs, :basefreq, 60.0), + "enabled" => get(kwargs, :enabled, true) + ) end @@ -387,46 +385,45 @@ Capacitor. If `bus2` is not specified, the capacitor will be treated as a shunt. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_capacitor(bus1="", name::AbstractString=""; kwargs...) +function _create_capacitor(bus1="", name::AbstractString=""; kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) phases = get(kwargs, :phases, 3) bus2 = get(kwargs, :bus2, string(split(bus1, ".")[1],".",join(fill("0", phases), "."))) - return Dict{String,Any}("name" => name, - "bus1" => bus1, - "bus2" => bus2, - "phases" => phases, - "kvar" => get(kwargs, :kvar, 1200.0), - "kv" => get(kwargs, :kv, 12.47), - "conn" => get(kwargs, :conn, "wye"), - "cmatrix" => get(kwargs, :cmatrix, zeros(phases, phases)), - "cuf" => get(kwargs, :cuf, zeros(phases)), - "r" => get(kwargs, :r, zeros(phases)), - "xl" => get(kwargs, :xl, zeros(phases)), - "harm" => get(kwargs, :harm, zeros(phases)), - "numsteps" => get(kwargs, :numsteps, 1), - "states" => get(kwargs, :states, zeros(Bool, phases)), - # Inherited Properties - "normamps" => get(kwargs, :normamps, 400.0), - "emergamps" => get(kwargs, :emergamps, 600.0), - "faultrate" => get(kwargs, :faultrate, 0.1), - "pctperm" => get(kwargs, :pctperm, 20.0), - "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) - ) + Dict{String,Any}( + "name" => name, + "bus1" => bus1, + "bus2" => bus2, + "phases" => phases, + "kvar" => get(kwargs, :kvar, 1200.0), + "kv" => get(kwargs, :kv, 12.47), + "conn" => get(kwargs, :conn, "wye"), + "cmatrix" => get(kwargs, :cmatrix, zeros(phases, phases)), + "cuf" => get(kwargs, :cuf, zeros(phases)), + "r" => get(kwargs, :r, zeros(phases)), + "xl" => get(kwargs, :xl, zeros(phases)), + "harm" => get(kwargs, :harm, zeros(phases)), + "numsteps" => get(kwargs, :numsteps, 1), + "states" => get(kwargs, :states, zeros(Bool, phases)), + # Inherited Properties + "normamps" => get(kwargs, :normamps, 400.0), + "emergamps" => get(kwargs, :emergamps, 600.0), + "faultrate" => get(kwargs, :faultrate, 0.1), + "pctperm" => get(kwargs, :pctperm, 20.0), + "basefreq" => get(kwargs, :basefreq, 60.0), + "enabled" => get(kwargs, :enabled, true) + ) end """ - _create_reactor(bus1, name, bus2=0; kwargs...) - Creates a Dict{String,Any} containing all of the expected properties for a Reactor. If `bus2` is not specified Reactor is treated like a shunt. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_reactor(bus1="", name::AbstractString="", bus2=""; kwargs...) +function _create_reactor(bus1="", name::AbstractString="", bus2=""; kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) phases = get(kwargs, :phases, 3) kvar = get(kwargs, :kvar, 1200.0) @@ -530,35 +527,36 @@ function _create_reactor(bus1="", name::AbstractString="", bus2=""; kwargs...) xmatrix = diagm(0 => fill(x, phases)) end - return Dict{String,Any}("name" => name, - "bus1" => bus1, - "bus2" => bus2, - "phases" => phases, - "kvar" => kvar, - "kv" => kv, - "conn" => conn, - "rmatrix" => rmatrix, - "xmatrix" => xmatrix, - "parallel" => parallel, - "r" => r, - "x" => x, - "rp" => rp, - "z1" => [real(z1), imag(z1)], - "z2" => [real(z2), imag(z2)], - "z0" => [real(z0) imag(z0)], - "z" => [real(z), imag(z)], - "rcurve" => get(kwargs, :rcurve, ""), - "lcurve" => get(kwargs, :lcurve, ""), - "lmh" => lmh, - # Inherited Properties - "normamps" => normamps, - "emergamps" => emergamps, - "repair" => get(kwargs, :repair, 3.0), - "faultrate" => get(kwargs, :faultrate, 0.1), - "pctperm" => get(kwargs, :pctperm, 20.0), - "basefreq" => basefreq, - "enabled" => get(kwargs, :enabled, true) - ) + Dict{String,Any}( + "name" => name, + "bus1" => bus1, + "bus2" => bus2, + "phases" => phases, + "kvar" => kvar, + "kv" => kv, + "conn" => conn, + "rmatrix" => rmatrix, + "xmatrix" => xmatrix, + "parallel" => parallel, + "r" => r, + "x" => x, + "rp" => rp, + "z1" => [real(z1), imag(z1)], + "z2" => [real(z2), imag(z2)], + "z0" => [real(z0) imag(z0)], + "z" => [real(z), imag(z)], + "rcurve" => get(kwargs, :rcurve, ""), + "lcurve" => get(kwargs, :lcurve, ""), + "lmh" => lmh, + # Inherited Properties + "normamps" => normamps, + "emergamps" => emergamps, + "repair" => get(kwargs, :repair, 3.0), + "faultrate" => get(kwargs, :faultrate, 0.1), + "pctperm" => get(kwargs, :pctperm, 20.0), + "basefreq" => basefreq, + "enabled" => get(kwargs, :enabled, true) + ) end @@ -569,7 +567,7 @@ generator. Mostly used as `sourcebus` which represents the circuit. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_vsource(bus1="", name::AbstractString="", bus2=0; kwargs...) +function _create_vsource(bus1="", name::AbstractString="", bus2=0; kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) x1r1 = get(kwargs, :x1r1, 4.0) x0r0 = get(kwargs, :x0r0, 3.0) @@ -729,48 +727,53 @@ function _create_vsource(bus1="", name::AbstractString="", bus2=0; kwargs...) puz0 = complex(r0 / Zbase, x0 / Zbase) end - return Dict{String,Any}("name" => name, - "bus1" => bus1, - "basekv" => basekv, - "pu" => pu, - "angle" => get(kwargs, :angle, 0.0), - "frequency" => get(kwargs, :frequency, get(kwargs, :basefreq, 60.0)), - "phases" => phases, - "mvasc3" => mvasc3, - "mvasc1" => mvasc1, - "x1r1" => x1r1, - "x0r0" => x0r0, - "isc3" => isc3, - "isc1" => isc1, - "r1" => r1, - "x1" => x1, - "r0" => r0, - "x0" => x0, - "scantype" => get(kwargs, :scantype, "pos"), - "sequence" => get(kwargs, :sequence, "pos"), - "bus2" => bus2, - "z1" => [real(z1), imag(z1)], - "z0" => [real(z0), imag(z0)], - "z2" => [real(z2), imag(z2)], - "puz1" => [real(puz1), imag(puz1)], - "puz0" => [real(puz0), imag(puz0)], - "puz2" => [real(puz2), imag(puz2)], - "basemva" => basemva, - "yearly" => get(kwargs, :yearly, get(kwargs, :daily, [1.0, 1.0])), - "daily" => get(kwargs, :daily, [1.0, 1.0]), - "duty" => get(kwargs, :duty, ""), - # Inherited Properties - "spectrum" => get(kwargs, :spectrum, "defaultvsource"), - "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true), - # Derived Properties - "rmatrix" => real(Z), - "xmatrix" => imag(Z), - "vmag" => Vmag - ) + Dict{String,Any}( + "name" => name, + "bus1" => bus1, + "basekv" => basekv, + "pu" => pu, + "angle" => get(kwargs, :angle, 0.0), + "frequency" => get(kwargs, :frequency, get(kwargs, :basefreq, 60.0)), + "phases" => phases, + "mvasc3" => mvasc3, + "mvasc1" => mvasc1, + "x1r1" => x1r1, + "x0r0" => x0r0, + "isc3" => isc3, + "isc1" => isc1, + "r1" => r1, + "x1" => x1, + "r0" => r0, + "x0" => x0, + "scantype" => get(kwargs, :scantype, "pos"), + "sequence" => get(kwargs, :sequence, "pos"), + "bus2" => bus2, + "z1" => [real(z1), imag(z1)], + "z0" => [real(z0), imag(z0)], + "z2" => [real(z2), imag(z2)], + "puz1" => [real(puz1), imag(puz1)], + "puz0" => [real(puz0), imag(puz0)], + "puz2" => [real(puz2), imag(puz2)], + "basemva" => basemva, + "yearly" => get(kwargs, :yearly, get(kwargs, :daily, [1.0, 1.0])), + "daily" => get(kwargs, :daily, [1.0, 1.0]), + "duty" => get(kwargs, :duty, ""), + # Inherited Properties + "spectrum" => get(kwargs, :spectrum, "defaultvsource"), + "basefreq" => get(kwargs, :basefreq, 60.0), + "enabled" => get(kwargs, :enabled, true), + # Derived Properties + "rmatrix" => real(Z), + "xmatrix" => imag(Z), + "vmag" => Vmag + ) end +"alias _create_circuit to _create_vsource" +_create_circuit = _create_vsource + + """ Creates a Dict{String,Any} containing all of the expected properties for a Transformer. See OpenDSS documentation for valid fields and ways to specify the @@ -810,81 +813,83 @@ function _create_transformer(name::AbstractString=""; kwargs...) end end - trfm = Dict{String,Any}("name" => name, - "phases" => phases, - "windings" => windings, - # Per wdg - "wdg" => 1, - "bus" => temp["buss"][1], - "conn" => temp["conns"][1], - "kv" => temp["kvs"][1], - "kva" => temp["kvas"][1], - "tap" => temp["taps"][1], - "%r" => temp["%rs"][1], - "rneut" => temp["rneuts"][1], - "xneut" => temp["xneuts"][1], - - "wdg_2" => 2, - "bus_2" => temp["buss"][2], - "conn_2" => temp["conns"][2], - "kv_2" => temp["kvs"][2], - "kva_2" => temp["kvas"][2], - "tap_2" => temp["taps"][2], - "%r_2" => temp["%rs"][2], - "rneut_2" => temp["rneuts"][2], - "xneut_2" => temp["xneuts"][2], - - # General - "buses" => temp["buss"], - "conns" => temp["conns"], - "kvs" => temp["kvs"], - "kvas" => temp["kvas"], - "taps" => temp["taps"], - "xhl" => get(kwargs, :xhl, 7.0), - "xht" => get(kwargs, :xht, 35.0), - "xlt" => get(kwargs, :xlt, 30.0), - "xscarray" => get(kwargs, :xscarry, ""), - "thermal" => get(kwargs, :thermal, 2.0), - "n" => get(kwargs, :n, 0.8), - "m" => get(kwargs, :m, 0.8), - "flrise" => get(kwargs, :flrise, 65.0), - "hsrise" => get(kwargs, :hsrise, 15.0), - "%loadloss" => get(kwargs, Symbol("%loadloss"), sum(temp["%rs"][1:2])), - "%noloadloss" => get(kwargs, Symbol("%noloadloss"), 0.0), - "normhkva" => get(kwargs, :normhkva, 1.1 * temp["kvas"][1]), - "emerghkva" => get(kwargs, :emerghkva, 1.5 * temp["kvas"][1]), - "sub" => get(kwargs, :sub, false), - "maxtap" => get(kwargs, :maxtap, 1.10), - "mintap" => get(kwargs, :mintap, 0.90), - "numtaps" => get(kwargs, :numtaps, 32), - "subname" => get(kwargs, :subname, ""), - "%imag" => get(kwargs, Symbol("%imag"), 0.0), - "ppm_antifloat" => get(kwargs, :ppm_antifloat, 1.0), - "%rs" => temp["%rs"], - "bank" => get(kwargs, :bank, ""), - "xfmrcode" => get(kwargs, :xfmrcode, ""), - "xrconst" => get(kwargs, :xrconst, false), - "x12" => get(kwargs, :xhl, 7.0), - "x13" => get(kwargs, :xht, 35.0), - "x23" => get(kwargs, :xlt, 30.0), - "leadlag" => get(kwargs, :leadlag, "lag"), - # Inherited Properties - "faultrate" => get(kwargs, :faultrate, 0.1), - "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) - ) + trfm = Dict{String,Any}( + "name" => name, + "phases" => phases, + "windings" => windings, + # Per wdg + "wdg" => 1, + "bus" => temp["buss"][1], + "conn" => temp["conns"][1], + "kv" => temp["kvs"][1], + "kva" => temp["kvas"][1], + "tap" => temp["taps"][1], + "%r" => temp["%rs"][1], + "rneut" => temp["rneuts"][1], + "xneut" => temp["xneuts"][1], + + "wdg_2" => 2, + "bus_2" => temp["buss"][2], + "conn_2" => temp["conns"][2], + "kv_2" => temp["kvs"][2], + "kva_2" => temp["kvas"][2], + "tap_2" => temp["taps"][2], + "%r_2" => temp["%rs"][2], + "rneut_2" => temp["rneuts"][2], + "xneut_2" => temp["xneuts"][2], + + # General + "buses" => temp["buss"], + "conns" => temp["conns"], + "kvs" => temp["kvs"], + "kvas" => temp["kvas"], + "taps" => temp["taps"], + "xhl" => get(kwargs, :xhl, 7.0), + "xht" => get(kwargs, :xht, 35.0), + "xlt" => get(kwargs, :xlt, 30.0), + "xscarray" => get(kwargs, :xscarry, ""), + "thermal" => get(kwargs, :thermal, 2.0), + "n" => get(kwargs, :n, 0.8), + "m" => get(kwargs, :m, 0.8), + "flrise" => get(kwargs, :flrise, 65.0), + "hsrise" => get(kwargs, :hsrise, 15.0), + "%loadloss" => get(kwargs, Symbol("%loadloss"), sum(temp["%rs"][1:2])), + "%noloadloss" => get(kwargs, Symbol("%noloadloss"), 0.0), + "normhkva" => get(kwargs, :normhkva, 1.1 * temp["kvas"][1]), + "emerghkva" => get(kwargs, :emerghkva, 1.5 * temp["kvas"][1]), + "sub" => get(kwargs, :sub, false), + "maxtap" => get(kwargs, :maxtap, 1.10), + "mintap" => get(kwargs, :mintap, 0.90), + "numtaps" => get(kwargs, :numtaps, 32), + "subname" => get(kwargs, :subname, ""), + "%imag" => get(kwargs, Symbol("%imag"), 0.0), + "ppm_antifloat" => get(kwargs, :ppm_antifloat, 1.0), + "%rs" => temp["%rs"], + "bank" => get(kwargs, :bank, ""), + "xfmrcode" => get(kwargs, :xfmrcode, ""), + "xrconst" => get(kwargs, :xrconst, false), + "x12" => get(kwargs, :xhl, 7.0), + "x13" => get(kwargs, :xht, 35.0), + "x23" => get(kwargs, :xlt, 30.0), + "leadlag" => get(kwargs, :leadlag, "lag"), + # Inherited Properties + "faultrate" => get(kwargs, :faultrate, 0.1), + "basefreq" => get(kwargs, :basefreq, 60.0), + "enabled" => get(kwargs, :enabled, true) + ) if windings == 3 - trfm3 = Dict{String,Any}("wdg_3" => 3, - "bus_3" => temp["buss"][3], - "conn_3" => temp["conns"][3], - "kv_3" => temp["kvs"][3], - "kva_3" => temp["kvas"][3], - "tap_3" => temp["taps"][3], - "%r_3" => temp["%rs"][3], - "rneut_3" => temp["rneuts"][3], - "xneut_3" => temp["xneuts"][3], - ) + trfm3 = Dict{String,Any}( + "wdg_3" => 3, + "bus_3" => temp["buss"][3], + "conn_3" => temp["conns"][3], + "kv_3" => temp["kvs"][3], + "kva_3" => temp["kvas"][3], + "tap_3" => temp["taps"][3], + "%r_3" => temp["%rs"][3], + "rneut_3" => temp["rneuts"][3], + "xneut_3" => temp["xneuts"][3], + ) merge!(trfm, trfm3) end @@ -932,42 +937,42 @@ function _create_pvsystem(bus1="", name::AbstractString=""; kwargs...) Memento.warn(_LOGGER, "\"like\" keyword on pvsystem $name is not supported.") end - pvsystem = Dict{String,Any}("name" => name, - "phases" => get(kwargs, :phases, 3), - "bus1" => bus1, - "kv" => kv, - "kw" => kw, - "pf" => pf, - "model" => get(kwargs, :model, 1), - "yearly" => get(kwargs, :yearly, get(kwargs, :daily, [1.0, 1.0])), - "daily" => get(kwargs, :daily, [1.0, 1.0]), - "duty" => get(kwargs, :duty, ""), - "irradiance" => get(kwargs, :irradiance, 0), - "pmpp" => get(kwargs, :pmpp, 0), - "temperature" => get(kwargs, :temperature, 0), - "conn" => get(kwargs, :conn, "wye"), - "kvar" => kvar, - "kva" => kva, - "%cutin" => get(kwargs, :cutin, 0), #TODO not sure what to do with this - "%cutout" => get(kwargs, :cutout, 0), #TODO not sure what to do with this - "effcurve" => get(kwargs, :effcurve, ""), - "p-tcurve" => get(kwargs, :ptcurve, ""), - "%r" => get(kwargs, :r, 0), - "%x" => get(kwargs, :x, 0.50), - "vminpu" => get(kwargs, :vminpu, 0.9), - "vmaxpu" => get(kwargs, :vmaxpu, 1.1), - "tyearly" => get(kwargs, :tyearly, 0), - "tduty" => get(kwargs, :tduty, 0), - "class" => get(kwargs, :class, 0), - "usermodel" => get(kwargs, :usermodel, ""), - "userdata" => get(kwargs, :userdata, ""), - "debugtrace" => get(kwargs, :debugtrace, "no"), - "spectrum" => get(kwargs, :spectrum, "defaultpvsystem"), - # Inherited Properties - "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) - ) - return pvsystem + Dict{String,Any}( + "name" => name, + "phases" => get(kwargs, :phases, 3), + "bus1" => bus1, + "kv" => kv, + "kw" => kw, + "pf" => pf, + "model" => get(kwargs, :model, 1), + "yearly" => get(kwargs, :yearly, get(kwargs, :daily, [1.0, 1.0])), + "daily" => get(kwargs, :daily, [1.0, 1.0]), + "duty" => get(kwargs, :duty, ""), + "irradiance" => get(kwargs, :irradiance, 0), + "pmpp" => get(kwargs, :pmpp, 0), + "temperature" => get(kwargs, :temperature, 0), + "conn" => get(kwargs, :conn, "wye"), + "kvar" => kvar, + "kva" => kva, + "%cutin" => get(kwargs, :cutin, 0), #TODO not sure what to do with this + "%cutout" => get(kwargs, :cutout, 0), #TODO not sure what to do with this + "effcurve" => get(kwargs, :effcurve, ""), + "p-tcurve" => get(kwargs, :ptcurve, ""), + "%r" => get(kwargs, :r, 0), + "%x" => get(kwargs, :x, 0.50), + "vminpu" => get(kwargs, :vminpu, 0.9), + "vmaxpu" => get(kwargs, :vmaxpu, 1.1), + "tyearly" => get(kwargs, :tyearly, 0), + "tduty" => get(kwargs, :tduty, 0), + "class" => get(kwargs, :class, 0), + "usermodel" => get(kwargs, :usermodel, ""), + "userdata" => get(kwargs, :userdata, ""), + "debugtrace" => get(kwargs, :debugtrace, "no"), + "spectrum" => get(kwargs, :spectrum, "defaultpvsystem"), + # Inherited Properties + "basefreq" => get(kwargs, :basefreq, 60.0), + "enabled" => get(kwargs, :enabled, true) + ) end @@ -979,50 +984,50 @@ different properties. function _create_storage(bus1="", name::AbstractString=""; kwargs...) kwargs = Dict{Symbol,Any}(kwargs) - storage = Dict{String,Any}("name" => name, - "%charge" => get(kwargs, :charge, 100.0), - "%discharge" => get(kwargs, :discharge, 100.0), - "%effcharge" => get(kwargs, :effcharge, 90.0), - "%effdischarge" => get(kwargs, :effdischarge, 90.0), - "%idlingkvar" => get(kwargs, :idlingkvar, 0.0), - "%idlingkw" => get(kwargs, :idlingkw, 1.0), - "%r" => get(kwargs, :r, 0.0), - "%reserve" => get(kwargs, :reserve, 20.0), - "%stored" => get(kwargs, :stored, 100.0), - "%x" => get(kwargs, :x, 50.0), - "basefreq" => get(kwargs, :basefreq, 60.0), - "bus1" => bus1, - "chargetrigger" => get(kwargs, :chargetrigger, 0.0), - "class" => get(kwargs, :class, 0), - "conn" => get(kwargs, :conn, "wye"), - "daily" => get(kwargs, :daily, [1.0, 1.0]), - "debugtrace" => get(kwargs, :debugtrace, false), - "dischargetrigger" => get(kwargs, :dischargetrigger, 0.0), - "dispmode" => get(kwargs, :dispmode, "default"), - "duty" => get(kwargs, :duty, ""), - "dynadata" => get(kwargs, :dynadata, ""), - "dynadll" => get(kwargs, :dynadll, "none"), - "enabled" => get(kwargs, :enabled, true), - "kv" => get(kwargs, :kv, 12.47), - "kw" => get(kwargs, :kw, 0.0), - "kva" => get(kwargs, :kva, 25.0), - "kvar" => get(kwargs, :kvar, 0.0), - "kwhrated" => get(kwargs, :kwhrated, 50.0), - "kwhstored" => get(kwargs, :kwhstored, 50.0), - "kwrated" => get(kwargs, :kwrated, 50.0), - "model" => get(kwargs, :model, 1), - "pf" => get(kwargs, :pf, 1.0), - "phases" => get(kwargs, :phases, 3), - "spectrum" => get(kwargs, :spectrum, "default"), - "state" => get(kwargs, :state, "idling"), - "timechargetrig" => get(kwargs, :timechargetrig, 2.0), - "userdata" => get(kwargs, :userdata, ""), - "usermodel" => get(kwargs, :usermodel, "none"), - "vmaxpu" => get(kwargs, :vmaxpu, 1.1), - "vminpu" => get(kwargs, :vimpu, 0.9), - "yearly" => get(kwargs, :yearly, [1.0, 1.0]), - ) - return storage + Dict{String,Any}( + "name" => name, + "%charge" => get(kwargs, :charge, 100.0), + "%discharge" => get(kwargs, :discharge, 100.0), + "%effcharge" => get(kwargs, :effcharge, 90.0), + "%effdischarge" => get(kwargs, :effdischarge, 90.0), + "%idlingkvar" => get(kwargs, :idlingkvar, 0.0), + "%idlingkw" => get(kwargs, :idlingkw, 1.0), + "%r" => get(kwargs, :r, 0.0), + "%reserve" => get(kwargs, :reserve, 20.0), + "%stored" => get(kwargs, :stored, 100.0), + "%x" => get(kwargs, :x, 50.0), + "basefreq" => get(kwargs, :basefreq, 60.0), + "bus1" => bus1, + "chargetrigger" => get(kwargs, :chargetrigger, 0.0), + "class" => get(kwargs, :class, 0), + "conn" => get(kwargs, :conn, "wye"), + "daily" => get(kwargs, :daily, [1.0, 1.0]), + "debugtrace" => get(kwargs, :debugtrace, false), + "dischargetrigger" => get(kwargs, :dischargetrigger, 0.0), + "dispmode" => get(kwargs, :dispmode, "default"), + "duty" => get(kwargs, :duty, ""), + "dynadata" => get(kwargs, :dynadata, ""), + "dynadll" => get(kwargs, :dynadll, "none"), + "enabled" => get(kwargs, :enabled, true), + "kv" => get(kwargs, :kv, 12.47), + "kw" => get(kwargs, :kw, 0.0), + "kva" => get(kwargs, :kva, 25.0), + "kvar" => get(kwargs, :kvar, 0.0), + "kwhrated" => get(kwargs, :kwhrated, 50.0), + "kwhstored" => get(kwargs, :kwhstored, 50.0), + "kwrated" => get(kwargs, :kwrated, 50.0), + "model" => get(kwargs, :model, 1), + "pf" => get(kwargs, :pf, 1.0), + "phases" => get(kwargs, :phases, 3), + "spectrum" => get(kwargs, :spectrum, "default"), + "state" => get(kwargs, :state, "idling"), + "timechargetrig" => get(kwargs, :timechargetrig, 2.0), + "userdata" => get(kwargs, :userdata, ""), + "usermodel" => get(kwargs, :usermodel, "none"), + "vmaxpu" => get(kwargs, :vmaxpu, 1.1), + "vminpu" => get(kwargs, :vimpu, 0.9), + "yearly" => get(kwargs, :yearly, [1.0, 1.0]), + ) end @@ -1047,48 +1052,29 @@ function _create_loadshape(name::AbstractString=""; kwargs...) hour = get(kwargs, :hour, collect(range(1.0, step=get(kwargs, :interval, 1.0), length=npts)))[1:npts] - loadshape = Dict{String,Any}("name" => name, - "npts" => npts, - "interval" => get(kwargs, :interval, 1.0), - "minterval" => get(kwargs, :interval, 1.0) .* 60, - "sinterval" => get(kwargs, :interval, 1.0) .* 3600, - "pmult" => pmult, - "qmult" => qmult, - "hour" => hour, - "mean" => get(kwargs, :mean, mean(pmult)), - "stddev" => get(kwargs, :stddev, std(pmult)), - "csvfile" => get(kwargs, :csvfile, ""), - "sngfile" => get(kwargs, :sngfile, ""), - "dblfile" => get(kwargs, :dblfile, ""), - "pqcsvfile" => get(kwargs, :pqcsvfile, ""), - "action" => get(kwargs, :action, ""), - "useactual" => get(kwargs, :useactual, true), - "pmax" => get(kwargs, :pmax, 1.0), - "qmax" => get(kwargs, :qmax, 1.0), - "pbase" => get(kwargs, :pbase, 0.0), - ) - - return loadshape + Dict{String,Any}( + "name" => name, + "npts" => npts, + "interval" => get(kwargs, :interval, 1.0), + "minterval" => get(kwargs, :interval, 1.0) .* 60, + "sinterval" => get(kwargs, :interval, 1.0) .* 3600, + "pmult" => pmult, + "qmult" => qmult, + "hour" => hour, + "mean" => get(kwargs, :mean, mean(pmult)), + "stddev" => get(kwargs, :stddev, std(pmult)), + "csvfile" => get(kwargs, :csvfile, ""), + "sngfile" => get(kwargs, :sngfile, ""), + "dblfile" => get(kwargs, :dblfile, ""), + "pqcsvfile" => get(kwargs, :pqcsvfile, ""), + "action" => get(kwargs, :action, ""), + "useactual" => get(kwargs, :useactual, true), + "pmax" => get(kwargs, :pmax, 1.0), + "qmax" => get(kwargs, :qmax, 1.0), + "pbase" => get(kwargs, :pbase, 0.0), + ) end "Returns a Dict{String,Type} for the desired component `comp`, giving all of the expected data types" -function _get_dtypes(comp::AbstractString)::Dict - return Dict{String,Type}((k, typeof(v)) for (k, v) in _constructors[comp]()) -end - - -"list of constructor functions for easy access" -const _constructors = Dict{String,Any}("line" => _create_line, - "load" => _create_load, - "generator" => _create_generator, - "capacitor" => _create_capacitor, - "reactor" => _create_reactor, - "transformer" => _create_transformer, - "linecode" => _create_linecode, - "circuit" => _create_vsource, - "pvsystem" => _create_pvsystem, - "vsource" => _create_vsource, - "storage" => _create_storage, - "loadshape" => _create_loadshape - ) +const _dss_parameter_data_types = Dict{String,Dict{String,Type}}((comp, Dict{String,Type}((k, typeof(v)) for (k,v) in @eval $(Symbol("_create_$comp"))())) for comp in _dss_supported_components) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 4715e4b90..afa285829 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -716,10 +716,6 @@ end function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, bank_transformers::Bool=true)::Dict{String,Any} data_eng = create_data_model() - # parse_dss_with_dtypes!(data_dss, _dss_supported_components) - - data_dss["options"] = _parse_options(get(data_dss, "options", Dict{String,Any}())) - if import_all data_eng["dss_options"] = data_dss["options"] end diff --git a/src/io/utils.jl b/src/io/utils.jl new file mode 100644 index 000000000..b7a4624e2 --- /dev/null +++ b/src/io/utils.jl @@ -0,0 +1,692 @@ +"all edge types that can help define buses" +const _dss_edge_components = ["line", "transformer", "reactor"] + +"components currently supported for automatic data type parsing" +const _dss_supported_components = ["line", "linecode", "load", "generator", "capacitor", "reactor", "circuit", "transformer", "pvsystem", "storage", "loadshape"] + +"two number operators for reverse polish notation" +_double_operators = Dict( + "+" => +, + "-" => -, + "*" => *, + "/" => /, + "^" => ^, + "atan2" => (x, y) -> rad2deg(atan(y, x)) +) + +"single number operators in reverse polish notation" +_single_operators = Dict( + "sqr" => x -> x * x, + "sqrt" => sqrt, + "inv" => inv, + "ln" => log, + "exp" => exp, + "log10" => log10, + "sin" => sind, + "cos" => cosd, + "tan" => tand, + "asin" => asind, + "acos" => acosd, + "atan" => atand +) + +"different acceptable delimiters for arrays" +const _array_delimiters = ['\"', '\'', '[', '{', '(', ']', '}', ')'] + +"properties that should be excluded from being overwritten during the application of `like`" +const _like_exclusions = Dict{String,Array}( + "all" => ["name", "bus1", "bus2", "phases", "nphases", "enabled"], + "line" => ["switch"], + "transformer" => ["bank", "bus", "bus_2", "bus_3", "buses", "windings", "wdg", "wdg_2", "wdg_3"], + "linegeometry" => ["nconds"] +) + +"data types of various dss option inputs" +const _dss_option_dtypes = Dict{String,Type}( + "defaultbasefreq" => Float64, + "voltagebases" => Float64, + "tolerance" => Float64 +) + + +"detects if `expr` is Reverse Polish Notation expression" +function _isa_rpn(expr::AbstractString)::Bool + expr = split(strip(expr, _array_delimiters)) + op_keys = keys(merge(_double_operators, _single_operators)) + for item in expr + if item in op_keys + return true + end + end + return false +end + + +"parses Reverse Polish Notation `expr`" +function _parse_rpn(expr::AbstractString, dtype::Type=Float64) + clean_expr = strip(expr, _array_delimiters) + + if occursin("rollup", clean_expr) || occursin("rolldn", clean_expr) || occursin("swap", clean_expr) + Memento.warn(_LOGGER, "_parse_rpn does not support \"rollup\", \"rolldn\", or \"swap\", leaving as String") + return expr + end + + stack = [] + split_expr = occursin(",", clean_expr) ? split(clean_expr, ',') : split(clean_expr) + + for item in split_expr + try + if haskey(_double_operators, item) + b = pop!(stack) + a = pop!(stack) + push!(stack, _double_operators[item](a, b)) + elseif haskey(_single_operators, item) + push!(stack, _single_operators[item](pop!(stack))) + else + if item == "pi" + push!(stack, pi) + else + push!(stack, parse(dtype, item)) + end + end + catch error + if isa(error, ArgumentError) + Memento.warn(_LOGGER, "\"$expr\" is not valid Reverse Polish Notation, leaving as String") + return expr + else + throw(error) + end + end + end + if length(stack) > 1 + Memento.warn(_LOGGER, "\"$expr\" is not valid Reverse Polish Notation, leaving as String") + return expr + else + return stack[1] + end +end + + +"checks is a string is a connection by checking the values" +function _isa_conn(expr::AbstractString)::Bool + if expr in ["wye", "y", "ln", "delta", "ll"] + return true + else + return false + end +end + + +"parses connection \"conn\" specification reducing to wye or delta" +function _parse_conn(conn::String)::String + if conn in ["wye", "y", "ln"] + return "wye" + elseif conn in ["delta", "ll"] + return "delta" + else + Memento.warn(_LOGGER, "Unsupported connection $conn, defaulting to \"wye\"") + return "wye" + end +end + + +"checks if `data` is an opendss-style matrix string" +function _isa_matrix(data::AbstractString)::Bool + if occursin("|", data) + return true + else + return false + end +end + + +""" +Parses a OpenDSS style triangular matrix string `data` into a two dimensional +array of type `dtype`. Matrix strings are capped by either parenthesis or +brackets, rows are separated by "|", and columns are separated by spaces. +""" +function _parse_matrix(dtype::Type, data::AbstractString)::Array + rows = [] + for line in split(strip(data, _array_delimiters), '|') + cols = [] + for item in split(line) + push!(cols, parse(dtype, item)) + end + push!(rows, cols) + end + + nphases = maximum([length(row) for row in rows]) + + if dtype == AbstractString || dtype == String + matrix = fill("", nphases, nphases) + elseif dtype == Char + matrix = fill(' ', nphases, nphases) + else + matrix = zeros(dtype, nphases, nphases) + end + + if length(rows) == 1 + for i in 1:nphases + matrix[i, i] = rows[1][1] + end + elseif all([length(row) for row in rows] .== [i for i in 1:nphases]) + for (i, row) in enumerate(rows) + for (j, col) in enumerate(row) + matrix[i, j] = matrix[j, i] = col + end + end + elseif all([length(row) for row in rows] .== nphases) + for (i, row) in enumerate(rows) + for (j, col) in enumerate(row) + matrix[i, j] = col + end + end + end + + return matrix +end + + +"checks if `data` is an opendss-style array string" +function _isa_array(data::AbstractString)::Bool + clean_data = strip(data) + if !occursin("|", clean_data) + if occursin(",", clean_data) + return true + elseif startswith(clean_data, "[") && endswith(clean_data, "]") + return true + elseif startswith(clean_data, "\"") && endswith(clean_data, "\"") + return true + elseif startswith(clean_data, "\'") && endswith(clean_data, "\'") + return true + elseif startswith(clean_data, "(") && endswith(clean_data, ")") + return true + elseif startswith(clean_data, "{") && endswith(clean_data, "}") + return true + else + return false + end + else + return false + end +end + + +""" +Parses a OpenDSS style array string `data` into a one dimensional array of type +`dtype`. Array strings are capped by either brackets, single quotes, or double +quotes, and elements are separated by spaces. +""" +function _parse_array(dtype::Type, data::AbstractString) + if occursin(",", data) + split_char = ',' + else + split_char = ' ' + end + + if _isa_rpn(data) + matches = collect((m.match for m = eachmatch(Regex(string("[",join(_array_delimiters, '\\'),"]")), data, overlap=false))) + if length(matches) == 2 + if dtype == String + return data + else + return _parse_rpn(data, dtype) + end + + else + elements = _parse_properties(data[2:end-1]) + end + else + elements = split(strip(data, _array_delimiters), split_char) + elements = [strip(el) for el in elements if strip(el) != ""] + end + + if dtype == String || dtype == AbstractString || dtype == Char + array = [] + for el in elements + push!(array, el) + end + else + array = zeros(dtype, length(elements)) + for (i, el) in enumerate(elements) + if _isa_rpn(data) + array[i] = _parse_rpn(el, dtype) + else + array[i] = parse(dtype, el) + end + end + end + + return array +end + + +"Combines transformers with 'bank' keyword into a single transformer" +function _bank_transformers!(data_eng::Dict{String,<:Any}) + banks = Dict{String,Array{String,1}}() + for (name, transformer) in get(data_eng, "transformer", Dict{String,Any}()) + if haskey(transformer, "bank") + if !haskey(banks, transformer["bank"]) + banks[transformer["bank"]] = Array{String,1}([name]) + else + push!(banks[transformer["bank"]], name) + end + end + end + + banked = Dict{String,Any}() + for (bank, names) in banks + transformers = [data_eng["transformer"][name] for name in names] + + total_phases = sum(Int[transformer["nphases"] for transformer in transformers]) + + # TODO + if !haskey(banked, bank) + banked[bank] = deepcopy(transformers[1]) + end + end + + for (bank, names) in banks + if haskey(banked, bank) + for name in names + delete!(data_eng["transformer"], name) + end + end + + data_eng["transformer"][bank] = banked[bank] + end +end + + +"" +function _discover_terminals!(data_eng::Dict{String,<:Any}) + terminals = Dict{String, Set{Int}}([(name, Set{Int}()) for (name,bus) in data_eng["bus"]]) + + for (_,dss_obj) in data_eng["line"] + # ignore 0 terminal + push!(terminals[dss_obj["f_bus"]], setdiff(dss_obj["f_connections"], [0])...) + push!(terminals[dss_obj["t_bus"]], setdiff(dss_obj["t_connections"], [0])...) + end + + if haskey(data_eng, "transformer") + for (_,tr) in data_eng["transformer"] + for w in 1:length(tr["bus"]) + # ignore 0 terminal + push!(terminals[tr["bus"][w]], setdiff(tr["connections"][w], [0])...) + end + end + end + + for (id, bus) in data_eng["bus"] + data_eng["bus"][id]["terminals"] = sort(collect(terminals[id])) + end + + # identify neutrals and propagate along cables + bus_neutral = _find_neutrals(data_eng) + + for (id,bus) in data_eng["bus"] + if haskey(bus, "awaiting_ground") || haskey(bus_neutral, id) + # this bus will need a neutral + if haskey(bus_neutral, id) + neutral = bus_neutral[id] + else + neutral = maximum(bus["terminals"])+1 + push!(bus["terminals"], neutral) + end + bus["neutral"] = neutral + if haskey(bus, "awaiting_ground") + bus["grounded"] = [neutral] + bus["rg"] = [0.0] + bus["xg"] = [0.0] + for comp in bus["awaiting_ground"] + if eltype(comp["connections"])<:Array + for w in 1:length(comp["connections"]) + if comp["bus"][w]==id + comp["connections"][w] .+= (comp["connections"][w].==0)*neutral + end + end + else + comp["connections"] .+= (comp["connections"].==0)*neutral + end + # @show comp["connections"] + end + #delete!(bus, "awaiting_ground") + end + end + phases = haskey(bus, "neutral") ? setdiff(bus["terminals"], bus["neutral"]) : bus["terminals"] + bus["phases"] = phases + end +end + + +"" +function _find_neutrals(data_eng::Dict{String,<:Any}) + vertices = [(id, t) for (id, bus) in data_eng["bus"] for t in bus["terminals"]] + neutrals = [] + edges = Set([((dss_obj["f_bus"], dss_obj["f_connections"][c]),(dss_obj["t_bus"], dss_obj["t_connections"][c])) for (id, dss_obj) in data_eng["line"] for c in 1:length(dss_obj["f_connections"])]) + + bus_neutrals = [(id,bus["neutral"]) for (id,bus) in data_eng["bus"] if haskey(bus, "neutral")] + trans_neutrals = [] + for (_, tr) in data_eng["transformer"] + for w in 1:length(tr["connections"]) + if tr["configuration"][w] == "wye" + push!(trans_neutrals, (tr["bus"][w], tr["connections"][w][end])) + end + end + end + load_neutrals = [(dss_obj["bus"],dss_obj["connections"][end]) for (_,dss_obj) in get(data_eng, "load", Dict{String,Any}()) if dss_obj["configuration"]=="wye"] + neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) + neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) + stack = deepcopy(neutrals) + while !isempty(stack) + vertex = pop!(stack) + candidates_t = [((f,t), t) for (f,t) in edges if f==vertex] + candidates_f = [((f,t), f) for (f,t) in edges if t==vertex] + for (edge,next) in [candidates_t..., candidates_f...] + delete!(edges, edge) + push!(stack, next) + push!(neutrals, next) + end + end + bus_neutral = Dict{String, Int}() + for (bus,t) in neutrals + bus_neutral[bus] = t + end + return bus_neutral +end + + +"Returns an ordered list of defined conductors. If ground=false, will omit any `0`" +function _get_conductors_ordered_dm(busname::AbstractString; default::Array=[], check_length::Bool=true, pad_ground::Bool=false)::Array + parts = split(busname, '.'; limit=2) + ret = [] + if length(parts)==2 + conds_str = split(parts[2], '.') + ret = [parse(Int, i) for i in conds_str] + else + return default + end + + if pad_ground && length(ret)==length(default)-1 + ret = [ret..., 0] + end + + if check_length && length(default)!=length(ret) + # TODO + Memento.warn(_LOGGER, "An incorrect number of nodes was specified on $(parts[1]); |$(parts[2])|!=$(length(default)).") + end + return ret +end + + +"" +function _import_all!(component::Dict{String,<:Any}, defaults::Dict{String,<:Any}, prop_order::Array{<:AbstractString,1}) + component["dss"] = Dict{String,Any}((key, defaults[key]) for key in prop_order) +end + + +""" +Given a vector and a list of elements to find, this method will return a list +of the positions of the elements in that vector. +""" +function _get_idxs(vec::Vector{<:Any}, els::Vector{<:Any})::Vector{Int} + ret = Array{Int, 1}(undef, length(els)) + for (i,f) in enumerate(els) + for (j,l) in enumerate(vec) + if f==l + ret[i] = j + end + end + end + return ret +end + + +"Discovers all of the buses (not separately defined in OpenDSS), from \"lines\"" +function _discover_buses(data_dss::Dict{String,<:Any})::Set + buses = Set([]) + for obj_type in _dss_edge_components + for (name, dss_obj) in get(data_dss, obj_type, Dict{String,Any}()) + if obj_type == "transformer" + dss_obj = _create_transformer(dss_obj["name"]; _to_kwargs(dss_obj)...) + for bus in dss_obj["buses"] + push!(buses, split(bus, '.'; limit=2)[1]) + end + elseif haskey(dss_obj, "bus2") + for key in ["bus1", "bus2"] + push!(buses, split(dss_obj[key], '.'; limit=2)[1]) + end + end + end + end + + return buses +end + + +"" +function _barrel_roll(x::Vector, shift) + N = length(x) + if shift < 0 + shift = shift + ceil(Int, shift/N)*N + end + + shift = mod(shift, N) + + return x[[(i-1+shift)%N+1 for i in 1:N]] +end + + +"Parses busnames as defined in OpenDSS, e.g. \"primary.1.2.3.0\"" +function _parse_busname(busname::AbstractString) + parts = split(busname, '.'; limit=2) + name = parts[1] + elements = "1.2.3" + + if length(parts) >= 2 + name, elements = split(busname, '.'; limit=2) + end + + nodes = Array{Bool}([0 0 0 0]) + + for num in 1:3 + if occursin("$num", elements) + nodes[num] = true + end + end + + if occursin("0", elements) || sum(nodes[1:3]) == 1 + nodes[4] = true + end + + return name, nodes +end + + +"Returns an ordered list of defined conductors. If ground=false, will omit any `0`" +function _get_conductors_ordered(busname::AbstractString; neutral::Bool=true, nconductors::Int=3)::Array + parts = split(busname, '.'; limit=2) + ret = [] + if length(parts)==2 + conductors_string = split(parts[2], '.') + if neutral + ret = [parse(Int, i) for i in conductors_string] + else + ret = [parse(Int, i) for i in conductors_string if i != "0"] + end + else + ret = collect(1:nconductors) + end + + return ret +end + + +"converts Dict{String,Any} to Dict{Symbol,Any} for passing as kwargs" +function _to_kwargs(data::Dict{String,Any})::Dict{Symbol,Any} + return Dict{Symbol,Any}((Symbol(k), v) for (k, v) in data) +end + + +"" +function _apply_ordered_properties(defaults::Dict{String,<:Any}, raw_dss::Dict{String,<:Any}; code_dict::Dict{String,<:Any}=Dict{String,Any}()) + _defaults = deepcopy(defaults) + + for prop in filter(p->p!="like", raw_dss["prop_order"]) + if prop in ["linecode", "loadshape"] + merge!(defaults, code_dict) + else + defaults[prop] = _defaults[prop] + end + end + + return defaults +end + + +"applies `like` to component" +function _apply_like!(raw_dss, data_dss, comp_type) + links = ["like"] + if any(link in raw_dss["prop_order"] for link in links) + new_prop_order = [] + raw_dss_copy = deepcopy(raw_dss) + + for prop in raw_dss["prop_order"] + push!(new_prop_order, prop) + + if prop in get(_like_exclusions, comp_type, []) || prop in _like_exclusions["all"] + continue + end + + if prop in links + linked_dss = get(get(data_dss, comp_type, Dict{String,Any}()), raw_dss[prop], Dict{String,Any}()) + if isempty(linked_dss) + Memento.warn(_LOGGER, "$comp_type.$(raw_dss["name"]): $prop=$(raw_dss[prop]) cannot be found") + else + for linked_prop in linked_dss["prop_order"] + if linked_prop in get(_like_exclusions, comp_type, []) || linked_prop in _like_exclusions["all"] + continue + end + + push!(new_prop_order, linked_prop) + if linked_prop in links + _apply_like!(linked_dss, data_dss, comp_type) + else + raw_dss[linked_prop] = deepcopy(linked_dss[linked_prop]) + end + end + end + else + raw_dss[prop] = deepcopy(raw_dss_copy[prop]) + end + end + + final_prop_order = [] + while !isempty(new_prop_order) + prop = popfirst!(new_prop_order) + if !(prop in new_prop_order) + push!(final_prop_order, prop) + end + end + raw_dss["prop_order"] = final_prop_order + end +end + + +"Parses options defined with the `set` command in OpenDSS" +function parse_dss_options!(data_dss::Dict{String,<:Any}) + if haskey(data_dss, "options") + for (k,v) in data_dss["options"] + if haskey(_dss_option_dtypes, k) + dtype = _dss_option_dtypes[k] + if _isa_array(v) + data_dss["options"][k] = _parse_array(dtype, v) + elseif _isa_matrix(v) + data_dss["options"][k] = _parse_matrix(dtype, v) + else + data_dss["options"][k] = parse(dtype, v) + end + end + end + end +end + + +""" + parse_dss_with_dtypes!(data_dss, to_parse) + +Parses the data in keys defined by `to_parse` in `data_dss` using types given by +the default properties from the `get_prop_default` function. +""" +function parse_dss_with_dtypes!(data_dss::Dict{String,<:Any}, to_parse::Array{String}=_dss_supported_components) + for obj_type in to_parse + if haskey(data_dss, obj_type) + dtypes = _dss_parameter_data_types[obj_type] + if obj_type == "circuit" + _parse_obj_dtypes!(obj_type, data_dss[obj_type], dtypes) + else + for object in values(data_dss[obj_type]) + _parse_obj_dtypes!(obj_type, object, dtypes) + end + end + end + end +end + + +"parses the raw dss values into their expected data types" +function _parse_element_with_dtype(dtype, element) + if _isa_matrix(element) + out = _parse_matrix(eltype(dtype), element) + elseif _isa_array(element) + out = _parse_array(eltype(dtype), element) + elseif dtype <: Bool + if element in ["n", "no"] + element = "false" + elseif element in ["y", "yes"] + element = "true" + end + out = parse(dtype, element) + elseif _isa_rpn(element) + out = _parse_rpn(element) + elseif dtype == String + out = element + else + if _isa_conn(element) + out = _parse_conn(element) + else + try + out = parse(dtype, element) + catch + Memento.warn(_LOGGER, "cannot parse $element as $dtype, leaving as String.") + out = element + end + end + end + + return out +end + + +"" +function _parse_obj_dtypes!(obj_type, object, dtypes) + for (k, v) in object + if haskey(dtypes, k) + if isa(v, Array) + arrout = [] + for el in v + if isa(v, AbstractString) + push!(arrout, _parse_element_with_dtype(dtypes[k], el)) + else + push!(arrout, el) + end + end + object[k] = arrout + elseif isa(v, AbstractString) + object[k] = _parse_element_with_dtype(dtypes[k], v) + else + Memento.error(_LOGGER, "dtype unknown $obj_type, $k, $v") + end + end + end +end diff --git a/test/opendss.jl b/test/opendss.jl index 8ed735d83..db63042ca 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -8,7 +8,7 @@ loadshapes = Dict{String,Any}() for ls in dss["loadshape"] - loadshapes[ls["name"]] = PMD._create_loadshape(ls["name"]; PMD._to_sym_keys(ls)...) + loadshapes[ls["name"]] = PMD._create_loadshape(ls["name"]; PMD._to_kwargs(ls)...) end @test isapprox(loadshapes["1"]["interval"], 1.0/60) @@ -292,7 +292,7 @@ @testset "opendss parse verify mvasc3/mvasc1 circuit parse" begin dss = PMD.parse_dss("../test/data/opendss/test_simple.dss") PMD.parse_dss_with_dtypes!(dss, ["circuit"]) - circuit = PMD._create_vsource("sourcebus", "simple"; PMD._to_sym_keys(dss["circuit"][1])...) + circuit = PMD._create_vsource("sourcebus", "simple"; PMD._to_kwargs(dss["circuit"][1])...) @test circuit["mvasc1"] == 2100.0 @test circuit["mvasc3"] == 1900.0 @@ -301,7 +301,7 @@ dss = PMD.parse_dss("../test/data/opendss/test_simple3.dss") PMD.parse_dss_with_dtypes!(dss, ["circuit"]) - circuit = PMD._create_vsource("sourcebus", "simple"; PMD._to_sym_keys(dss["circuit"][1])...) + circuit = PMD._create_vsource("sourcebus", "simple"; PMD._to_kwargs(dss["circuit"][1])...) @test circuit["mvasc1"] == 2100.0 @test isapprox(circuit["mvasc3"], 1900.0; atol=1e-1) @@ -310,7 +310,7 @@ dss = PMD.parse_dss("../test/data/opendss/test_simple4.dss") PMD.parse_dss_with_dtypes!(dss, ["circuit"]) - circuit = PMD._create_vsource("sourcebus", "simple"; PMD._to_sym_keys(dss["circuit"][1])...) + circuit = PMD._create_vsource("sourcebus", "simple"; PMD._to_kwargs(dss["circuit"][1])...) @test isapprox(circuit["mvasc1"], 2091.5; atol=1e-1) @test circuit["mvasc3"] == 2000.0 diff --git a/test/transformer.jl b/test/transformer.jl index 1b796d3de..9373ec2ed 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -75,7 +75,7 @@ # # dss = PMD.parse_dss(file) # PMD.parse_dss_with_dtypes!(dss, ["line", "load", "transformer"]) - # trans = PMD._create_transformer(dss["transformer"][1]["name"]; PMD._to_sym_keys(dss["transformer"][1])...) + # trans = PMD._create_transformer(dss["transformer"][1]["name"]; PMD._to_kwargs(dss["transformer"][1])...) # @test all(trans["%rs"] .== [1.0, 2.0]) # end From a510f0de77e03c3f1f62cb02b16b984616700b42 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 4 Mar 2020 16:22:26 -0700 Subject: [PATCH 061/224] FIX: data.jl tests --- src/core/data.jl | 86 +++++++++++++++++++------------------- src/data_model/eng2math.jl | 6 ++- src/io/dss_parse.jl | 4 +- src/io/opendss.jl | 5 ++- src/io/utils.jl | 8 ++-- test/runtests.jl | 30 ++++++------- 6 files changed, 76 insertions(+), 63 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index b68bd1347..1b992a8f8 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -39,30 +39,19 @@ end _replace_nan(v) = map(x -> isnan(x) ? zero(x) : x, v) + "Counts number of nodes in network" -function count_nodes(dss_data::Dict{String,Array})::Int - sourcebus = get(dss_data["circuit"][1], "bus1", "sourcebus") - all_nodes = Dict() - for comp_type in values(dss_data) - for comp in values(comp_type) - if isa(comp, Dict) && haskey(comp, "buses") - for busname in values(_parse_array(String, comp["buses"])) - name, nodes = _parse_busname(busname) - - if !haskey(all_nodes, name) - all_nodes[name] = Set([]) - end +function count_nodes(data::Dict{String,<:Any})::Int + n_nodes = 0 - for (n, node) in enumerate(nodes[1:3]) - if node - push!(all_nodes[name], n) - end - end - end - elseif isa(comp, Dict) - for (prop, val) in comp - if startswith(prop, "bus") && prop != "buses" - name, nodes = _parse_busname(val) + if get(data, "source_type", "none") == "dss" && !haskey(data, "data_model") + sourcebus = get(data["circuit"], "bus1", "sourcebus") + all_nodes = Dict() + for comp_type in values(data) + for comp in values(comp_type) + if isa(comp, Dict) && haskey(comp, "buses") + for busname in values(comp["buses"]) + name, nodes = _parse_busname(busname) if !haskey(all_nodes, name) all_nodes[name] = Set([]) @@ -74,37 +63,50 @@ function count_nodes(dss_data::Dict{String,Array})::Int end end end + elseif isa(comp, Dict) + for (prop, val) in comp + if startswith(prop, "bus") && prop != "buses" + name, nodes = _parse_busname(val) + + if !haskey(all_nodes, name) + all_nodes[name] = Set([]) + end + + for (n, node) in enumerate(nodes[1:3]) + if node + push!(all_nodes[name], n) + end + end + end + end end end end - end - n_nodes = 0 - for (name, phases) in all_nodes - if name != sourcebus - n_nodes += length(phases) + for (name, phases) in all_nodes + if name != sourcebus + n_nodes += length(phases) + end end - end - - return n_nodes -end + elseif get(data, "source_type", "none") == "dss" && haskey(data, "data_model") -"Counts number of nodes in network" -function count_nodes(pmd_data::Dict{String,Any})::Int - if pmd_data["source_type"] == "dss" - Memento.info(_LOGGER, "counting nodes from PowerModelsDistribution structure may not be as accurate as directly from `parse_dss` data due to virtual buses, etc.") - end + if get(data, "data_model", "mathematical") == "mathematical" + Memento.info(_LOGGER, "counting nodes from PowerModelsDistribution structure may not be as accurate as directly from `parse_dss` data due to virtual buses, etc.") + end - n_nodes = 0 - for bus in values(pmd_data["bus"]) - if pmd_data["source_type"] == "matlab" - n_nodes += sum(bus["vm"] .> 0.0) - elseif pmd_data["source_type"] == "dss" - if !(pmd_data["source_type"] == "dss" && bus["name"] in ["virtual_sourcebus", pmd_data["sourcebus"]]) && !(pmd_data["source_type"] == "dss" && startswith(bus["name"], "tr") && endswith(bus["name"], r"_b\d")) + n_nodes = 0 + for bus in values(data["bus"]) + if get(data, "source_type", "none") == "matlab" n_nodes += sum(bus["vm"] .> 0.0) + elseif get(data, "source_type", "none") == "dss" + if !(data["source_type"] == "dss" && bus["name"] in ["_virtual_sourcebus", data["sourcebus"]]) && !(data["source_type"] == "dss" && startswith(bus["name"], "tr") && endswith(bus["name"], r"_b\d")) + n_nodes += sum(bus["vm"] .> 0.0) + end end end + else + Memento.error(_LOGGER, "Origin of data structure not recognized, cannot count nodes reliably") end return n_nodes diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index ec2349a86..0be78b64d 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -43,7 +43,9 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) data_math = Dict{String,Any}( "name" => data_eng["name"], - "per_unit" => get(data_eng, "per_unit", false) + "per_unit" => get(data_eng, "per_unit", false), + "source_type" => get(data_eng, "source_type", "none"), + "sourcebus" => get(data_eng, "sourcebus", "sourcebus"), ) data_math["conductors"] = kron_reduced ? 3 : 4 @@ -135,6 +137,8 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, math_obj = _init_math_obj("bus", eng_obj, length(data_math["bus"])+1) + math_obj["name"] = name + math_obj["bus_i"] = math_obj["index"] math_obj["bus_type"] = eng_obj["status"] == 1 ? 1 : 4 diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index ba7c47e66..00ed3a4d1 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -640,7 +640,7 @@ end "" -function parse_dss(filename::AbstractString)::Dict +function parse_dss(filename::AbstractString)::Dict{String,Any} data_dss = open(filename) do io parse_dss(io) end @@ -773,5 +773,7 @@ function parse_dss(io::IOStream)::Dict{String,Any} parse_dss_options!(data_dss) + data_dss["source_type"] = "dss" + return data_dss end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index afa285829..efbf87644 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -714,7 +714,10 @@ end "Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, bank_transformers::Bool=true)::Dict{String,Any} - data_eng = create_data_model() + data_eng = Dict{String,Any}( + "source_type" => data_dss["source_type"], + "settings" => Dict{String,Any}(), + ) if import_all data_eng["dss_options"] = data_dss["options"] diff --git a/src/io/utils.jl b/src/io/utils.jl index b7a4624e2..cc0dfab92 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -217,7 +217,7 @@ Parses a OpenDSS style array string `data` into a one dimensional array of type `dtype`. Array strings are capped by either brackets, single quotes, or double quotes, and elements are separated by spaces. """ -function _parse_array(dtype::Type, data::AbstractString) +function _parse_array(dtype::Type, data::AbstractString)::Vector if occursin(",", data) split_char = ',' else @@ -242,7 +242,7 @@ function _parse_array(dtype::Type, data::AbstractString) end if dtype == String || dtype == AbstractString || dtype == Char - array = [] + array = Vector{String}([]) for el in elements push!(array, el) end @@ -636,7 +636,9 @@ end "parses the raw dss values into their expected data types" function _parse_element_with_dtype(dtype, element) - if _isa_matrix(element) + if _isa_rpn(element) + out = _parse_rpn(element, dtype) + elseif _isa_matrix(element) out = _parse_matrix(eltype(dtype), element) elseif _isa_array(element) out = _parse_array(eltype(dtype), element) diff --git a/test/runtests.jl b/test/runtests.jl index 8dd845a80..ff2094b3b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,37 +31,37 @@ cbc_solver = with_optimizer(Cbc.Optimizer, logLevel=0) scs_solver = with_optimizer(SCS.Optimizer, max_iters=20000, eps=1e-5, alpha=0.4, verbose=0) juniper_solver = with_optimizer(Juniper.Optimizer, nl_solver=with_optimizer(Ipopt.Optimizer, tol=1e-4, print_level=0), mip_solver=cbc_solver, log_levels=[]) -include("common.jl") # all passing +include("common.jl") @testset "PowerModelsDistribution" begin - # include("opendss.jl") # all passing + # include("opendss.jl") - # include("data.jl") # all passing + include("data.jl") # all passing include("pf.jl") # all passing - # include("pf_iv.jl") # all passing + # include("pf_iv.jl") - # include("opf.jl") # all passing + # include("opf.jl") - # include("opf_bf.jl") # all passing + # include("opf_bf.jl") - # include("opf_iv.jl") # all passing + # include("opf_iv.jl") - # include("storage.jl") # all passing + # include("storage.jl") - # include("debug.jl") # all passing + # include("debug.jl") - # include("multinetwork.jl") # all passing + # include("multinetwork.jl") - # include("transformer.jl") # all passing + # include("transformer.jl") - # include("loadmodels.jl") # all passing + # include("loadmodels.jl") - # include("delta_gens.jl") # all passing + # include("delta_gens.jl") - # include("shunt.jl") # all passing + # include("shunt.jl") - # include("mld.jl") # all passing + # include("mld.jl") end From 4387873205d396f7cc17cb393530f179aba93e32 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 5 Mar 2020 08:49:00 -0700 Subject: [PATCH 062/224] FIX: opf tests --- src/data_model/eng2math.jl | 454 ++----------------------------------- src/data_model/math2eng.jl | 59 +++++ src/data_model/utils.jl | 375 ++++++++++++++++++++++++++++++ src/io/opendss.jl | 12 +- test/opf.jl | 42 ++-- test/runtests.jl | 4 +- 6 files changed, 486 insertions(+), 460 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 0be78b64d..b3f9b2570 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -132,6 +132,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, phases = get(eng_obj, "phases", [1, 2, 3]) neutral = get(eng_obj, "neutral", 4) terminals = eng_obj["terminals"] + nconductors = data_math["conductors"] @assert all(t in [phases..., neutral] for t in terminals) @@ -158,6 +159,8 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, end end + _pad_properties!(math_obj, ["vm", "va", "vmin", "vmax"], terminals, collect(1:nconductors); neutral=neutral) + data_math["bus"]["$(math_obj["index"])"] = math_obj if !haskey(data_math, "bus_lookup") @@ -282,6 +285,8 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D math_obj = _init_math_obj("shunt_reactor", eng_obj, length(data_math["shunt"])+1) nphases = eng_obj["phases"] + connections = eng_obj["connections"] + nconductors = data_math["conductors"] Zbase = (data_math["basekv"] / sqrt(3.0))^2 * nphases / data_math["baseMVA"] # Use single-phase base impedance for each phase Gcap = Zbase * sum(eng_obj["kvar"]) / (nphases * 1e3 * (data_math["basekv"] / sqrt(3.0))^2) @@ -292,6 +297,8 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D math_obj["gs"] = fill(0.0, nphases, nphases) math_obj["bs"] = diagm(0=>fill(Gcap, nphases)) + _pad_properties!(math_obj, ["gs", "bs"], connections, collect(1:nconductors)) + data_math["shunt"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( @@ -311,6 +318,8 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj = _init_math_obj("generator", eng_obj, length(data_math["gen"])+1) phases = eng_obj["phases"] + connections = eng_obj["connections"] + nconductors = data_math["conductors"] math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["name"] = name @@ -328,6 +337,8 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["conn"] = eng_obj["configuration"] + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, collect(1:nconductors)) + # if PV generator mode convert attached bus to PV bus if eng_obj["control_model"] == 3 data_math["bus"][data_math["bus_lookup"][eng_obj["bus"]]]["bus_type"] = 2 @@ -352,6 +363,8 @@ function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{< math_obj = _init_math_obj("pvsystem", eng_obj, length(data_math["gen"])+1) phases = eng_obj["phases"] + connections = eng_obj["connections"] + nconductors = data_math["conductors"] math_obj["name"] = name math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] @@ -369,6 +382,8 @@ function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{< math_obj["conn"] = eng_obj["configuration"] + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, collect(1:nconductors)) + data_math["gen"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( @@ -388,6 +403,8 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: math_obj = _init_math_obj("storage", eng_obj, length(data_math["storage"])+1) phases = eng_obj["phases"] + connections = eng_obj["connections"] + nconductors = data_math["conductors"] math_obj["name"] = name math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] @@ -409,6 +426,8 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: math_obj["ps"] = fill(0.0, phases) math_obj["qs"] = fill(0.0, phases) + _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, collect(1:nconductors)) + data_math["storage"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( @@ -743,438 +762,3 @@ function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{ end end end - - -"" -function _map_math2eng!(data_math) - @assert get(data_math, "data_model", "mathematical") == "mathematical" "Cannot map data to engineering model: provided data is not a mathematical model" - @assert haskey(data_math, "map") "Cannot map data to engineering model: no mapping from mathematical to engineering data model is provided" - - data_eng = Dict{<:Any,<:Any}() - - map_keys = sort(keys(data_math["map"]); reverse=true) - for map in map_keys - # TODO - end - -end - - -"" -function _expand_linecode!(data_model) - # expand line codes - for (id, line) in data_model["line"] - if haskey(line, "linecode") - linecode = data_model["linecode"][line["linecode"]] - for key in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - line[key] = line["length"]*linecode[key] - end - delete!(line, "linecode") - delete!(line, "length") - end - end - delete!(data_model, "linecode") -end - - -"" -function _lossy_ground_to_shunt!(data_model) - mappings = [] - - if haskey(data_model, "bus") - for (id, bus) in data_model["bus"] - grounding_lossy_inds = [i for (i,t) in enumerate(bus["grounded"]) if bus["rg"][i]!=0 || bus["xg"][i]!=0] - grounding_lossy = bus["grounded"][grounding_lossy_inds] - grounding_perfect = bus["grounded"][setdiff(1:length(bus["grounded"]), grounding_lossy_inds)] - - if !isempty(grounding_lossy) - zg = bus["rg"][grounding_lossy_inds].+im*bus["xg"][grounding_lossy_inds] - Y_sh = diagm(0=>inv.(zg)) # diagonal matrix, so matrix inverse is element-wise inverse - add_virtual!(data_model, "shunt", create_shunt(bus=bus["id"], connections=grounding_lossy, - g_sh=real.(Y_sh), b_sh=imag.(Y_sh) - )) - end - end - end - return mappings -end - - -"" -function _load_to_shunt!(data_model) - mappings = [] - if haskey(data_model, "load") - for (id, load) in data_model["load"] - if load["model"]=="constant_impedance" - b = load["qd_ref"]./load["vnom"].^2*1E3 - g = load["pd_ref"]./load["vnom"].^2*1E3 - y = b.+im*g - N = length(b) - - if load["configuration"]=="delta" - # create delta transformation matrix Md - Md = diagm(0=>ones(N), 1=>-ones(N-1)) - Md[N,1] = -1 - Y = Md'*diagm(0=>y)*Md - - else # load["configuration"]=="wye" - Y_fr = diagm(0=>y) - # B = [[b]; -1'*[b]]*[I -1] - Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(diagm(0=>ones(N)), -ones(N)) - end - - shunt = add_virtual!(data_model, "shunt", create_shunt(bus=load["bus"], connections=load["connections"], b_sh=imag.(Y), g_sh=real.(Y))) - - delete_component!(data_model, "load", load) - - push!(mappings, Dict( - "load" => load, - "shunt_id" => shunt["id"], - )) - end - end - end - - return mappings -end - - -"" -function _capacitor_to_shunt!(data_model) - mappings = [] - - if haskey(data_model, "capacitor") - for (id, cap) in data_model["capacitor"] - b = cap["qd_ref"]./cap["vnom"]^2*1E3 - N = length(b) - - if cap["configuration"]=="delta" - # create delta transformation matrix Md - Md = diagm(0=>ones(N), 1=>-ones(N-1)) - Md[N,1] = -1 - B = Md'*diagm(0=>b)*Md - - elseif cap["configuration"]=="wye-grounded" - B = diagm(0=>b) - - elseif cap["configuration"]=="wye-floating" - # this is a floating wye-segment - # B = [b]*(I-1/(b'*1)*[b';...;b']) - B = diagm(0=>b)*(diagm(0=>ones(N)) - 1/sum(b)*repeat(b',N,1)) - - else # cap["configuration"]=="wye" - B_fr = diagm(0=>b) - # B = [[b]; -1'*[b]]*[I -1] - B = vcat(B_fr, -ones(N)'*B_fr)*hcat(diagm(0=>ones(N)), -ones(N)) - end - - shunt = create_shunt(NaN, cap["bus"], cap["connections"], b_sh=B) - add_virtual!(data_model, "shunt", shunt) - delete_component!(data_model, "capacitor", cap) - - push!(mappings, Dict( - "capacitor" => cap, - "shunt_id" => shunt["id"], - )) - end - end - - return mappings -end - - - -""" -Converts a set of short-circuit tests to an equivalent reactance network. -Reference: -R. C. Dugan, “A perspective on transformer modeling for distribution system analysis,” -in 2003 IEEE Power Engineering Society General Meeting (IEEE Cat. No.03CH37491), 2003, vol. 1, pp. 114-119 Vol. 1. -""" -function _sc2br_impedance(Zsc) - N = maximum([maximum(k) for k in keys(Zsc)]) - # check whether no keys are missing - # Zsc should contain tupples for upper triangle of NxN - for i in 1:N - for j in i+1:N - if !haskey(Zsc, (i,j)) - if haskey(Zsc, (j,i)) - # Zsc is symmetric; use value of lower triangle if defined - Zsc[(i,j)] = Zsc[(j,i)] - else - Memento.error(_LOGGER, "Short-circuit impedance between winding $i and $j is missing.") - end - end - end - end - # make Zb - Zb = zeros(Complex{Float64}, N-1,N-1) - for i in 1:N-1 - Zb[i,i] = Zsc[(1,i+1)] - end - for i in 1:N-1 - for j in 1:i-1 - Zb[i,j] = (Zb[i,i]+Zb[j,j]-Zsc[(j+1,i+1)])/2 - Zb[j,i] = Zb[i,j] - end - end - # get Ybus - Y = LinearAlgebra.pinv(Zb) - Y = [-Y*ones(N-1) Y] - Y = [-ones(1,N-1)*Y; Y] - # extract elements - Zbr = Dict() - for k in keys(Zsc) - Zbr[k] = (abs(Y[k...])==0) ? Inf : -1/Y[k...] - end - return Zbr -end - - -"" -function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::String, to_map::Vector{String}, r_s::Vector{Float64}, zsc::Dict{Tuple{Int,Int},Complex{Float64}}, ysh::Complex{Float64}; nphases::Int=3) - # precompute the minimal set of buses and lines - N = length(r_s) - tr_t_bus = collect(1:N) - buses = Set(1:2*N) - - edges = [[[i,i+N] for i in 1:N]..., [[i+N,j+N] for (i,j) in keys(zsc)]...] - lines = Dict(enumerate(edges)) - - z = Dict(enumerate([r_s..., values(zsc)...])) - - shunts = Dict(2=>ysh) - - # remove Inf lines - for (l,edge) in lines - if real(z[l])==Inf || imag(z[l])==Inf - delete!(lines, l) - delete!(z, l) - end - end - - # merge short circuits - stack = Set(keys(lines)) - - while !isempty(stack) - l = pop!(stack) - if z[l] == 0 - (i,j) = lines[l] - - # remove line - delete!(lines, l) - - # remove bus j - delete!(buses, j) - - # update lines - for (k,(edge)) in lines - if edge[1] == j - edge[1] = i - end - if edge[2] == j - edge[2] = i - end - if edge[1]==edge[2] - delete!(lines, k) - delete!(stack, k) - end - end - - # move shunts - if haskey(shunts, j) - if haskey(shunts, i) - shunts[i] += shunts[j] - else - shunts[i] = shunts[j] - end - end - - # update transformer buses - for w in 1:N - if tr_t_bus[w] == j - tr_t_bus[w] = i - end - end - end - end - - bus_ids = Dict() - for bus in buses - bus_obj = Dict{String,Any}( - "name" => "_virtual_bus.transformer.$(transformer_name)_$(bus)", - "bus_i" => length(data_math["bus"])+1, - "vm" => fill(1.0, nphases), - "va" => fill(0.0, nphases), - "vmin" => fill(0.0, nphases), - "vmax" => fill(Inf, nphases), - "base_kv" => 1.0, - "bus_type" => 1, - "status" => 1, - "index" => length(data_math["bus"])+1, - ) - - data_math["bus"]["$(bus_obj["index"])"] = bus_obj - - bus_ids[bus] = bus_obj["bus_i"] - - push!(to_map, "bus.$(bus_obj["index"])") - end - - - for (l,(i,j)) in lines - # merge the shunts into the shunts of the pi model of the line - g_fr = b_fr = g_to = b_to = 0 - - if haskey(shunts, i) - g_fr = real(shunts[i]) - b_fr = imag(shunts[i]) - delete!(shunts, i) - end - - if haskey(shunts, j) - g_fr = real(shunts[j]) - b_fr = imag(shunts[j]) - delete!(shunts, j) - end - - branch_obj = Dict{String,Any}( - "name" => "_virtual_branch.transformer.$(transformer_name)_$(l)", - "source_id" => "_virtual_branch.transformer.$(transformer_name)_$(l)", - "index" => length(data_math["branch"])+1, - "br_status"=>1, - "f_bus"=>bus_ids[i], - "t_bus"=>bus_ids[j], - "f_connections"=>collect(1:nphases), - "t_connections"=>collect(1:nphases), - "br_r" => diagm(0=>fill(real(z[l]), nphases)), - "br_x" => diagm(0=>fill(imag(z[l]), nphases)), - "g_fr" => diagm(0=>fill(g_fr, nphases)), - "b_fr" => diagm(0=>fill(b_fr, nphases)), - "g_to" => diagm(0=>fill(g_to, nphases)), - "b_to" => diagm(0=>fill(b_to, nphases)), - "angmin" => fill(-60.0, nphases), - "angmax" => fill( 60.0, nphases), - "shift" => zeros(nphases), - "tap" => ones(nphases) - ) - - data_math["branch"]["$(branch_obj["index"])"] = branch_obj - - push!(to_map, "branch.$(branch_obj["index"])") - end - - return [bus_ids[bus] for bus in tr_t_bus] -end - - -"" -function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; neutral::Int=4, kron_reduced::Bool=true) - if kron_reduced - pos = Dict((x,i) for (i,x) in enumerate(phases)) - inds = [pos[x] for x in connections[connections.!=neutral]] - else - # TODO - end - - for property in properties - if haskey(object, property) - if isa(object[property], Vector) - tmp = zeros(length(phases)) - tmp[inds] = object[property] - object[property] = tmp - elseif isa(object[property], Matrix) - tmp = zeros(length(phases), length(phases)) - tmp[inds, inds] = object[property] - object[property] = tmp - end - end - end -end - - -# MAP SOLUTION UP -"" -function solution_unmap!(solution::Dict, data_model::Dict) - for (name, data) in reverse(data_model["mappings"]) - if name=="decompose_transformer_nw" - for bus_id in values(data["vbuses"]) - delete!(solution["bus"], bus_id) - end - - for line_id in values(data["vlines"]) - delete!(solution["branch"], line_id) - end - - pt = [solution["transformer"][tr_id]["pf"] for tr_id in data["trans_2wa"]] - qt = [solution["transformer"][tr_id]["qf"] for tr_id in data["trans_2wa"]] - for tr_id in data["trans_2wa"] - delete!(solution["transformer"], tr_id) - end - - add_solution!(solution, "transformer_nw", data["trans"]["id"], Dict("pt"=>pt, "qt"=>qt)) - elseif name=="capacitor_to_shunt" - # shunt has no solutions defined - delete_solution!(solution, "shunt", data["shunt_id"]) - add_solution!(solution, "capacitor", data["capacitor"]["id"], Dict()) - elseif name=="load_to_shunt" - # shunt has no solutions, but a load should have! - delete!(solution, "shunt", data["shunt_id"]) - add_solution!(solution, "load", data["load"]["id"], Dict()) - elseif name=="decompose_voltage_source" - gen = solution["gen"][data["gen_id"]] - delete_solution!(solution, "gen", data["gen_id"]) - add_solution!(solution, "voltage_source", data["voltage_source"]["id"], Dict("pg"=>gen["pg"], "qg"=>gen["qg"])) - - end - end - - # remove component dicts if empty - for (comp_type, comp_dict) in solution - if isa(comp_dict, Dict) && isempty(comp_dict) - delete!(solution, comp_type) - end - end -end - - - -""" -Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the -conductors 't_cnds', this method will return a list of conductors 'cnd' and a -matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. -""" -function calc_shunt(f_cnds::Vector{Int}, t_cnds::Vector{Int}, y::Vector{Float64}) - cnds = unique([f_cnds..., t_cnds...]) - e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) - Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) - return (cnds, Y) -end - - - -""" -Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this -method will calculate the reduced addmittance matrix if terminal 'ground' is -grounded. -""" -function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{Float64}, ground::Int) - # TODO add types - if ground in cnds - cndsr = setdiff(cnds, ground) - cndsr_inds = _get_idxs(cnds, cndsr) - Yr = Y[cndsr_inds, cndsr_inds] - return (cndsr, Yr) - else - return cnds, Y - end -end - - -"" -function _rm_floating_cnd(cnds::Vector{Int}, Y::Matrix{Float64}, f::Int) - P = setdiff(cnds, f) - f_inds = _get_idxs(cnds, [f]) - P_inds = _get_idxs(cnds, P) - Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] - return (P,Yrm) -end diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index e69de29bb..c270fd143 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -0,0 +1,59 @@ +"" +function _map_math2eng!(data_math) + @assert get(data_math, "data_model", "mathematical") == "mathematical" "Cannot map data to engineering model: provided data is not a mathematical model" + @assert haskey(data_math, "map") "Cannot map data to engineering model: no mapping from mathematical to engineering data model is provided" + + data_eng = Dict{<:Any,<:Any}() + + map_keys = sort(keys(data_math["map"]); reverse=true) + for map in map_keys + # TODO + end + +end + + +# MAP SOLUTION UP +"" +function solution_unmap!(solution::Dict, data_model::Dict) + for (name, data) in reverse(data_model["mappings"]) + if name=="decompose_transformer_nw" + for bus_id in values(data["vbuses"]) + delete!(solution["bus"], bus_id) + end + + for line_id in values(data["vlines"]) + delete!(solution["branch"], line_id) + end + + pt = [solution["transformer"][tr_id]["pf"] for tr_id in data["trans_2wa"]] + qt = [solution["transformer"][tr_id]["qf"] for tr_id in data["trans_2wa"]] + for tr_id in data["trans_2wa"] + delete!(solution["transformer"], tr_id) + end + + add_solution!(solution, "transformer_nw", data["trans"]["id"], Dict("pt"=>pt, "qt"=>qt)) + elseif name=="capacitor_to_shunt" + # shunt has no solutions defined + delete_solution!(solution, "shunt", data["shunt_id"]) + add_solution!(solution, "capacitor", data["capacitor"]["id"], Dict()) + elseif name=="load_to_shunt" + # shunt has no solutions, but a load should have! + delete!(solution, "shunt", data["shunt_id"]) + add_solution!(solution, "load", data["load"]["id"], Dict()) + elseif name=="decompose_voltage_source" + gen = solution["gen"][data["gen_id"]] + delete_solution!(solution, "gen", data["gen_id"]) + add_solution!(solution, "voltage_source", data["voltage_source"]["id"], Dict("pg"=>gen["pg"], "qg"=>gen["qg"])) + + end + end + + # remove component dicts if empty + for (comp_type, comp_dict) in solution + if isa(comp_dict, Dict) && isempty(comp_dict) + delete!(solution, comp_type) + end + end +end + diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index a3ad4d150..d29b3eed1 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -193,3 +193,378 @@ function _get_ground!(bus) return g end end + + +""" +Converts a set of short-circuit tests to an equivalent reactance network. +Reference: +R. C. Dugan, “A perspective on transformer modeling for distribution system analysis,” +in 2003 IEEE Power Engineering Society General Meeting (IEEE Cat. No.03CH37491), 2003, vol. 1, pp. 114-119 Vol. 1. +""" +function _sc2br_impedance(Zsc) + N = maximum([maximum(k) for k in keys(Zsc)]) + # check whether no keys are missing + # Zsc should contain tupples for upper triangle of NxN + for i in 1:N + for j in i+1:N + if !haskey(Zsc, (i,j)) + if haskey(Zsc, (j,i)) + # Zsc is symmetric; use value of lower triangle if defined + Zsc[(i,j)] = Zsc[(j,i)] + else + Memento.error(_LOGGER, "Short-circuit impedance between winding $i and $j is missing.") + end + end + end + end + # make Zb + Zb = zeros(Complex{Float64}, N-1,N-1) + for i in 1:N-1 + Zb[i,i] = Zsc[(1,i+1)] + end + for i in 1:N-1 + for j in 1:i-1 + Zb[i,j] = (Zb[i,i]+Zb[j,j]-Zsc[(j+1,i+1)])/2 + Zb[j,i] = Zb[i,j] + end + end + # get Ybus + Y = LinearAlgebra.pinv(Zb) + Y = [-Y*ones(N-1) Y] + Y = [-ones(1,N-1)*Y; Y] + # extract elements + Zbr = Dict() + for k in keys(Zsc) + Zbr[k] = (abs(Y[k...])==0) ? Inf : -1/Y[k...] + end + return Zbr +end + + + + +"" +function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::String, to_map::Vector{String}, r_s::Vector{Float64}, zsc::Dict{Tuple{Int,Int},Complex{Float64}}, ysh::Complex{Float64}; nphases::Int=3) + # precompute the minimal set of buses and lines + N = length(r_s) + tr_t_bus = collect(1:N) + buses = Set(1:2*N) + + edges = [[[i,i+N] for i in 1:N]..., [[i+N,j+N] for (i,j) in keys(zsc)]...] + lines = Dict(enumerate(edges)) + + z = Dict(enumerate([r_s..., values(zsc)...])) + + shunts = Dict(2=>ysh) + + # remove Inf lines + for (l,edge) in lines + if real(z[l])==Inf || imag(z[l])==Inf + delete!(lines, l) + delete!(z, l) + end + end + + # merge short circuits + stack = Set(keys(lines)) + + while !isempty(stack) + l = pop!(stack) + if z[l] == 0 + (i,j) = lines[l] + + # remove line + delete!(lines, l) + + # remove bus j + delete!(buses, j) + + # update lines + for (k,(edge)) in lines + if edge[1] == j + edge[1] = i + end + if edge[2] == j + edge[2] = i + end + if edge[1]==edge[2] + delete!(lines, k) + delete!(stack, k) + end + end + + # move shunts + if haskey(shunts, j) + if haskey(shunts, i) + shunts[i] += shunts[j] + else + shunts[i] = shunts[j] + end + end + + # update transformer buses + for w in 1:N + if tr_t_bus[w] == j + tr_t_bus[w] = i + end + end + end + end + + bus_ids = Dict() + for bus in buses + bus_obj = Dict{String,Any}( + "name" => "_virtual_bus.transformer.$(transformer_name)_$(bus)", + "bus_i" => length(data_math["bus"])+1, + "vm" => fill(1.0, nphases), + "va" => fill(0.0, nphases), + "vmin" => fill(0.0, nphases), + "vmax" => fill(Inf, nphases), + "base_kv" => 1.0, + "bus_type" => 1, + "status" => 1, + "index" => length(data_math["bus"])+1, + ) + + data_math["bus"]["$(bus_obj["index"])"] = bus_obj + + bus_ids[bus] = bus_obj["bus_i"] + + push!(to_map, "bus.$(bus_obj["index"])") + end + + + for (l,(i,j)) in lines + # merge the shunts into the shunts of the pi model of the line + g_fr = b_fr = g_to = b_to = 0 + + if haskey(shunts, i) + g_fr = real(shunts[i]) + b_fr = imag(shunts[i]) + delete!(shunts, i) + end + + if haskey(shunts, j) + g_fr = real(shunts[j]) + b_fr = imag(shunts[j]) + delete!(shunts, j) + end + + branch_obj = Dict{String,Any}( + "name" => "_virtual_branch.transformer.$(transformer_name)_$(l)", + "source_id" => "_virtual_branch.transformer.$(transformer_name)_$(l)", + "index" => length(data_math["branch"])+1, + "br_status"=>1, + "f_bus"=>bus_ids[i], + "t_bus"=>bus_ids[j], + "f_connections"=>collect(1:nphases), + "t_connections"=>collect(1:nphases), + "br_r" => diagm(0=>fill(real(z[l]), nphases)), + "br_x" => diagm(0=>fill(imag(z[l]), nphases)), + "g_fr" => diagm(0=>fill(g_fr, nphases)), + "b_fr" => diagm(0=>fill(b_fr, nphases)), + "g_to" => diagm(0=>fill(g_to, nphases)), + "b_to" => diagm(0=>fill(b_to, nphases)), + "angmin" => fill(-60.0, nphases), + "angmax" => fill( 60.0, nphases), + "shift" => zeros(nphases), + "tap" => ones(nphases) + ) + + data_math["branch"]["$(branch_obj["index"])"] = branch_obj + + push!(to_map, "branch.$(branch_obj["index"])") + end + + return [bus_ids[bus] for bus in tr_t_bus] +end + + +"" +function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; neutral::Int=4, kron_reduced::Bool=true) + if kron_reduced + pos = Dict((x,i) for (i,x) in enumerate(phases)) + inds = [pos[x] for x in connections[connections.!=neutral]] + else + # TODO + end + + for property in properties + if haskey(object, property) + if isa(object[property], Vector) + tmp = zeros(length(phases)) + tmp[inds] = object[property] + object[property] = tmp + elseif isa(object[property], Matrix) + tmp = zeros(length(phases), length(phases)) + tmp[inds, inds] = object[property] + object[property] = tmp + end + end + end +end + + +""" +Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the +conductors 't_cnds', this method will return a list of conductors 'cnd' and a +matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. +""" +function calc_shunt(f_cnds::Vector{Int}, t_cnds::Vector{Int}, y::Vector{Float64}) + cnds = unique([f_cnds..., t_cnds...]) + e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) + Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) + return (cnds, Y) +end + + + +""" +Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this +method will calculate the reduced addmittance matrix if terminal 'ground' is +grounded. +""" +function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{Float64}, ground::Int) + # TODO add types + if ground in cnds + cndsr = setdiff(cnds, ground) + cndsr_inds = _get_idxs(cnds, cndsr) + Yr = Y[cndsr_inds, cndsr_inds] + return (cndsr, Yr) + else + return cnds, Y + end +end + + +"" +function _rm_floating_cnd(cnds::Vector{Int}, Y::Matrix{Float64}, f::Int) + P = setdiff(cnds, f) + f_inds = _get_idxs(cnds, [f]) + P_inds = _get_idxs(cnds, P) + Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] + return (P,Yrm) +end + + +"" +function _expand_linecode!(data_model) + # expand line codes + for (id, line) in data_model["line"] + if haskey(line, "linecode") + linecode = data_model["linecode"][line["linecode"]] + for key in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + line[key] = line["length"]*linecode[key] + end + delete!(line, "linecode") + delete!(line, "length") + end + end + delete!(data_model, "linecode") +end + + +"" +function _lossy_ground_to_shunt!(data_model) + mappings = [] + + if haskey(data_model, "bus") + for (id, bus) in data_model["bus"] + grounding_lossy_inds = [i for (i,t) in enumerate(bus["grounded"]) if bus["rg"][i]!=0 || bus["xg"][i]!=0] + grounding_lossy = bus["grounded"][grounding_lossy_inds] + grounding_perfect = bus["grounded"][setdiff(1:length(bus["grounded"]), grounding_lossy_inds)] + + if !isempty(grounding_lossy) + zg = bus["rg"][grounding_lossy_inds].+im*bus["xg"][grounding_lossy_inds] + Y_sh = diagm(0=>inv.(zg)) # diagonal matrix, so matrix inverse is element-wise inverse + add_virtual!(data_model, "shunt", create_shunt(bus=bus["id"], connections=grounding_lossy, + g_sh=real.(Y_sh), b_sh=imag.(Y_sh) + )) + end + end + end + return mappings +end + + +"" +function _load_to_shunt!(data_model) + mappings = [] + if haskey(data_model, "load") + for (id, load) in data_model["load"] + if load["model"]=="constant_impedance" + b = load["qd_ref"]./load["vnom"].^2*1E3 + g = load["pd_ref"]./load["vnom"].^2*1E3 + y = b.+im*g + N = length(b) + + if load["configuration"]=="delta" + # create delta transformation matrix Md + Md = diagm(0=>ones(N), 1=>-ones(N-1)) + Md[N,1] = -1 + Y = Md'*diagm(0=>y)*Md + + else # load["configuration"]=="wye" + Y_fr = diagm(0=>y) + # B = [[b]; -1'*[b]]*[I -1] + Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(diagm(0=>ones(N)), -ones(N)) + end + + shunt = add_virtual!(data_model, "shunt", create_shunt(bus=load["bus"], connections=load["connections"], b_sh=imag.(Y), g_sh=real.(Y))) + + delete_component!(data_model, "load", load) + + push!(mappings, Dict( + "load" => load, + "shunt_id" => shunt["id"], + )) + end + end + end + + return mappings +end + + +"" +function _capacitor_to_shunt!(data_model) + mappings = [] + + if haskey(data_model, "capacitor") + for (id, cap) in data_model["capacitor"] + b = cap["qd_ref"]./cap["vnom"]^2*1E3 + N = length(b) + + if cap["configuration"]=="delta" + # create delta transformation matrix Md + Md = diagm(0=>ones(N), 1=>-ones(N-1)) + Md[N,1] = -1 + B = Md'*diagm(0=>b)*Md + + elseif cap["configuration"]=="wye-grounded" + B = diagm(0=>b) + + elseif cap["configuration"]=="wye-floating" + # this is a floating wye-segment + # B = [b]*(I-1/(b'*1)*[b';...;b']) + B = diagm(0=>b)*(diagm(0=>ones(N)) - 1/sum(b)*repeat(b',N,1)) + + else # cap["configuration"]=="wye" + B_fr = diagm(0=>b) + # B = [[b]; -1'*[b]]*[I -1] + B = vcat(B_fr, -ones(N)'*B_fr)*hcat(diagm(0=>ones(N)), -ones(N)) + end + + shunt = create_shunt(NaN, cap["bus"], cap["connections"], b_sh=B) + add_virtual!(data_model, "shunt", shunt) + delete_component!(data_model, "capacitor", cap) + + push!(mappings, Dict( + "capacitor" => cap, + "shunt_id" => shunt["id"], + )) + end + end + + return mappings +end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index efbf87644..2b7b07e0f 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -224,6 +224,11 @@ function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{St eng_obj = Dict{String,Any}() eng_obj["phases"] = defaults["phases"] + + eng_obj["configuration"] = defaults["conn"] + connections_default = eng_obj["configuration"] == "wye" ? [collect(1:eng_obj["phases"])..., 0] : collect(1:eng_obj["phases"]) + eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=connections_default, check_length=false) + eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] eng_obj["kvar"] = defaults["kvar"] eng_obj["status"] = convert(Int, defaults["enabled"]) @@ -254,6 +259,7 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String eng_obj = Dict{String,Any}() eng_obj["phases"] = defaults["phases"] + eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], check_length=false) eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] @@ -372,8 +378,8 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An end if any(haskey(dss_obj, key) for key in ["b0", "b1", "c0", "c1", "cmatrix"]) || !haskey(dss_obj, "linecode") - eng_obj["b_fr"] = defaults["cmatrix"] ./ 2.0 - eng_obj["b_to"] = defaults["cmatrix"] ./ 2.0 + eng_obj["b_fr"] = reshape(defaults["cmatrix"], nphases, nphases) ./ 2.0 + eng_obj["b_to"] = reshape(defaults["cmatrix"], nphases, nphases) ./ 2.0 eng_obj["g_fr"] = fill(0.0, nphases, nphases) eng_obj["g_to"] = fill(0.0, nphases, nphases) end @@ -589,6 +595,7 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] eng_obj["phases"] = defaults["phases"] + eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], check_length=false) eng_obj["kva"] = defaults["kva"] eng_obj["kv"] = defaults["kv"] @@ -627,6 +634,7 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< eng_obj = Dict{String,Any}() eng_obj["phases"] = defaults["phases"] + eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], check_length=false) eng_obj["name"] = name eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] diff --git a/test/opf.jl b/test/opf.jl index 665b9ee30..3bfccda8b 100644 --- a/test/opf.jl +++ b/test/opf.jl @@ -80,7 +80,7 @@ @test isapprox(result["objective"], 0.0182595; atol=1e-4) @test all(isapprox.(result["solution"]["gen"]["1"]["pg"], [5.06513e-5, 6.0865e-5, 7.1119e-5]; atol=1e-7)) - @test all(isapprox.(result["solution"]["bus"]["2"]["vm"], [0.990023, 1.000000, 1.000000]; atol=1e-4)) + @test isapprox(result["solution"]["bus"]["4"]["vm"][1], 0.98995; atol=1.5e-4) end @testset "4-bus phase drop acr opf" begin @@ -92,7 +92,7 @@ calc_vm(id) = sqrt.(result["solution"]["bus"][id]["vr"].^2+result["solution"]["bus"][id]["vi"].^2) @test all(isapprox.(result["solution"]["gen"]["1"]["pg"], [5.06513e-5, 6.0865e-5, 7.1119e-5]; atol=1e-7)) - @test isapprox(calc_vm("2")[1], 0.98995; atol=1.5e-4) + @test isapprox(calc_vm("4")[1], 0.98995; atol=1.5e-4) end @testset "5-bus phase drop acp opf" begin @@ -103,7 +103,7 @@ @test isapprox(result["objective"], 0.0599389; atol=1e-4) @test all(isapprox.(result["solution"]["gen"]["1"]["pg"], [0.00015236280779412599, 0.00019836795302238667, 0.0002486642034746673]; atol=1e-7)) - @test all(isapprox.(result["solution"]["bus"]["2"]["vm"], [0.97351, 0.96490, 0.95646]; atol=1e-4)) + @test all(isapprox.(result["solution"]["bus"]["3"]["vm"], [0.97351, 0.96490, 0.95646]; atol=1e-4)) end @testset "5-bus phase drop acr opf" begin @@ -115,7 +115,7 @@ calc_vm(id) = sqrt.(result["solution"]["bus"][id]["vr"].^2+result["solution"]["bus"][id]["vi"].^2) @test all(isapprox.(result["solution"]["gen"]["1"]["pg"], [0.00015236280779412599, 0.00019836795302238667, 0.0002486688793741932]; atol=1e-7)) - @test all(isapprox.(calc_vm("2"), [0.9735188343958152, 0.9649003198689144, 0.9564593296045091]; atol=1e-4)) + @test all(isapprox.(calc_vm("3"), [0.9735188343958152, 0.9649003198689144, 0.9564593296045091]; atol=1e-4)) end @testset "5-bus phase drop dcp opf" begin @@ -123,7 +123,7 @@ result = run_mc_opf(mp_data, PMs.DCPPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(result["objective"], 0.0540021; atol=1e-4) + @test isapprox(result["objective"], 0.0544220; atol=1e-4) end @testset "5-bus phase drop nfa opf" begin @@ -142,8 +142,8 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test all(isapprox.(sol["solution"]["bus"]["2"]["vm"], 0.984377; atol=1e-4)) - @test all(isapprox.(sol["solution"]["bus"]["2"]["va"], PMD._wrap_to_pi.([2 * pi / pmd["conductors"] * (1 - c) - deg2rad(0.79) for c in 1:pmd["conductors"]]); atol=deg2rad(0.2))) + @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], 0.984377; atol=1e-4)) + @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], PMD._wrap_to_pi.([2 * pi / pmd["conductors"] * (1 - c) - deg2rad(0.79) for c in 1:pmd["conductors"]]); atol=deg2rad(0.2))) @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0181409; atol=1e-5) @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.0; atol=1e-4) @@ -155,7 +155,7 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - for (bus, va, vm) in zip(["1", "2", "3"], [0.0, deg2rad(-0.03), deg2rad(-0.07)], [0.9959, 0.986973, 0.976605]) + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.03), deg2rad(-0.07)], [0.9959, 0.986973, 0.976605]) @test all(isapprox.(sol["solution"]["bus"][bus]["va"], PMD._wrap_to_pi.([2 * pi / pmd["conductors"] * (1 - c) + va for c in 1:pmd["conductors"]]); atol=deg2rad(0.01))) @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-4)) end @@ -170,7 +170,7 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - for (bus, va, vm) in zip(["1", "2", "3"], + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], [0.9959, [0.980937, 0.98936, 0.987039], [0.963546, 0.981757, 0.976779]]) @test all(isapprox.(sol["solution"]["bus"][bus]["va"], PMD._wrap_to_pi.([2 * pi / pmd["conductors"] * (1 - c) for c in 1:pmd["conductors"]]) .+ va; atol=deg2rad(0.01))) @@ -193,17 +193,17 @@ pmd = PMD.parse_file("../test/data/opendss/case3_balanced_pv.dss") @test length(pmd["gen"]) == 2 - @test all(pmd["gen"]["2"]["qmin"] .== -pmd["gen"]["2"]["qmax"]) - @test all(pmd["gen"]["2"]["pmax"] .== pmd["gen"]["2"]["qmax"]) - @test all(pmd["gen"]["2"]["pmin"] .== 0.0) + @test all(pmd["gen"]["1"]["qmin"] .== -pmd["gen"]["1"]["qmax"]) + @test all(pmd["gen"]["1"]["pmax"] .== pmd["gen"]["1"]["qmax"]) + @test all(pmd["gen"]["1"]["pmin"] .== 0.0) sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]) < 0.0 - @test sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]) < 0.0 - @test isapprox(sum(sol["solution"]["gen"]["2"]["pg"] * sol["solution"]["baseMVA"]), 0.0183685; atol=1e-4) - @test isapprox(sum(sol["solution"]["gen"]["2"]["qg"] * sol["solution"]["baseMVA"]), 0.00919404; atol=1e-4) + @test sum(sol["solution"]["gen"]["2"]["pg"] * sol["solution"]["baseMVA"]) < 0.0 + @test sum(sol["solution"]["gen"]["2"]["qg"] * sol["solution"]["baseMVA"]) < 0.0 + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183685; atol=1e-4) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00919404; atol=1e-4) end @testset "3-bus unbalanced single-phase pv acp opf" begin @@ -212,11 +212,11 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0196116; atol=1e-3) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923107; atol=1e-3) + @test isapprox(sum(sol["solution"]["gen"]["2"]["pg"] * sol["solution"]["baseMVA"]), 0.01838728; atol=1e-3) + @test isapprox(sum(sol["solution"]["gen"]["2"]["qg"] * sol["solution"]["baseMVA"]), 0.00756634; atol=1e-3) - @test all(sol["solution"]["gen"]["2"]["pg"][2:3] .== 0.0) - @test all(sol["solution"]["gen"]["2"]["qg"][2:3] .== 0.0) + @test all(sol["solution"]["gen"]["1"]["pg"][2:3] .== 0.0) + @test all(sol["solution"]["gen"]["1"]["qg"][2:3] .== 0.0) end @testset "3-bus balanced capacitor acp opf" begin @@ -226,7 +226,7 @@ @test sol["termination_status"] == PMs.LOCALLY_SOLVED @test all(abs(sol["solution"]["bus"]["3"]["vm"][c]-0.98588)<=1E-4 for c in 1:3) - @test all(abs(sol["solution"]["bus"]["2"]["vm"][c]-0.99127)<=1E-4 for c in 1:3) + @test all(abs(sol["solution"]["bus"]["1"]["vm"][c]-0.99127)<=1E-4 for c in 1:3) end @testset "3w transformer nfa opf" begin diff --git a/test/runtests.jl b/test/runtests.jl index ff2094b3b..446165790 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,9 +41,9 @@ include("common.jl") include("pf.jl") # all passing - # include("pf_iv.jl") + include("pf_iv.jl") # all passing - # include("opf.jl") + include("opf.jl") # all passing # include("opf_bf.jl") From 056091a232d7fe703d1aaa9a0cba52ca85b471f5 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 5 Mar 2020 10:09:08 -0700 Subject: [PATCH 063/224] Enable all opf tests --- test/runtests.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 446165790..7844b66f1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -45,9 +45,9 @@ include("common.jl") include("opf.jl") # all passing - # include("opf_bf.jl") + include("opf_bf.jl") # all passing - # include("opf_iv.jl") + include("opf_iv.jl") # all passing # include("storage.jl") From 0c5e3ee9f4a63c597315d270e54c30c85bfd5185 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 5 Mar 2020 10:11:55 -0700 Subject: [PATCH 064/224] Enable storage tests --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 7844b66f1..ecfa4ac1b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -49,7 +49,7 @@ include("common.jl") include("opf_iv.jl") # all passing - # include("storage.jl") + include("storage.jl") # all passing # include("debug.jl") From 191152e68cf7a04eab2079d36a7a849f4f13429d Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 5 Mar 2020 10:14:50 -0700 Subject: [PATCH 065/224] Enable debug tests --- test/runtests.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index ecfa4ac1b..38d6f1e3b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -49,9 +49,9 @@ include("common.jl") include("opf_iv.jl") # all passing - include("storage.jl") # all passing + include("storage.jl") # all passing - # include("debug.jl") + include("debug.jl") # all passing # include("multinetwork.jl") From cceecb1d0a39e90d71bdaa45c5ff96143af680c4 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 5 Mar 2020 10:21:33 -0700 Subject: [PATCH 066/224] Enable MLD tests only two transformer tests failing, to fix --- test/mld.jl | 28 ++++++++++++++-------------- test/runtests.jl | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/test/mld.jl b/test/mld.jl index 6d3b0133a..ad69dd391 100644 --- a/test/mld.jl +++ b/test/mld.jl @@ -73,14 +73,14 @@ @test isapprox(result["solution"]["load"]["1"]["status"], 0.544; atol = 1e-3) end - @testset "transformer nfa mld" begin - mp_data = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") - result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) + # @testset "transformer nfa mld" begin + # mp_data = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + # result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(result["objective"], 0.411, atol = 1e-3) - @test isapprox(result["solution"]["load"]["1"]["status"], 1.0, atol = 1e-3) - end + # @test result["termination_status"] == PMs.LOCALLY_SOLVED + # @test isapprox(result["objective"], 0.411, atol = 1e-3) + # @test isapprox(result["solution"]["load"]["1"]["status"], 1.0, atol = 1e-3) + # end @testset "5-bus lpubfdiag mld" begin mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5.m"); PMD.make_multiconductor!(mp_data, 3) @@ -104,14 +104,14 @@ @test isapprox(result["solution"]["load"]["1"]["status"], 0.313; atol = 1e-3) end - @testset "transformer case" begin - dss = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") - result = run_mc_mld_bf(dss, LPUBFDiagPowerModel, ipopt_solver) + # @testset "transformer case" begin + # dss = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + # result = run_mc_mld_bf(dss, LPUBFDiagPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(result["objective"], 0.0; atol=1e-3) - @test isapprox(result["solution"]["load"]["1"]["status"], 1.0; atol=1e-3) - end + # @test result["termination_status"] == PMs.LOCALLY_SOLVED + # @test isapprox(result["objective"], 0.0; atol=1e-3) + # @test isapprox(result["solution"]["load"]["1"]["status"], 1.0; atol=1e-3) + # end @testset "5-bus acp mld_uc" begin mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5.m"); PMD.make_multiconductor!(mp_data, 3) diff --git a/test/runtests.jl b/test/runtests.jl index 38d6f1e3b..9b5fa9a89 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -53,7 +53,7 @@ include("common.jl") include("debug.jl") # all passing - # include("multinetwork.jl") + include("multinetwork.jl") # all passing # include("transformer.jl") @@ -63,5 +63,5 @@ include("common.jl") # include("shunt.jl") - # include("mld.jl") + include("mld.jl") # only transformer tests failing end From 34071d4d340622a1f894953ae21e026318f82b4f Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 5 Mar 2020 10:24:28 -0700 Subject: [PATCH 067/224] Enable shunt tests --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 9b5fa9a89..7f61ad125 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -61,7 +61,7 @@ include("common.jl") # include("delta_gens.jl") - # include("shunt.jl") + include("shunt.jl") include("mld.jl") # only transformer tests failing end From 80ec57a68c7c3623b60cf393ce193a2ca29c2098 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 5 Mar 2020 14:53:51 -0700 Subject: [PATCH 068/224] FIX: delta gens tests changes conn -> configuration internally --- src/core/constraint_template.jl | 4 ++-- src/core/data.jl | 4 ++-- src/core/ref.jl | 2 +- src/data_model/eng2math.jl | 22 ++++++++++++---------- src/form/bf_mx.jl | 18 +++++++++--------- src/io/opendss.jl | 9 ++------- src/io/utils.jl | 10 +++++----- test/delta_gens.jl | 4 ++-- test/runtests.jl | 4 ++-- 9 files changed, 37 insertions(+), 40 deletions(-) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index c41a5a58f..dc6d900a8 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -275,7 +275,7 @@ function constraint_mc_load(pm::_PMs.AbstractPowerModel, id::Int; nw::Int=pm.cnw load = _PMs.ref(pm, nw, :load, id) bus = _PMs.ref(pm, nw,:bus, load["load_bus"]) - conn = haskey(load, "conn") ? load["conn"] : "wye" + conn = haskey(load, "configuration") ? load["configuration"] : "wye" a, alpha, b, beta = _load_expmodel_params(load, bus) @@ -309,7 +309,7 @@ function constraint_mc_generation(pm::_PMs.AbstractPowerModel, id::Int; nw::Int= qmin = get(generator, "qmin", fill(-Inf, N)) qmax = get(generator, "qmax", fill( Inf, N)) - if generator["conn"]=="wye" + if get(generator, "configuration", "wye") == "wye" constraint_mc_generation_wye(pm, nw, id, bus["index"], pmin, pmax, qmin, qmax; report=report, bounded=bounded) else constraint_mc_generation_delta(pm, nw, id, bus["index"], pmin, pmax, qmin, qmax; report=report, bounded=bounded) diff --git a/src/core/data.jl b/src/core/data.jl index 1b992a8f8..c3a3513ef 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -493,11 +493,11 @@ function _make_multiconductor!(data::Dict{String,<:Any}, conductors::Real) for (_, load) in data["load"] load["model"] = "constant_power" - load["conn"] = "wye" + load["configuration"] = "wye" end for (_, load) in data["gen"] - load["conn"] = "wye" + load["configuration"] = "wye" end end diff --git a/src/core/ref.jl b/src/core/ref.jl index c8ad91a6c..7a7a2e324 100644 --- a/src/core/ref.jl +++ b/src/core/ref.jl @@ -98,7 +98,7 @@ function _calc_mc_transformer_Tvi(pm::_PMs.AbstractPowerModel, i::Int; nw=pm.cnw Memento.error(_LOGGER, "The winding type should be either delta or wye, but got \'$dyz\'.") end # for now, grounded by default - #grounded = length(trans["conn"])>5 && trans["conn"][6]=='n' + #grounded = length(trans["configuration"])>5 && trans["configuration"][6]=='n' # Tw will contain transformations related to permutation and polarity perm_to_trans = Dict( [1,2,3]=>diagm(0=>ones(Float64, 3)), diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index b3f9b2570..81988a196 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -6,7 +6,7 @@ const _1to1_maps = Dict{String,Vector{String}}( "load" => ["model", "configuration", "status", "source_id"], "capacitor" => ["status"], "shunt_reactor" => ["status", "source_id"], - "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], + "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id", "configuration"], "pvsystem" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], "storage" => ["status", "source_id"], "line" => ["source_id"], @@ -185,17 +185,19 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any # TODO add delta loads for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("load", eng_obj, length(data_math["load"])+1) + math_obj["name"] = name - phases = get(eng_obj, "phases", [1, 2, 3]) - conductors = collect(1:data_math["conductors"]) + connections = eng_obj["connections"] + neutral = data_eng["bus"][eng_obj["bus"]]["neutral"] + phases = connections[connections.!=neutral] + nconductors = data_math["conductors"] if eng_obj["configuration"] == "wye" bus = data_eng["bus"][eng_obj["bus"]] # TODO add message for failure @assert length(bus["grounded"]) == 1 && bus["grounded"][1] == eng_obj["connections"][end] else - # TODO add message for failure - @assert all(load["connections"] .== phases) + # @assert all(eng_obj["connections"] .== phases) end math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] @@ -203,9 +205,9 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["pd"] = eng_obj["pd"] / 1e3 math_obj["qd"] = eng_obj["qd"] / 1e3 - math_obj["vnom_kv"] = get(eng_obj, "vnom", data_eng["settings"]["set_vbase_val"]/sqrt(3)*1e3) + _pad_properties!(math_obj, ["pd", "qd"], connections, collect(1:nconductors); neutral=neutral) - _pad_properties!(math_obj, ["pd", "qd"], eng_obj["connections"], phases; neutral=data_eng["bus"][eng_obj["bus"]]["neutral"]) + math_obj["vnom_kv"] = eng_obj["vnom"] data_math["load"]["$(math_obj["index"])"] = math_obj @@ -335,7 +337,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["pmax"] = fill(eng_obj["kw"] / 1e3 / phases, phases) math_obj["pmin"] = fill(0.0, phases) - math_obj["conn"] = eng_obj["configuration"] + math_obj["configuration"] = eng_obj["configuration"] _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, collect(1:nconductors)) @@ -380,7 +382,7 @@ function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{< math_obj["qmin"] = fill(-eng_obj["kva"] / 1e3 / phases, phases) math_obj["qmax"] = fill( eng_obj["kva"] / 1e3 / phases, phases) - math_obj["conn"] = eng_obj["configuration"] + math_obj["configuration"] = eng_obj["configuration"] _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, collect(1:nconductors)) @@ -718,7 +720,7 @@ function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{ "shutdown" => 0.0, "ncost" => 3, "cost" => [0.0, 1.0, 0.0], - "conn" => "wye", # TODO change name to configuration + "configuration" => "wye", # TODO change name to configuration "index" => length(data_math["gen"]) + 1, "source_id" => "vsource._virtual_sourcebus" ) diff --git a/src/form/bf_mx.jl b/src/form/bf_mx.jl index 5ab758489..23b00e137 100644 --- a/src/form/bf_mx.jl +++ b/src/form/bf_mx.jl @@ -279,8 +279,8 @@ constant power or constant impedance. In all other cases (e.g. when a cone is used to constrain the power), variables need to be created. """ function variable_mc_load(pm::AbstractUBFModels; nw=pm.cnw) - load_wye_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["conn"]=="wye"] - load_del_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["conn"]=="delta"] + load_wye_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["configuration"]=="wye"] + load_del_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["configuration"]=="delta"] load_cone_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if _check_load_needs_cone(load)] # create dictionaries _PMs.var(pm, nw)[:pd] = Dict() @@ -310,8 +310,8 @@ all other load model variables are then linear transformations of these (linear Expressions). """ function variable_mc_load(pm::SDPUBFKCLMXModel; nw=pm.cnw) - load_wye_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["conn"]=="wye"] - load_del_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["conn"]=="delta"] + load_wye_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["configuration"]=="wye"] + load_del_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["configuration"]=="delta"] load_cone_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if _check_load_needs_cone(load)] # create dictionaries _PMs.var(pm, nw)[:Pd] = Dict{Int, Any}() @@ -384,7 +384,7 @@ function variable_mc_load_power_bus(pm::SDPUBFKCLMXModel, load_ids::Array{Int,1} bound = Dict{eltype(load_ids), Array{Real,2}}() for id in load_ids load = _PMs.ref(pm, nw, :load, id) - @assert(load["conn"]=="wye") + @assert(load["configuration"]=="wye") bus = _PMs.ref(pm, nw, :bus, load["load_bus"]) cmax = _calc_load_current_max(load, bus) bound[id] = bus["vmax"]*cmax' @@ -578,7 +578,7 @@ function constraint_mc_load(pm::AbstractUBFModels, load_id::Int; nw::Int=pm.cnw) pmin, pmax, qmin, qmax = _calc_load_pq_bounds(load, bus) # take care of connections - if load["conn"]=="wye" + if load["configuration"]=="wye" if load["model"]=="constant_power" _PMs.var(pm, nw, :pl)[load_id] = pd0 _PMs.var(pm, nw, :ql)[load_id] = qd0 @@ -598,7 +598,7 @@ function constraint_mc_load(pm::AbstractUBFModels, load_id::Int; nw::Int=pm.cnw) # :pd is identical to :pl now _PMs.var(pm, nw, :pd)[load_id] = _PMs.var(pm, nw, :pl)[load_id] _PMs.var(pm, nw, :qd)[load_id] = _PMs.var(pm, nw, :ql)[load_id] - elseif load["conn"]=="delta" + elseif load["configuration"]=="delta" # link Wy, CCd and X Wr = _PMs.var(pm, nw, :Wr, bus_id) Wi = _PMs.var(pm, nw, :Wi, bus_id) @@ -667,7 +667,7 @@ function constraint_mc_load(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int=pm.cnw) CCdr = _PMs.var(pm, nw, :CCdr, load_id) CCdi = _PMs.var(pm, nw, :CCdi, load_id) - if load["conn"]=="wye" + if load["configuration"]=="wye" if load["model"]=="constant_power" _PMs.var(pm, nw, :pl)[load_id] = pd0 _PMs.var(pm, nw, :ql)[load_id] = qd0 @@ -694,7 +694,7 @@ function constraint_mc_load(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int=pm.cnw) Qd[c,c] = _PMs.var(pm, nw, :ql)[load_id][c] end - elseif load["conn"]=="delta" + elseif load["configuration"]=="delta" # link Wy, CCd and X Xdr = _PMs.var(pm, nw, :Xdr, load_id) Xdi = _PMs.var(pm, nw, :Xdi, load_id) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 2b7b07e0f..df6f85377 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -132,13 +132,14 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An kv = kv/sqrt(3) end + eng_obj["vnom"] = kv + if model=="constant_power" eng_obj["pd"] = fill(defaults["kw"]/nphases, nphases) eng_obj["qd"] = fill(defaults["kvar"]/nphases, nphases) else eng_obj["pd_ref"] = fill(defaults["kw"]/nphases, nphases) eng_obj["qd_ref"] = fill(defaults["kvar"]/nphases, nphases) - eng_obj["vnom"] = kv end eng_obj["status"] = convert(Int, defaults["enabled"]) @@ -248,8 +249,6 @@ function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{St end - - "Adds generators to `data_eng` from `data_dss`" function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "generator", Dict{String,Any}()) @@ -708,10 +707,6 @@ function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String end - - - - "Parses a DSS file into a PowerModels usable format" function parse_opendss_dm(io::IOStream; import_all::Bool=false, bank_transformers::Bool=true)::Dict data_dss = parse_dss(io) diff --git a/src/io/utils.jl b/src/io/utils.jl index cc0dfab92..9e00f9e6c 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -302,10 +302,10 @@ end function _discover_terminals!(data_eng::Dict{String,<:Any}) terminals = Dict{String, Set{Int}}([(name, Set{Int}()) for (name,bus) in data_eng["bus"]]) - for (_,dss_obj) in data_eng["line"] + for (_,eng_obj) in data_eng["line"] # ignore 0 terminal - push!(terminals[dss_obj["f_bus"]], setdiff(dss_obj["f_connections"], [0])...) - push!(terminals[dss_obj["t_bus"]], setdiff(dss_obj["t_connections"], [0])...) + push!(terminals[eng_obj["f_bus"]], setdiff(eng_obj["f_connections"], [0])...) + push!(terminals[eng_obj["t_bus"]], setdiff(eng_obj["t_connections"], [0])...) end if haskey(data_eng, "transformer") @@ -363,7 +363,7 @@ end function _find_neutrals(data_eng::Dict{String,<:Any}) vertices = [(id, t) for (id, bus) in data_eng["bus"] for t in bus["terminals"]] neutrals = [] - edges = Set([((dss_obj["f_bus"], dss_obj["f_connections"][c]),(dss_obj["t_bus"], dss_obj["t_connections"][c])) for (id, dss_obj) in data_eng["line"] for c in 1:length(dss_obj["f_connections"])]) + edges = Set([((eng_obj["f_bus"], eng_obj["f_connections"][c]),(eng_obj["t_bus"], eng_obj["t_connections"][c])) for (id, eng_obj) in data_eng["line"] for c in 1:length(eng_obj["f_connections"])]) bus_neutrals = [(id,bus["neutral"]) for (id,bus) in data_eng["bus"] if haskey(bus, "neutral")] trans_neutrals = [] @@ -374,7 +374,7 @@ function _find_neutrals(data_eng::Dict{String,<:Any}) end end end - load_neutrals = [(dss_obj["bus"],dss_obj["connections"][end]) for (_,dss_obj) in get(data_eng, "load", Dict{String,Any}()) if dss_obj["configuration"]=="wye"] + load_neutrals = [(eng_obj["bus"],eng_obj["connections"][end]) for (_,eng_obj) in get(data_eng, "load", Dict{String,Any}()) if eng_obj["configuration"]=="wye"] neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) stack = deepcopy(neutrals) diff --git a/test/delta_gens.jl b/test/delta_gens.jl index 31a1ffa47..e97914321 100644 --- a/test/delta_gens.jl +++ b/test/delta_gens.jl @@ -21,7 +21,7 @@ gen["index"] = i+1 gen["cost"] *= 0 - gen["conn"] = load["conn"] + gen["configuration"] = load["configuration"] gen["pmax"] = gen["pmin"] = -load["pd"] gen["qmin"] = gen["qmax"] = -load["qd"] gen["gen_bus"] = load["load_bus"] @@ -68,7 +68,7 @@ # # gen["index"] = i+1 # gen["cost"] *= 0.01 - # gen["conn"] = load["conn"] + # gen["configuration"] = load["configuration"] # gen["pmax"] = (-load["pd"])/10 # gen["pmin"] *= 0 # gen["qmin"] = -abs.(gen["pmax"])/10 diff --git a/test/runtests.jl b/test/runtests.jl index 7f61ad125..246c6c2b9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -59,9 +59,9 @@ include("common.jl") # include("loadmodels.jl") - # include("delta_gens.jl") + include("delta_gens.jl") # all passing - include("shunt.jl") + include("shunt.jl") # all passing include("mld.jl") # only transformer tests failing end From 3b764608d715fde9ee5b0a970b616e8f51ef293c Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 5 Mar 2020 15:36:51 -0700 Subject: [PATCH 069/224] Misc fixes to parser --- src/data_model/math2eng.jl | 2 +- src/io/common.jl | 4 ++-- src/io/dss_parse.jl | 2 +- src/io/opendss.jl | 27 +++++++++++++-------------- src/io/utils.jl | 2 -- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index c270fd143..169a98b02 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -5,7 +5,7 @@ function _map_math2eng!(data_math) data_eng = Dict{<:Any,<:Any}() - map_keys = sort(keys(data_math["map"]); reverse=true) + map_keys = sort(keys(data_math["map"]); rev=true) for map in map_keys # TODO end diff --git a/src/io/common.jl b/src/io/common.jl index 5d06ecd04..a547efd49 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -41,8 +41,8 @@ function transform_data_model(data::Dict{<:Any,<:Any}) if current_data_model == "engineering" return _map_eng2math(data) - elseif current_data_model == "mathematical" - return _map_math2eng!(data) + # elseif current_data_model == "mathematical" + # return _map_math2eng!(data) else @warn "Data model '$current_data_model' is not recognized, no transformation performed" return data diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 00ed3a4d1..399263e7c 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -584,7 +584,7 @@ OpenDSS commands inside the originating file. """ function _merge_dss!(dss_prime::Dict{String,<:Any}, dss_to_add::Dict{String,<:Any}) for (k, v) in dss_to_add - if k in keys(dss_prime) + if k in keys(dss_prime) && isa(v, Array) append!(dss_prime[k], v) else dss_prime[k] = v diff --git a/src/io/opendss.jl b/src/io/opendss.jl index df6f85377..1090a525a 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -116,7 +116,15 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An # now we can create the load; if you do not have the correct model, # pd/qd fields will be populated by default (should not happen for constant current/impedance) - eng_obj = create_load(model=model, connections=connections, bus=bus, configuration=conf) + eng_obj = Dict{String,Any}( + "name" => name, + "bus" => bus, + "model" => model, + "configuration" => conf, + "connections" => connections, + "source_id" => "load.$name", + "status" => convert(Int, defaults["enabled"]) + ) # if the ground is used directly, register load if 0 in connections @@ -134,16 +142,8 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["vnom"] = kv - if model=="constant_power" - eng_obj["pd"] = fill(defaults["kw"]/nphases, nphases) - eng_obj["qd"] = fill(defaults["kvar"]/nphases, nphases) - else - eng_obj["pd_ref"] = fill(defaults["kw"]/nphases, nphases) - eng_obj["qd_ref"] = fill(defaults["kvar"]/nphases, nphases) - end - - eng_obj["status"] = convert(Int, defaults["enabled"]) - eng_obj["source_id"] = "load.$name" + eng_obj["pd"] = fill(defaults["kw"]/nphases, nphases) + eng_obj["qd"] = fill(defaults["kvar"]/nphases, nphases) if import_all _import_all!(eng_obj, defaults, dss_obj["prop_order"]) @@ -322,7 +322,7 @@ function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj = Dict{String,Any}() - nphases = size(defaults["rmatrix"])[1] + nphases = defaults["nphases"] eng_obj["rs"] = reshape(defaults["rmatrix"], nphases, nphases) eng_obj["xs"] = reshape(defaults["xmatrix"], nphases, nphases) @@ -360,8 +360,7 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["length"] = defaults["length"] - # TODO nphases not being read correctly from linecode; infer indirectly instead - nphases = size(defaults["rmatrix"])[1] + nphases = defaults["phases"] eng_obj["n_conductors"] = nphases if haskey(dss_obj, "linecode") diff --git a/src/io/utils.jl b/src/io/utils.jl index 9e00f9e6c..20a5be59f 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -686,8 +686,6 @@ function _parse_obj_dtypes!(obj_type, object, dtypes) object[k] = arrout elseif isa(v, AbstractString) object[k] = _parse_element_with_dtype(dtypes[k], v) - else - Memento.error(_LOGGER, "dtype unknown $obj_type, $k, $v") end end end From b4f3fd61e2959e8dda44e2f3808f6edccc84cd19 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Sun, 8 Mar 2020 19:53:36 +1100 Subject: [PATCH 070/224] pu conversion added --- src/core/data.jl | 2 +- src/data_model/eng2math.jl | 231 ++++++++++++++++++++++++++++--------- src/data_model/units.jl | 82 +++++++------ src/data_model/utils.jl | 66 +++++++++++ src/io/common.jl | 17 ++- src/io/opendss.jl | 123 +++++++++++++++++--- src/io/utils.jl | 109 ++++++++++++----- 7 files changed, 486 insertions(+), 144 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index c3a3513ef..93b9aca1e 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -101,7 +101,7 @@ function count_nodes(data::Dict{String,<:Any})::Int n_nodes += sum(bus["vm"] .> 0.0) elseif get(data, "source_type", "none") == "dss" if !(data["source_type"] == "dss" && bus["name"] in ["_virtual_sourcebus", data["sourcebus"]]) && !(data["source_type"] == "dss" && startswith(bus["name"], "tr") && endswith(bus["name"], r"_b\d")) - n_nodes += sum(bus["vm"] .> 0.0) + n_nodes += sum(bus["vmax"] .> 0.0) end end end diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 81988a196..8c9f1b320 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -5,6 +5,7 @@ const _1to1_maps = Dict{String,Vector{String}}( "bus" => ["vm", "va", "vmin", "vmax"], "load" => ["model", "configuration", "status", "source_id"], "capacitor" => ["status"], + "shunt" => ["status"], "shunt_reactor" => ["status", "source_id"], "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id", "configuration"], "pvsystem" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], @@ -21,6 +22,7 @@ const _extra_eng_data = Dict{String,Vector{String}}( "bus" => ["grounded", "neutral", "awaiting_ground", "xg", "phases", "rg", "terminals"], "load" => [], "capacitor" => [], + "shunt" => [], "shunt_reactor" => [], "generator" => ["control_model"], "pvsystem" => [], @@ -50,7 +52,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) data_math["conductors"] = kron_reduced ? 3 : 4 data_math["basekv"] = data_eng["settings"]["set_vbase_val"] - data_math["baseMVA"] = data_eng["settings"]["set_sbase_val"] + data_math["baseMVA"] = data_eng["settings"]["set_sbase_val"]*data_eng["settings"]["v_var_scalar"]/1E6 data_math["data_model"] = "mathematical" @@ -69,6 +71,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_load!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) @@ -126,7 +129,7 @@ end "" -function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) # TODO fix vnom phases = get(eng_obj, "phases", [1, 2, 3]) @@ -143,8 +146,12 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, math_obj["bus_i"] = math_obj["index"] math_obj["bus_type"] = eng_obj["status"] == 1 ? 1 : 4 - math_obj["vm"] = get(eng_obj, "vm", fill(1.0, length(terminals))) - math_obj["va"] = get(eng_obj, "va", [_wrap_to_180(-rad2deg(2*pi/length(phases)*(i-1))) for i in terminals]) + if haskey(eng_obj, "vm") + math_obj["vm"] = eng_obj["vm"] + end + if haskey(eng_obj, "va") + math_obj["va"] = deg2rad(eng_obj["va"]) + end math_obj["vmin"] = fill(0.0, length(terminals)) math_obj["vmax"] = fill(Inf, length(terminals)) @@ -152,14 +159,13 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, math_obj["base_kv"] = data_eng["settings"]["set_vbase_val"] if kron_reduced - for key in ["vm", "va", "vmin", "vmax"] - if haskey(math_obj, key) - math_obj[key] = math_obj[key][terminals.!=neutral] - end - end - end + filter = terminals.!=kr_neutral + terminals_kr = terminals[filter] + @assert(all(t in kr_phases for t in terminals_kr)) - _pad_properties!(math_obj, ["vm", "va", "vmin", "vmax"], terminals, collect(1:nconductors); neutral=neutral) + _apply_filter!(math_obj, ["vm", "va", "vmin", "vmax"], filter) + _pad_properties!(math_obj, ["vm", "va", "vmin", "vmax"], terminals_kr, kr_phases) + end data_math["bus"]["$(math_obj["index"])"] = math_obj @@ -181,31 +187,32 @@ end "" -function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) # TODO add delta loads for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("load", eng_obj, length(data_math["load"])+1) math_obj["name"] = name connections = eng_obj["connections"] - neutral = data_eng["bus"][eng_obj["bus"]]["neutral"] - phases = connections[connections.!=neutral] nconductors = data_math["conductors"] - if eng_obj["configuration"] == "wye" - bus = data_eng["bus"][eng_obj["bus"]] - # TODO add message for failure - @assert length(bus["grounded"]) == 1 && bus["grounded"][1] == eng_obj["connections"][end] - else - # @assert all(eng_obj["connections"] .== phases) - end - math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["pd"] = eng_obj["pd"] / 1e3 - math_obj["qd"] = eng_obj["qd"] / 1e3 + math_obj["pd"] = eng_obj["pd"] + math_obj["qd"] = eng_obj["qd"] - _pad_properties!(math_obj, ["pd", "qd"], connections, collect(1:nconductors); neutral=neutral) + math_obj["configuration"] = eng_obj["configuration"] + + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pd", "qd"], connections[1:end-1], kr_phases) + else + _pad_properties_delta!(math_obj, ["pd", "qd"], connections, kr_phases) + end + else + math_obj["connections"] = connections + end math_obj["vnom_kv"] = eng_obj["vnom"] @@ -223,7 +230,7 @@ end "" -function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) for (name, eng_obj) in get(data_eng, "capacitor", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("capacitor", eng_obj, length(data_math["shunt"])+1) @@ -264,9 +271,18 @@ function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["gs"] = fill(0.0, nphases, nphases) math_obj["bs"] = B - neutral = get(data_eng["bus"][eng_obj["bus"]], "neutral", 4) + # neutral = get(data_eng["bus"][eng_obj["bus"]], "neutral", 4) - _pad_properties!(math_obj, ["gs", "bs"], eng_obj["f_terminals"], collect(1:data_math["conductors"]); neutral=neutral) + if kron_reduced + filter = _kron_reduce_branch!(math_obj, + [], ["gs", "bs"], + eng_obj["connections"], kr_neutral + ) + connections = eng_obj["f_connections"][filter] + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + else + math_obj["connections"] = eng_obj["connections"] + end data_math["shunt"]["$(math_obj["index"])"] = math_obj @@ -281,6 +297,46 @@ function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{ end +"" +function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) + for (name, eng_obj) in get(data_eng, "shunt", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt", eng_obj, length(data_math["shunt"])+1) + + # TODO change to new capacitor shunt calc logic + math_obj["name"] = name + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + + Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) + Zbase = 1 + + + math_obj["gs"] = eng_obj["g_sh"]*Zbase + math_obj["bs"] = eng_obj["b_sh"]*Zbase + + if kron_reduced + filter = _kron_reduce_branch!(math_obj, + [], ["gs", "bs"], + eng_obj["connections"], kr_neutral + ) + connections = eng_obj["connections"][filter] + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + else + math_obj["connections"] = eng_obj["connections"] + end + + data_math["shunt"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => "shunt.$name", + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_capacitor!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["shunt"]) + ) + end +end + + "" function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) @@ -315,7 +371,7 @@ end "" -function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) math_obj = _init_math_obj("generator", eng_obj, length(data_math["gen"])+1) @@ -327,19 +383,28 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["name"] = name math_obj["gen_status"] = eng_obj["status"] - math_obj["pg"] = fill(eng_obj["kw"] / 1e3 / phases, phases) - math_obj["qg"] = fill(eng_obj["kvar"] / 1e3 / phases, phases) + math_obj["pg"] = fill(eng_obj["kw"] / phases, phases) + math_obj["qg"] = fill(eng_obj["kvar"] / phases, phases) math_obj["vg"] = fill(eng_obj["kv"] / data_math["basekv"]) - math_obj["qmin"] = fill(eng_obj["kvar_min"] / 1e3 / phases, phases) - math_obj["qmax"] = fill(eng_obj["kvar_max"] / 1e3 / phases, phases) + math_obj["qmin"] = fill(eng_obj["kvar_min"] / phases, phases) + math_obj["qmax"] = fill(eng_obj["kvar_max"] / phases, phases) - math_obj["pmax"] = fill(eng_obj["kw"] / 1e3 / phases, phases) + math_obj["pmax"] = fill(eng_obj["kw"] / phases, phases) math_obj["pmin"] = fill(0.0, phases) math_obj["configuration"] = eng_obj["configuration"] - _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, collect(1:nconductors)) + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) + else + _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) + end + else + math_obj["connections"] = connections + end # if PV generator mode convert attached bus to PV bus if eng_obj["control_model"] == 3 @@ -360,7 +425,7 @@ end "" -function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) for (name, eng_obj) in get(data_eng, "pvsystem", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("pvsystem", eng_obj, length(data_math["gen"])+1) @@ -372,19 +437,28 @@ function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{< math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["gen_status"] = eng_obj["status"] - math_obj["pg"] = fill(eng_obj["kva"] / 1e3 / phases, phases) - math_obj["qg"] = fill(eng_obj["kva"] / 1e3 / phases, phases) + math_obj["pg"] = fill(eng_obj["kva"] / phases, phases) + math_obj["qg"] = fill(eng_obj["kva"] / phases, phases) math_obj["vg"] = fill(eng_obj["kv"] / data_math["basekv"], phases) math_obj["pmin"] = fill(0.0, phases) - math_obj["pmax"] = fill(eng_obj["kva"] / 1e3 / phases, phases) + math_obj["pmax"] = fill(eng_obj["kva"] / phases, phases) - math_obj["qmin"] = fill(-eng_obj["kva"] / 1e3 / phases, phases) - math_obj["qmax"] = fill( eng_obj["kva"] / 1e3 / phases, phases) + math_obj["qmin"] = fill(-eng_obj["kva"] / phases, phases) + math_obj["qmax"] = fill( eng_obj["kva"] / phases, phases) math_obj["configuration"] = eng_obj["configuration"] - _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, collect(1:nconductors)) + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) + else + _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) + end + else + math_obj["connections"] = connections + end data_math["gen"]["$(math_obj["index"])"] = math_obj @@ -453,7 +527,7 @@ end "" -function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) if haskey(eng_obj, "linecode") linecode = data_eng["linecode"][eng_obj["linecode"]] @@ -470,8 +544,8 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] - Zbase = (data_math["basekv"] / sqrt(3))^2 * nconductors / (data_math["baseMVA"]) - Zbase = Zbase / 3 + Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) + Zbase = 1 math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] @@ -494,9 +568,21 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any f_bus = data_eng["bus"][eng_obj["f_bus"]] t_bus = data_eng["bus"][eng_obj["t_bus"]] - neutral = haskey(f_bus, "neutral") ? f_bus["neutral"] : haskey(t_bus, "neutral") ? t_bus["neutral"] : nphases+1 + # neutral = haskey(f_bus, "neutral") ? f_bus["neutral"] : haskey(t_bus, "neutral") ? t_bus["neutral"] : nphases+1 - _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], eng_obj["f_connections"], collect(1:nconductors); neutral=neutral) + if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") + filter = _kron_reduce_branch!(math_obj, + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], + eng_obj["f_connections"], kr_neutral + ) + _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) + connections = eng_obj["f_connections"][filter] + _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + else + math_obj["f_connections"] = eng_obj["f_connections"] + math_obj["f_connections"] = eng_obj["t_connections"] + end math_obj["switch"] = false @@ -515,6 +601,42 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any end +function _kron_reduce_branch!(obj, Zs_keys, Ys_keys, terminals, neutral) + Zs = [obj[k] for k in Zs_keys] + Ys = [obj[k] for k in Ys_keys] + Zs_kr, Ys_kr, terminals_kr = _kron_reduce_branch(Zs, Ys, terminals, neutral) + + for (i,k) in enumerate(Zs_keys) + obj[k] = Zs_kr[i] + end + + for (i,k) in enumerate(Ys_keys) + obj[k] = Ys_kr[i] + end + + return _get_idxs(terminals, terminals_kr) +end + + +function _kron_reduce_branch(Zs, Ys, terminals, neutral) + Zs_kr = [deepcopy(Z) for Z in Zs] + Ys_kr = [deepcopy(Y) for Y in Ys] + terminals_kr = deepcopy(terminals) + + while neutral in terminals_kr + n = _get_ilocs(terminals_kr, neutral)[1] + P = setdiff(collect(1:length(terminals_kr)), n) + + Zs_kr = [Z[P,P]-(1/Z[n,n])*Z[P,[n]]*Z[[n],P] for Z in Zs_kr] + Ys_kr = [Y[P,P] for Y in Ys_kr] + + terminals_kr = terminals_kr[P] + end + + return Zs_kr, Ys_kr, terminals_kr +end + + "" function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) # TODO enable real switches (right now only using vitual lines) @@ -551,8 +673,8 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A branch_obj = _init_math_obj("switch", eng_obj, length(data_math["branch"])+1) - Zbase = (data_math["basekv"] / sqrt(3))^2 * nconductors / (data_math["baseMVA"]) - Zbase = Zbase / 3 + Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) + Zbase = 1 _branch_obj = Dict{String,Any}( "name" => "_virtual_branch.switch.$name", @@ -700,10 +822,10 @@ function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{ "index" => length(data_math["bus"])+1, "name" => "_virtual_sourcebus", "bus_type" => 3, - "vm" => eng_obj["vm"] ./ data_eng["settings"]["set_vbase_val"], - "va" => eng_obj["va"], - "vmin" => eng_obj["vm"] ./ data_eng["settings"]["set_vbase_val"], - "vmax" => eng_obj["vm"] ./ data_eng["settings"]["set_vbase_val"], + "vm" => eng_obj["vm"], + "va" => deg2rad.(eng_obj["va"]), + "vmin" => eng_obj["vm"], + "vmax" => eng_obj["vm"], "basekv" => data_math["basekv"] ) @@ -729,7 +851,8 @@ function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{ vbase = data_math["basekv"] sbase = data_math["baseMVA"] - zbase = vbase^2 / sbase / 3 + zbase = vbase^2 / (sbase) + zbase = 1 branch_obj = Dict{String,Any}( "name" => "_virtual_sourcebus", diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 8c882c22e..2a8747c9b 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -1,11 +1,11 @@ "finds voltage zones" function _find_zones(data_model) - unused_line_ids = Set(keys(data_model["line"])) + unused_line_ids = Set(keys(data_model["branch"])) bus_lines = Dict([(id,Set()) for id in keys(data_model["bus"])]) - for (line_id,line) in data_model["line"] - f_bus = line["f_bus"] - t_bus = line["t_bus"] + for (line_id,line) in data_model["branch"] + f_bus = string(line["f_bus"]) + t_bus = string(line["t_bus"]) push!(bus_lines[f_bus], (line_id,t_bus)) push!(bus_lines[t_bus], (line_id,f_bus)) end @@ -51,10 +51,10 @@ function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) # transformers form the edges between these zones zone_edges = Dict([(zone,[]) for zone in keys(zones)]) edges = Set() - for (i,(_,trans)) in enumerate(data_model["transformer_2wa"]) + for (i,(_,trans)) in enumerate(data_model["transformer"]) push!(edges,i) - f_zone = bus_to_zone[trans["f_bus"]] - t_zone = bus_to_zone[trans["t_bus"]] + f_zone = bus_to_zone[string(trans["f_bus"])] + t_zone = bus_to_zone[string(trans["t_bus"])] tm_nom = trans["configuration"]=="delta" ? trans["tm_nom"]/sqrt(3) : trans["tm_nom"] push!(zone_edges[f_zone], (i, t_zone, 1/tm_nom)) push!(zone_edges[t_zone], (i, f_zone, tm_nom)) @@ -78,33 +78,31 @@ function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) end bus_vbase = Dict([(bus,zone_vbase[zone]) for (bus,zone) in bus_to_zone]) - line_vbase = Dict([(id, bus_vbase[line["f_bus"]]) for (id,line) in data_model["line"]]) + line_vbase = Dict([(id, bus_vbase[string(line["f_bus"])]) for (id,line) in data_model["branch"]]) return (bus_vbase, line_vbase) end "converts to per unit from SI" -function data_model_make_pu!(data_model; sbase=missing, vbases=missing) - v_var_scalar = data_model["settings"]["v_var_scalar"] - - if haskey(data_model["settings"], "sbase") - sbase_old = data_model["settings"]["sbase"] - else - sbase_old = 1 - end - +function data_model_make_pu!(data_model; settings=missing, sbase=1.0, vbases=missing, v_var_scalar=missing) if ismissing(sbase) - if haskey(data_model["settings"], "set_sbase_val") - sbase = data_model["settings"]["set_sbase_val"] + if !ismissing(settings) && haskey(settings, "set_sbase") + sbase = settings["set_sbase"] else - sbase = 1 + sbase = 1.0 end end + if haskey(data_model, "sbase") + sbase_old = data_model["sbase"] + else + sbase_old = 1.0 + end + # automatically find a good vbase if ismissing(vbases) - if haskey(data_model["settings"], "set_vbase_val") && haskey(data_model["settings"], "set_vbase_bus") - vbases = Dict(data_model["settings"]["set_vbase_bus"]=>data_model["settings"]["set_vbase_val"]) + if !ismissing(settings) && haskey(settings, "set_vbases") + vbases = settings["vbases"] else buses_type_3 = [(id, sum(bus["vm"])/length(bus["vm"])) for (id,bus) in data_model["bus"] if haskey(bus, "bus_type") && bus["bus_type"]==3] if !isempty(buses_type_3) @@ -121,31 +119,32 @@ function data_model_make_pu!(data_model; sbase=missing, vbases=missing) _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, v_var_scalar) end - for (id, line) in data_model["line"] + for (id, line) in data_model["branch"] vbase = line_vbase[id] _rebase_pu_line!(line, line_vbase[id], sbase, sbase_old, v_var_scalar) end for (id, shunt) in data_model["shunt"] - _rebase_pu_shunt!(shunt, bus_vbase[shunt["bus"]], sbase, sbase_old, v_var_scalar) + _rebase_pu_shunt!(shunt, bus_vbase[string(shunt["shunt_bus"])], sbase, sbase_old, v_var_scalar) end for (id, load) in data_model["load"] - _rebase_pu_load!(load, bus_vbase[load["bus"]], sbase, sbase_old, v_var_scalar) + _rebase_pu_load!(load, bus_vbase[string(load["load_bus"])], sbase, sbase_old, v_var_scalar) end - for (id, gen) in data_model["generator"] - _rebase_pu_generator!(gen, bus_vbase[gen["bus"]], sbase, sbase_old, v_var_scalar) + for (id, gen) in data_model["gen"] + _rebase_pu_generator!(gen, bus_vbase[string(gen["gen_bus"])], sbase, sbase_old, v_var_scalar, data_model) end - for (id, trans) in data_model["transformer_2wa"] + for (id, trans) in data_model["transformer"] # voltage base across transformer does not have to be consistent with the ratio! - f_vbase = bus_vbase[trans["f_bus"]] - t_vbase = bus_vbase[trans["t_bus"]] + f_vbase = bus_vbase[string(trans["f_bus"])] + t_vbase = bus_vbase[string(trans["t_bus"])] _rebase_pu_transformer_2w_ideal!(trans, f_vbase, t_vbase, sbase_old, sbase, v_var_scalar) end - data_model["settings"]["sbase"] = sbase + data_model["sbase"] = sbase + data_model["per_unit"] = true return data_model end @@ -194,7 +193,7 @@ function _rebase_pu_line!(line, vbase, sbase, sbase_old, v_var_scalar) z_scale = z_old/z_new y_scale = 1/z_scale - _scale_props!(line, ["rs", "xs"], z_scale) + _scale_props!(line, ["br_r", "br_x"], z_scale) _scale_props!(line, ["b_fr", "g_fr", "b_to", "g_to"], y_scale) # save new vbase @@ -212,10 +211,11 @@ function _rebase_pu_shunt!(shunt, vbase, sbase, sbase_old, v_var_scalar) # rebase grounding resistance z_new = vbase^2/sbase*v_var_scalar + z_scale = z_old/z_new y_scale = 1/z_scale - scale(shunt, "g_sh", y_scale) - scale(shunt, "b_sh", y_scale) + scale(shunt, "gs", y_scale) + scale(shunt, "bs", y_scale) # save new vbase shunt["vbase"] = vbase @@ -245,16 +245,24 @@ end "per-unit conversion for generators" -function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar) +function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar, data_model) vbase_old = get(gen, "vbase", 1.0/v_var_scalar) vbase_scale = vbase_old/vbase sbase_scale = sbase_old/sbase - for key in ["pd_set", "qd_set", "pd_min", "qd_min", "pd_max", "qd_max"] + for key in ["pg", "qg", "pmin", "qmin", "pmax", "qmax"] scale(gen, key, sbase_scale) end - scale(gen, "cost", 1/sbase_scale) + # if not in per unit yet, the cost has is in $/MWh + if !haskey(data_model, "sbase") + sbase_old_cost = 1E6/v_var_scalar + sbase_scale_cost = sbase_old_cost/sbase + else + sbase_scale_cost = sbase_scale + end + + _PMs._rescale_cost_model!(gen, 1/sbase_scale_cost) # save new vbase gen["vbase"] = vbase diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index d29b3eed1..dafc6f5de 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -405,6 +405,72 @@ function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, end +"" +function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}) + @assert(all(c in phases for c in connections)) + inds = _get_idxs(phases, connections) + + for property in properties + if haskey(object, property) + if isa(object[property], Vector) + tmp = zeros(length(phases)) + tmp[inds] = object[property] + object[property] = tmp + elseif isa(object[property], Matrix) + tmp = zeros(length(phases), length(phases)) + tmp[inds, inds] = object[property] + object[property] = tmp + end + end + end +end + + +"" +function _pad_properties_delta!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; invert=false) + @assert(all(c in phases for c in connections)) + @assert(length(connections) in [2, 3], "A delta configuration has to have at least 2 or 3 connections!") + @assert(length(phases)==3, "Padding only possible to a |phases|==3!") + + for property in properties + val = object[property] + val_length = length(connections)==2 ? 1 : length(connections) + @assert(isa(val, Vector) && length(val)==val_length) + + # build tmp + tmp = Dict() + sign = invert ? -1 : 1 + if val_length==1 + tmp[(connections[1], connections[2])] = val[1] + tmp[(connections[2], connections[1])] = sign*val[1] + else + tmp[(connections[1], connections[2])] = val[1] + tmp[(connections[2], connections[3])] = val[2] + tmp[(connections[3], connections[1])] = val[3] + end + merge!(tmp, Dict((k[2], k[1])=>sign*v for (k,v) in tmp)) + get_val(x,y) = haskey(tmp, (x,y)) ? tmp[(x,y)] : 0.0 + + object[property] = [get_val(phases[1], phases[2]), get_val(phases[2], phases[3]), get_val(phases[3], phases[1])] + end +end + + +function _apply_filter!(obj, properties, filter) + for property in properties + if haskey(obj, property) + if isa(obj[property], Vector) + obj[property] = obj[property][filter] + elseif isa(obj[property], Matrix) + obj[property] = obj[property][filter, filter] + else + error("The property $property is not a Vector or a Matrix!") + end + end + end +end + + """ Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the conductors 't_cnds', this method will return a list of conductors 'cnd' and a diff --git a/src/io/common.jl b/src/io/common.jl index a547efd49..fc7ebe1ce 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -36,13 +36,20 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model(data::Dict{<:Any,<:Any}) +function transform_data_model(data::Dict{<:Any,<:Any}; kron_reduced::Bool=true) current_data_model = get(data, "data_model", "mathematical") if current_data_model == "engineering" - return _map_eng2math(data) - # elseif current_data_model == "mathematical" - # return _map_math2eng!(data) + out = _map_eng2math(data, kron_reduced=kron_reduced) + + bus_indexed_id = string(out["bus_lookup"][data["settings"]["set_vbase_bus"]]) + vbases = Dict(bus_indexed_id=>data["settings"]["set_vbase_val"]) + sbase = data["settings"]["set_sbase_val"] + + data_model_make_pu!(out, vbases=vbases, sbase=sbase, v_var_scalar=data["settings"]["v_var_scalar"]) + return out + elseif current_data_model == "mathematical" + return _map_math2eng!(data) else @warn "Data model '$current_data_model' is not recognized, no transformation performed" return data @@ -52,7 +59,7 @@ end "" function correct_network_data!(data::Dict{String,Any}) - _PMs.make_per_unit!(data) + #_PMs.make_per_unit!(data) _PMs.check_connectivity(data) _PMs.correct_transformer_parameters!(data) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 1090a525a..066ac6571 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -2,6 +2,14 @@ import LinearAlgebra: diagm +function _register_awaiting_ground!(bus, connections) + if !haskey(bus, "awaiting_ground") + bus["awaiting_ground"] = [] + end + push!(bus["awaiting_ground"], connections) +end + + "Parses buscoords [lon,lat] (if present) into their respective buses" function _dss2eng_buscoords!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}) for (name, coords) in get(data_dss, "buscoords", Dict{String,Any}()) @@ -128,11 +136,7 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An # if the ground is used directly, register load if 0 in connections - if !haskey(data_eng["bus"][bus], "awaiting_ground") - data_eng["bus"][bus]["awaiting_ground"] = [] - end - - push!(data_eng["bus"][bus]["awaiting_ground"], eng_obj) + _register_awaiting_ground!(data_eng["bus"][bus], connections) end kv = defaults["kv"] @@ -158,6 +162,63 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An end +"Adds capacitors to `data_eng` from `data_dss`" +function _dss2eng_capacitor_to_shunt!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + for (name, dss_obj) in get(data_dss, "capacitor", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "capacitor") + defaults = _apply_ordered_properties(_create_capacitor(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + + nphases = defaults["phases"] + + dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") + conn = dyz_map[defaults["conn"]] + + bus_name = _parse_busname(defaults["bus1"])[1] + bus2_name = _parse_busname(defaults["bus2"])[1] + if bus_name!=bus2_name + Memento.error("Capacitor $(name): bus1 and bus2 should connect to the same bus.") + end + + f_terminals = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) + if conn=="wye" + t_terminals = _get_conductors_ordered_dm(defaults["bus2"], default=fill(0,nphases)) + else + # if delta connected, ignore bus2 and generate t_terminals such that + # it corresponds to a delta winding + t_terminals = [f_terminals[2:end]..., f_terminals[1]] + end + + # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) + #TODO figure out for more than 3 phases + vnom_ln = defaults["kv"] + if defaults["phases"] in [2,3] + vnom_ln = vnom_ln/sqrt(3) + end + # 'kvar' is specified for all phases at once; we want the per-phase one, in MVar + qnom = (defaults["kvar"]/1E3)/nphases + b = fill(qnom/vnom_ln^2, nphases) + + # convert to a shunt matrix + terminals, B = _calc_shunt(f_terminals, t_terminals, b) + + # if one terminal is ground (0), reduce shunt addmittance matrix + terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) + + eng_obj = create_shunt(status=convert(Int, defaults["enabled"]), bus=bus_name, connections=terminals, g_sh=fill(0.0, size(B)...), b_sh=B) + + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end + + if !haskey(data_eng, "shunt") + data_eng["shunt"] = Dict{String,Any}() + end + + data_eng["shunt"][name] = eng_obj + end +end + + "Adds capacitors to `data_eng` from `data_dss`" function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "capacitor", Dict{String,Any}()) @@ -235,6 +296,11 @@ function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{St eng_obj["status"] = convert(Int, defaults["enabled"]) eng_obj["source_id"] = "reactor.$name" + # if the ground is used directly, register + if 0 in eng_obj["connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) + end + if import_all _import_all!(eng_obj, defaults, dss_obj["prop_order"]) end @@ -262,6 +328,11 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] + # if the ground is used directly, register load + if 0 in eng_obj["connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) + end + eng_obj["kw"] = defaults["kw"] eng_obj["kvar"] = defaults["kvar"] eng_obj["kv"] = defaults["kv"] @@ -385,6 +456,14 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) eng_obj["t_connections"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) + # if the ground is used directly, register + if 0 in eng_obj["f_connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["f_bus"]], eng_obj["f_connections"]) + end + if 0 in eng_obj["t_connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["t_bus"]], eng_obj["t_connections"]) + end + eng_obj["status"] = convert(Int, defaults["enabled"]) eng_obj["source_id"] = "line.$(name)" @@ -462,11 +541,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["connections"][w] = terminals_w if 0 in terminals_w - bus = eng_obj["bus"][w] - if !haskey(data_eng["bus"][bus], "awaiting_ground") - data_eng["bus"][bus]["awaiting_ground"] = [] - end - push!(data_eng["bus"][bus]["awaiting_ground"], eng_obj) + _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"][w]], eng_obj["connections"][w]) end eng_obj["polarity"][w] = 1 @@ -593,7 +668,15 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] eng_obj["phases"] = defaults["phases"] - eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], check_length=false) + + eng_obj["configuration"] = "wye" + + eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)) + + # if the ground is used directly, register load + if 0 in eng_obj["connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) + end eng_obj["kva"] = defaults["kva"] eng_obj["kv"] = defaults["kv"] @@ -604,8 +687,6 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["ncost"] = 3 eng_obj["cost"] = [0.0, 1.0, 0.0] - eng_obj["configuration"] = "wye" - eng_obj["status"] = convert(Int, defaults["enabled"]) eng_obj["source_id"] = "pvsystem.$(name)" @@ -637,6 +718,11 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< eng_obj["name"] = name eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] + # if the ground is used directly, register load + if 0 in eng_obj["connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) + end + eng_obj["kwhstored"] = defaults["kwhstored"] eng_obj["kwhrated"] = defaults["kwhrated"] eng_obj["kwrated"] = defaults["kwrated"] @@ -694,6 +780,11 @@ function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String "status" => 1 ) + # if the ground is used directly, register load + if 0 in eng_obj["connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) + end + if import_all _import_all!(eng_obj, circuit, data_dss["circuit"]["prop_order"]) end @@ -737,9 +828,9 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, # TODO fix scale factors data_eng["settings"]["v_var_scalar"] = 1e3 # data_eng["settings"]["set_vbase_val"] = defaults["basekv"] / sqrt(3) * 1e3 - data_eng["settings"]["set_vbase_val"] = defaults["basekv"] + data_eng["settings"]["set_vbase_val"] = defaults["basekv"]/sqrt(3) data_eng["settings"]["set_vbase_bus"] = data_eng["sourcebus"] - data_eng["settings"]["set_sbase_val"] = defaults["basemva"] + data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1E3 data_eng["settings"]["basefreq"] = get(data_dss["options"], "defaultbasefreq", 60.0) data_eng["files"] = data_dss["filename"] @@ -761,7 +852,7 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, _dss2eng_loadshape!(data_eng, data_dss, import_all) _dss2eng_load!(data_eng, data_dss, import_all) - _dss2eng_capacitor!(data_eng, data_dss, import_all) + _dss2eng_capacitor_to_shunt!(data_eng, data_dss, import_all) _dss2eng_shunt_reactor!(data_eng, data_dss, import_all) _dss2eng_sourcebus!(data_eng, data_dss, import_all) diff --git a/src/io/utils.jl b/src/io/utils.jl index 20a5be59f..00c372e35 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -317,44 +317,43 @@ function _discover_terminals!(data_eng::Dict{String,<:Any}) end end + for comp_type in [x for x in ["voltage_source", "load", "shunt", "gen"] if haskey(data_eng, x)] + for comp in values(data_eng[comp_type]) + push!(terminals[comp["bus"]], setdiff(comp["connections"], [0])...) + end + end + for (id, bus) in data_eng["bus"] data_eng["bus"][id]["terminals"] = sort(collect(terminals[id])) end - # identify neutrals and propagate along cables - bus_neutral = _find_neutrals(data_eng) - for (id,bus) in data_eng["bus"] - if haskey(bus, "awaiting_ground") || haskey(bus_neutral, id) - # this bus will need a neutral - if haskey(bus_neutral, id) - neutral = bus_neutral[id] - else - neutral = maximum(bus["terminals"])+1 - push!(bus["terminals"], neutral) - end - bus["neutral"] = neutral - if haskey(bus, "awaiting_ground") - bus["grounded"] = [neutral] - bus["rg"] = [0.0] - bus["xg"] = [0.0] - for comp in bus["awaiting_ground"] - if eltype(comp["connections"])<:Array - for w in 1:length(comp["connections"]) - if comp["bus"][w]==id - comp["connections"][w] .+= (comp["connections"][w].==0)*neutral - end - end - else - comp["connections"] .+= (comp["connections"].==0)*neutral - end - # @show comp["connections"] - end - #delete!(bus, "awaiting_ground") + if haskey(bus, "awaiting_ground") + neutral = !(4 in bus["terminals"]) ? 4 : maximum(bus["terminals"])+1 + push!(bus["terminals"], neutral) + + bus["grounded"] = [neutral] + bus["rg"] = [0.0] + bus["xg"] = [0.0] + for i in 1:length(bus["awaiting_ground"]) + bus["awaiting_ground"][i][bus["awaiting_ground"][i].==0] .= neutral end end - phases = haskey(bus, "neutral") ? setdiff(bus["terminals"], bus["neutral"]) : bus["terminals"] - bus["phases"] = phases + end +end + + +function _discover_phases_neutral!(data_eng::Dict{String,<:Any}) + bus_neutral = _find_neutrals(data_eng) + for (id, bus) in data_eng["bus"] + terminals = bus["terminals"] + if haskey(bus_neutral, id) + bus["neutral"] = bus_neutral[id] + phases = setdiff(terminals, bus["neutral"]) + else + phases = terminals + end + @assert(length(phases)<=3, "At bus $id, we found $(length(phases))>3 phases; aborting discovery, requires manual inspection.") end end @@ -442,6 +441,11 @@ function _get_idxs(vec::Vector{<:Any}, els::Vector{<:Any})::Vector{Int} end +function _get_ilocs(vec::Vector{<:Any}, loc::Any)::Vector{Int} + return collect(1:length(vec))[vec.==loc] +end + + "Discovers all of the buses (not separately defined in OpenDSS), from \"lines\"" function _discover_buses(data_dss::Dict{String,<:Any})::Set buses = Set([]) @@ -690,3 +694,46 @@ function _parse_obj_dtypes!(obj_type, object, dtypes) end end end + + +""" +Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the +conductors 't_cnds', this method will return a list of conductors 'cnd' and a +matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. +""" +function _calc_shunt(f_cnds, t_cnds, y) + # TODO add types + cnds = unique([f_cnds..., t_cnds...]) + e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) + Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) + return (cnds, Y) +end + + +""" +Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this +method will calculate the reduced addmittance matrix if terminal 'ground' is +grounded. +""" +function _calc_ground_shunt_admittance_matrix(cnds, Y, ground) + # TODO add types + if ground in cnds + cndsr = setdiff(cnds, ground) + cndsr_inds = _get_idxs(cnds, cndsr) + Yr = Y[cndsr_inds, cndsr_inds] + return (cndsr, Yr) + else + return cnds, Y + end +end + + +"" +function _rm_floating_cnd(cnds, Y, f) + # TODO add types + P = setdiff(cnds, f) + f_inds = _get_idxs(cnds, [f]) + P_inds = _get_idxs(cnds, P) + Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] + return (P,Yrm) +end From 859c2118f91eb313e2d2a1f61e9b13535e4c55b7 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Mon, 9 Mar 2020 10:35:42 +1100 Subject: [PATCH 071/224] nearly all ut working --- src/core/data.jl | 2 +- src/data_model/units.jl | 2 +- src/io/opendss.jl | 27 +++++++++------- src/io/utils.jl | 4 ++- test/data/opendss/case3_lm_1230.dss | 18 +++++------ test/data/opendss/case3_lm_models.dss | 8 +++-- test/loadmodels.jl | 45 --------------------------- test/runtests.jl | 4 +-- test/transformer.jl | 44 +++++++++++--------------- 9 files changed, 55 insertions(+), 99 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index 93b9aca1e..a8f828e01 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -232,7 +232,7 @@ function _load_expmodel_params(load::Dict, bus::Dict) @assert(all(beta.>=0)) end # calculate proportionality constants - v0 = load["vnom_kv"]/(bus["base_kv"]/sqrt(3)) + v0 = load["vnom_kv"] a = pd./v0.^alpha b = qd./v0.^beta # get bounds diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 2a8747c9b..61c496eb7 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -233,7 +233,7 @@ function _rebase_pu_load!(load, vbase, sbase, sbase_old, v_var_scalar) vbase_old = get(load, "vbase", 1.0) vbase_scale = vbase_old/vbase - scale(load, "vnom", vbase_scale) + scale(load, "vnom_kv", vbase_scale) sbase_scale = sbase_old/sbase scale(load, "pd", sbase_scale) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 066ac6571..5ca9e7c83 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -90,19 +90,19 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An model = defaults["model"] if model == 3 - Memento.warn(_LOGGER, "$load_name: load model 3 not supported. Treating as model 1.") + Memento.warn(_LOGGER, "$name: load model 3 not supported. Treating as model 1.") model = 1 elseif model == 4 - Memento.warn(_LOGGER, "$load_name: load model 4 not supported. Treating as model 1.") + Memento.warn(_LOGGER, "$name: load model 4 not supported. Treating as model 1.") model = 1 elseif model == 6 - Memento.warn(_LOGGER, "$load_name: load model 6 identical to model 1 in current feature set. Treating as model 1.") + Memento.warn(_LOGGER, "$name: load model 6 identical to model 1 in current feature set. Treating as model 1.") model = 1 elseif model == 7 - Memento.warn(_LOGGER, "$load_name: load model 7 not supported. Treating as model 1.") + Memento.warn(_LOGGER, "$name: load model 7 not supported. Treating as model 1.") model = 1 elseif model == 8 - Memento.warn(_LOGGER, "$load_name: load model 8 not supported. Treating as model 1.") + Memento.warn(_LOGGER, "$name: load model 8 not supported. Treating as model 1.") model = 1 end @@ -112,15 +112,16 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An nphases = defaults["phases"] conf = defaults["conn"] + if conf=="delta" + @assert(nphases in [1, 3], "$name: only 1 and 3-phase delta loads are supported!") + end + # connections bus = _parse_busname(defaults["bus1"])[1] + connections_default = conf=="wye" ? [collect(1:nphases)..., 0] : nphases==1 ? [1,2] : [1,2,3] + connections = _get_conductors_ordered_dm(defaults["bus1"], default=connections_default, pad_ground=(conf=="wye")) - connections_default = conf=="wye" ? [collect(1:nphases)..., 0] : collect(1:nphases) - connections = _get_conductors_ordered_dm(defaults["bus1"], default=connections_default, check_length=false) - # if wye connected and neutral not specified, append ground - if conf=="wye" && length(connections)==nphases - connections = [connections..., 0] - end + @assert(length(unique(connections))==length(connections), "$name: connections cannot be made to a terminal more than once.") # now we can create the load; if you do not have the correct model, # pd/qd fields will be populated by default (should not happen for constant current/impedance) @@ -519,6 +520,8 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["bus"] = Array{String, 1}(undef, nrw) eng_obj["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) eng_obj["tm"] = Array{Array{Real, 1}, 1}(undef, nrw) + eng_obj["tm_min"] = Array{Array{Real, 1}, 1}(undef, nrw) + eng_obj["tm_max"] = Array{Array{Real, 1}, 1}(undef, nrw) eng_obj["fixed"] = [[true for i in 1:nphases] for j in 1:nrw] eng_obj["vnom"] = [defaults["kvs"][w] for w in 1:nrw] eng_obj["snom"] = [defaults["kvas"][w] for w in 1:nrw] @@ -546,6 +549,8 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["polarity"][w] = 1 eng_obj["tm"][w] = fill(defaults["taps"][w], nphases) + eng_obj["tm_min"][w] = fill(defaults["mintap"], nphases) + eng_obj["tm_max"][w] = fill(defaults["maxtap"], nphases) if w>1 prim_conf = eng_obj["configuration"][1] diff --git a/src/io/utils.jl b/src/io/utils.jl index 00c372e35..73b824eec 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -329,7 +329,7 @@ function _discover_terminals!(data_eng::Dict{String,<:Any}) for (id,bus) in data_eng["bus"] if haskey(bus, "awaiting_ground") - neutral = !(4 in bus["terminals"]) ? 4 : maximum(bus["terminals"])+1 + neutral = !(4 in bus["terminals"]) ? 4 : maximum(bus["terminals"])+1 push!(bus["terminals"], neutral) bus["grounded"] = [neutral] @@ -338,6 +338,8 @@ function _discover_terminals!(data_eng::Dict{String,<:Any}) for i in 1:length(bus["awaiting_ground"]) bus["awaiting_ground"][i][bus["awaiting_ground"][i].==0] .= neutral end + + delete!(bus, "awaiting_ground") end end end diff --git a/test/data/opendss/case3_lm_1230.dss b/test/data/opendss/case3_lm_1230.dss index 3749ffe35..46757e89e 100644 --- a/test/data/opendss/case3_lm_1230.dss +++ b/test/data/opendss/case3_lm_1230.dss @@ -25,30 +25,30 @@ New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=0 !Loads - single phase ! single-phase loads -New Load.d1ph phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 +! New Load.d1ph phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 ! if only one is specified, .0 is added -New Load.d1ph2 phases=1 loadbus.2 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 +! New Load.d1ph2 phases=1 loadbus.2 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 ! when connected between two phases, acts as delta load in the PMD sense New Load.d1ph23 phases=1 loadbus.2.3 kv=0.4 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 -New Load.d1ph0 phases=1 loadbus.0 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 -New Load.d1ph00 phases=1 loadbus.0.0 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 +! New Load.d1ph0 phases=1 loadbus.0 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 +! New Load.d1ph00 phases=1 loadbus.0.0 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 ! This specification will now lead to an error ! New Load.d1ph123 phases=1 loadbus.0.1.2.3 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 ! This specification will now lead to an error ! New Load.y1ph phases=1 loadbus kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 New Load.y1ph2 phases=1 loadbus.2 kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 -New Load.y1ph23 phases=1 loadbus.2.3 kv=0.4 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 -New Load.y1ph0 phases=1 loadbus.0 kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 -New Load.y1ph00 phases=1 loadbus.0.0 kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 +! New Load.y1ph23 phases=1 loadbus.2.3 kv=0.4 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 +! New Load.y1ph0 phases=1 loadbus.0 kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 +! New Load.y1ph00 phases=1 loadbus.0.0 kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 ! This specification will now lead to an error ! New Load.y1ph123 phases=1 loadbus.0.1.2.3 kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 ! three-phase loads New Load.d3ph phases=3 loadbus kv=0.4 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 New Load.d3ph123 phases=3 loadbus.1.2.3 kv=0.4 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 -New Load.d3ph1230 phases=3 loadbus.1.2.3.0 kv=0.4 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 +! New Load.d3ph1230 phases=3 loadbus.1.2.3.0 kv=0.4 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 New Load.d3ph213 phases=3 loadbus.2.1.3 kv=0.4 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 -New Load.d3ph3120 phases=3 loadbus.3.1.2.0 kv=0.4 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 +! New Load.d3ph3120 phases=3 loadbus.3.1.2.0 kv=0.4 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 ! note change kv New Load.y3ph phases=3 loadbus kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 New Load.y3ph123 phases=3 loadbus.1.2.3 kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 diff --git a/test/data/opendss/case3_lm_models.dss b/test/data/opendss/case3_lm_models.dss index de59ebb85..3ada49a21 100644 --- a/test/data/opendss/case3_lm_models.dss +++ b/test/data/opendss/case3_lm_models.dss @@ -24,10 +24,12 @@ New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=0 !Loads - single phase +! kept only to not change the numerical results; single-phase delta-loads should be specified with two connections +New Load.d1phm1 phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 +New Load.d1phm2 phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=2 conn=wye vminpu=0.7 vmaxpu=1.3 +New Load.d1phm5 phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=5 conn=wye vminpu=0.7 vmaxpu=1.3 + ! single-phase loads -New Load.d1phm1 phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=1 conn=delta vminpu=0.7 vmaxpu=1.3 -New Load.d1phm2 phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=2 conn=delta vminpu=0.7 vmaxpu=1.3 -New Load.d1phm5 phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=5 conn=delta vminpu=0.7 vmaxpu=1.3 New Load.y1phm1 phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=1 conn=wye vminpu=0.7 vmaxpu=1.3 New Load.y1phm2 phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=2 conn=wye vminpu=0.7 vmaxpu=1.3 New Load.y1phm5 phases=1 loadbus.1 kv=0.23 kW=400 kvar=300 model=5 conn=wye vminpu=0.7 vmaxpu=1.3 diff --git a/test/loadmodels.jl b/test/loadmodels.jl index c8375dd55..0b2ca2e49 100644 --- a/test/loadmodels.jl +++ b/test/loadmodels.jl @@ -8,45 +8,18 @@ # voltage magnitude at load bus @test isapprox(vm(sol, pmd, "loadbus"), [0.999993, 0.999992, 0.999993], atol=1E-5) # single-phase delta loads - @test isapprox(pd(sol, pmd, "d1ph"), [0.4000, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1ph"), [0.3000, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "d1ph2"), [0, 0.4000, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1ph2"), [0, 0.3000, 0], atol=1E-4) @test isapprox(pd(sol, pmd, "d1ph23"), [0, 0.2866, 0.1134], atol=1E-4) @test isapprox(qd(sol, pmd, "d1ph23"), [0, 0.0345, 0.2655], atol=1E-4) - @test isapprox(pd(sol, pmd, "d1ph0"), [0, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1ph0"), [0, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "d1ph00"), [0, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1ph00"), [0, 0, 0], atol=1E-4) - # Leads to an error now instead; order of conductors needed for this - # @test isapprox(pd(sol, pmd, "d1ph123"), [0.4000, 0, 0], atol=1E-4) - # @test isapprox(qd(sol, pmd, "d1ph123"), [0.3000, 0, 0], atol=1E-4) # single-phase wye loads - # Leads to an error now instead; order of conductors needed for this - # @test isapprox(pd(sol, pmd, "y1ph"), [0.4000, 0, 0], atol=1E-4) - # @test isapprox(qd(sol, pmd, "y1ph"), [0.3000, 0, 0], atol=1E-4) @test isapprox(pd(sol, pmd, "y1ph2"), [0, 0.4000, 0], atol=1E-4) @test isapprox(qd(sol, pmd, "y1ph2"), [0, 0.3000, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "y1ph23"), [0, 0.2866, 0.1134], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1ph23"), [0, 0.0345, 0.2655], atol=1E-4) - @test isapprox(pd(sol, pmd, "y1ph0"), [0, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1ph0"), [0, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "y1ph00"), [0, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1ph00"), [0, 0, 0], atol=1E-4) - # Leads to an error now instead; order of conductors needed for this - # @test isapprox(pd(sol, pmd, "y1ph123"), [0.4000, 0, 0], atol=1E-4) - # @test isapprox(qd(sol, pmd, "y1ph123"), [0.3000, 0, 0], atol=1E-4) # three-phase loads @test isapprox(pd(sol, pmd, "d3ph"), [0.1333, 0.1333, 0.1333], atol=1E-4) @test isapprox(qd(sol, pmd, "d3ph"), [0.100, 0.100, 0.100], atol=1E-4) @test isapprox(pd(sol, pmd, "d3ph123"), [0.1333, 0.1333, 0.1333], atol=1E-4) @test isapprox(qd(sol, pmd, "d3ph123"), [0.100, 0.100, 0.100], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3ph1230"), [0.1333, 0.1333, 0.1333], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3ph1230"), [0.100, 0.100, 0.100], atol=1E-4) @test isapprox(pd(sol, pmd, "d3ph213"), [0.1333, 0.1333, 0.1333], atol=1E-4) @test isapprox(qd(sol, pmd, "d3ph213"), [0.100, 0.100, 0.100], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3ph3120"), [0.1333, 0.1333, 0.1333], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3ph3120"), [0.100, 0.100, 0.100], atol=1E-4) end @testset "loadmodels 1/2/5 in acp pf" begin pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss") @@ -55,12 +28,6 @@ # voltage magnitude at load bus @test isapprox(vm(sol, pmd, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) # delta and wye single-phase load models - @test isapprox(pd(sol, pmd, "d1phm1"), [0.4000, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1phm1"), [0.3000, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "d1phm2"), [0.2783, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1phm2"), [0.2087, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "d1phm5"), [0.3336, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1phm5"), [0.2502, 0, 0], atol=1E-4) @test isapprox(pd(sol, pmd, "y1phm1"), [0.4000, 0, 0], atol=1E-4) @test isapprox(qd(sol, pmd, "y1phm1"), [0.3000, 0, 0], atol=1E-4) @test isapprox(pd(sol, pmd, "y1phm2"), [0.2783, 0, 0], atol=1E-4) @@ -89,12 +56,6 @@ # voltage magnitude at load bus @test isapprox(calc_vm_acr(sol, pmd, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) # delta and wye single-phase load models - @test isapprox(pd(sol, pmd, "d1phm1"), [0.4000, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1phm1"), [0.3000, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "d1phm2"), [0.2783, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1phm2"), [0.2087, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "d1phm5"), [0.3336, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1phm5"), [0.2502, 0, 0], atol=1E-4) @test isapprox(pd(sol, pmd, "y1phm1"), [0.4000, 0, 0], atol=1E-4) @test isapprox(qd(sol, pmd, "y1phm1"), [0.3000, 0, 0], atol=1E-4) @test isapprox(pd(sol, pmd, "y1phm2"), [0.2783, 0, 0], atol=1E-4) @@ -123,12 +84,6 @@ # voltage magnitude at load bus @test isapprox(calc_vm_acr(sol, pmd, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) # delta and wye single-phase load models - @test isapprox(pd(sol, pmd, "d1phm1"), [0.4000, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1phm1"), [0.3000, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "d1phm2"), [0.2783, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1phm2"), [0.2087, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "d1phm5"), [0.3336, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1phm5"), [0.2502, 0, 0], atol=1E-4) @test isapprox(pd(sol, pmd, "y1phm1"), [0.4000, 0, 0], atol=1E-4) @test isapprox(qd(sol, pmd, "y1phm1"), [0.3000, 0, 0], atol=1E-4) @test isapprox(pd(sol, pmd, "y1phm2"), [0.2783, 0, 0], atol=1E-4) diff --git a/test/runtests.jl b/test/runtests.jl index 246c6c2b9..60a98c80b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -55,9 +55,9 @@ include("common.jl") include("multinetwork.jl") # all passing - # include("transformer.jl") + include("transformer.jl") - # include("loadmodels.jl") + include("loadmodels.jl") include("delta_gens.jl") # all passing diff --git a/test/transformer.jl b/test/transformer.jl index 9373ec2ed..0e64fe1de 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -7,27 +7,24 @@ file = "../test/data/opendss/ut_trans_2w_yy.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) - solution_identify!(sol["solution"], pmd_data) - @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 end @testset "2w transformer acp pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) - solution_identify!(sol["solution"], pmd_data) - @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([29.8, -90.4, 149.8]), Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([29.8, -90.4, 149.8]), Inf) <= 0.1 end @testset "2w transformer acp pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - solution_identify!(sol["solution"], pmd_data) - @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 - @test norm(PMD._wrap_to_pi(sol["solution"]["bus"]["3"]["va"])-deg2rad.([-30.0, -150.4, 89.8]), Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 + @test norm(PMD._wrap_to_pi(sol["solution"]["bus"]["4"]["va"])-deg2rad.([-30.0, -150.4, 89.8]), Inf) <= 0.1 end end @@ -36,27 +33,24 @@ file = "../test/data/opendss/ut_trans_2w_yy.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) - solution_identify!(sol["solution"], pmd_data) - @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) - solution_identify!(sol["solution"], pmd_data) - @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([29.8, -90.4, 149.8]), Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([29.8, -90.4, 149.8]), Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) - solution_identify!(sol["solution"], pmd_data) - @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["3"]["va"]-deg2rad.([-30.0, -150.4, 89.8]), Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([-30.0, -150.4, 89.8]), Inf) <= 0.1 end end @@ -101,18 +95,16 @@ file = "../test/data/opendss/ut_trans_3w_dyy_3.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - solution_identify!(sol["solution"], pmd_data) - @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["3"]["va"]-rad2deg.([30.6, -90.0, 151.9]), Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([30.6, -90.0, 151.9]), Inf) <= 0.1 end @testset "3w transformer ac pf dyy - %loadloss=0" begin file = "../test/data/opendss/ut_trans_3w_dyy_3_loadloss.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test haskey(sol["solution"]["bus"], "10") - @test norm(vm(sol, pmd_data, "3")-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 - @test norm(va(sol, pmd_data, "3")-[30.7, -90.0, 152.0], Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([30.7, -90.0, 152.0]), Inf) <= 0.1 end end @@ -131,8 +123,8 @@ @test norm(tap(1,pm)-[0.95, 0.95, 0.95], Inf) <= 1E-4 @test norm(tap(2,pm)-[1.05, 1.05, 1.05], Inf) <= 1E-4 # then check whether voltage is what OpenDSS expects for those values - @test norm(vm(sol, pmd_data, "3")-[1.0352, 1.022, 1.0142], Inf) <= 1E-4 - @test norm(va(sol, pmd_data, "3")-[-0.1, -120.4, 119.8], Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[1.0352, 1.022, 1.0142], Inf) <= 1E-4 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 end end end From c331396e721e443faddb2474770bcfadf58e75ab Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Mon, 9 Mar 2020 10:41:47 +1100 Subject: [PATCH 072/224] remove pu from eng2math.jl --- src/data_model/eng2math.jl | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 8c9f1b320..5b3a574f2 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -306,12 +306,8 @@ function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An math_obj["name"] = name math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) - Zbase = 1 - - - math_obj["gs"] = eng_obj["g_sh"]*Zbase - math_obj["bs"] = eng_obj["b_sh"]*Zbase + math_obj["gs"] = eng_obj["g_sh"] + math_obj["bs"] = eng_obj["b_sh"] if kron_reduced filter = _kron_reduce_branch!(math_obj, @@ -346,8 +342,7 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D connections = eng_obj["connections"] nconductors = data_math["conductors"] - Zbase = (data_math["basekv"] / sqrt(3.0))^2 * nphases / data_math["baseMVA"] # Use single-phase base impedance for each phase - Gcap = Zbase * sum(eng_obj["kvar"]) / (nphases * 1e3 * (data_math["basekv"] / sqrt(3.0))^2) + Gcap = sum(eng_obj["kvar"]) / (nphases * 1e3 * (data_math["basekv"])^2) math_obj["name"] = name math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] @@ -544,20 +539,17 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] - Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) - Zbase = 1 - math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] / Zbase - math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] / Zbase + math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] + math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] - math_obj["g_fr"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) - math_obj["g_to"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) + math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) + math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) - math_obj["b_fr"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) - math_obj["b_to"] = Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) + math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) + math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) math_obj["angmin"] = fill(-60.0, nphases) math_obj["angmax"] = fill( 60.0, nphases) @@ -849,11 +841,6 @@ function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{ data_math["gen"]["$(gen_obj["index"])"] = gen_obj - vbase = data_math["basekv"] - sbase = data_math["baseMVA"] - zbase = vbase^2 / (sbase) - zbase = 1 - branch_obj = Dict{String,Any}( "name" => "_virtual_sourcebus", "source_id" => "vsource._virtual_sourcebus", @@ -866,8 +853,8 @@ function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{ "tranformer" => false, "switch" => false, "br_status" => 1, - "br_r" => eng_obj["rs"]./zbase, - "br_x" => eng_obj["xs"]./zbase, + "br_r" => eng_obj["rs"], + "br_x" => eng_obj["xs"], "g_fr" => zeros(nconductors, nconductors), "g_to" => zeros(nconductors, nconductors), "b_fr" => zeros(nconductors, nconductors), From bbe0a461c5015532ea87046172bb24aaa834408a Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Mon, 9 Mar 2020 16:06:09 +1100 Subject: [PATCH 073/224] all ut working --- src/data_model/utils.jl | 17 ++++++++++++----- src/io/dss_parse.jl | 4 ++-- test/transformer.jl | 8 ++++---- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index dafc6f5de..5e79f91ab 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -217,6 +217,12 @@ function _sc2br_impedance(Zsc) end end end + + # if all zero, return all zeros + if all(values(Zsc).==0.0) + return Zsc + end + # make Zb Zb = zeros(Complex{Float64}, N-1,N-1) for i in 1:N-1 @@ -250,10 +256,12 @@ function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::Str tr_t_bus = collect(1:N) buses = Set(1:2*N) - edges = [[[i,i+N] for i in 1:N]..., [[i+N,j+N] for (i,j) in keys(zsc)]...] + zbr = _sc2br_impedance(zsc) + + edges = [[[i,i+N] for i in 1:N]..., [[i+N,j+N] for (i,j) in keys(zbr)]...] lines = Dict(enumerate(edges)) - z = Dict(enumerate([r_s..., values(zsc)...])) + z = Dict(enumerate([r_s..., values(zbr)...])) shunts = Dict(2=>ysh) @@ -333,7 +341,6 @@ function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::Str push!(to_map, "bus.$(bus_obj["index"])") end - for (l,(i,j)) in lines # merge the shunts into the shunts of the pi model of the line g_fr = b_fr = g_to = b_to = 0 @@ -345,8 +352,8 @@ function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::Str end if haskey(shunts, j) - g_fr = real(shunts[j]) - b_fr = imag(shunts[j]) + g_to = real(shunts[j]) + b_to = imag(shunts[j]) delete!(shunts, j) end diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 399263e7c..59999d691 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -584,8 +584,8 @@ OpenDSS commands inside the originating file. """ function _merge_dss!(dss_prime::Dict{String,<:Any}, dss_to_add::Dict{String,<:Any}) for (k, v) in dss_to_add - if k in keys(dss_prime) && isa(v, Array) - append!(dss_prime[k], v) + if k in keys(dss_prime) && isa(v, Dict) + merge!(dss_prime[k], v) else dss_prime[k] = v end diff --git a/test/transformer.jl b/test/transformer.jl index 0e64fe1de..480db13af 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -78,8 +78,8 @@ file = "../test/data/opendss/ut_trans_3w_dyy_1.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 - @test norm(va(sol, pmd_data, "3")-[30.1, -90.7, 151.2], Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([30.1, -90.7, 151.2]), Inf) <= 0.1 end @testset "3w transformer ac pf dyy - some non-zero" begin @@ -87,8 +87,8 @@ pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) #@test isapprox(vm(sol, pmd_data, "3"), [0.93876, 0.90227, 0.90454], atol=1E-4) - @test norm(vm(sol, pmd_data, "3")-[0.93876, 0.90227, 0.90454], Inf) <= 1.5E-5 - @test norm(va(sol, pmd_data, "3")-[31.6, -88.8, 153.3], Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.93876, 0.90227, 0.90454], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([31.6, -88.8, 153.3]), Inf) <= 0.1 end @testset "3w transformer ac pf dyy - all zero" begin From b967732a804a37ac3adeb36fcef1681b4ad64e16 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 11:31:54 -0600 Subject: [PATCH 074/224] FIX: mld for opendss files Moves upper and lower bounds to outside variable creation for on_off variables OpenDSS does not have power limits by default currently, so generation constraints needed to be updated Re-enables transformer tests for mld --- src/core/constraint.jl | 22 ++++++-- src/core/constraint_template.jl | 8 ++- src/core/variable.jl | 97 +++++++++++++++++++++++++-------- src/form/apo.jl | 11 +++- test/mld.jl | 28 +++++----- test/runtests.jl | 6 +- 6 files changed, 124 insertions(+), 48 deletions(-) diff --git a/src/core/constraint.jl b/src/core/constraint.jl index bb68b72b5..36f62f6d2 100644 --- a/src/core/constraint.jl +++ b/src/core/constraint.jl @@ -74,12 +74,26 @@ function constraint_mc_generation_on_off(pm::_PMs.AbstractPowerModel, n::Int, i: qg = _PMs.var(pm, n, :qg, i) z = _PMs.var(pm, n, :z_gen, i) - JuMP.@constraint(pm.model, pg .<= pmax.*z) - JuMP.@constraint(pm.model, pg .>= pmin.*z) - JuMP.@constraint(pm.model, qg .<= qmax.*z) - JuMP.@constraint(pm.model, qg .>= qmin.*z) + for c in _PMs.conductor_ids(pm, n) + if isfinite(pmax[c]) + JuMP.@constraint(pm.model, pg[c] .<= pmax[c].*z) + end + + if isfinite(pmin[c]) + JuMP.@constraint(pm.model, pg[c] .>= pmin[c].*z) + end + + if isfinite(qmax[c]) + JuMP.@constraint(pm.model, qg[c] .<= qmax[c].*z) + end + + if isfinite(qmin[c]) + JuMP.@constraint(pm.model, qg[c] .>= qmin[c].*z) + end + end end + "" function constraint_mc_storage_thermal_limit(pm::_PMs.AbstractPowerModel, n::Int, i, rating) ps = _PMs.var(pm, n, :ps, i) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index dc6d900a8..8a44bf30c 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -438,8 +438,14 @@ end function constraint_mc_generation_on_off(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) gen = _PMs.ref(pm, nw, :gen, i) + ncnds = length(_PMs.conductor_ids(pm; nw=nw)) - constraint_mc_generation_on_off(pm, nw, i, gen["pmin"], gen["pmax"], gen["qmin"], gen["qmax"]) + pmin = get(gen, "pmin", fill(-Inf, ncnds)) + pmax = get(gen, "pmax", fill( Inf, ncnds)) + qmin = get(gen, "qmin", fill(-Inf, ncnds)) + qmax = get(gen, "qmax", fill( Inf, ncnds)) + + constraint_mc_generation_on_off(pm, nw, i, pmin, pmax, qmin, qmax) end "" diff --git a/src/core/variable.jl b/src/core/variable.jl index bde977daa..b6f4941c8 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -715,71 +715,100 @@ end "Create variables for `active` storage injection" -function variable_mc_on_off_storage_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) +function variable_mc_on_off_storage_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = _PMs.conductor_ids(pm; nw=nw) ncnds = length(cnds) - inj_lb = Dict() - inj_ub = Dict() - for cnd in 1:ncnds - inj_lb[cnd], inj_ub[cnd] = _PMs.ref_calc_storage_injection_bounds(_PMs.ref(pm, nw, :storage), _PMs.ref(pm, nw, :bus), cnd) - end - ps = _PMs.var(pm, nw)[:ps] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_ps_$(i)", - lower_bound = min(0, inj_lb[cnd][i]), - upper_bound = max(0, inj_ub[cnd][i]), start = comp_start_value(_PMs.ref(pm, nw, :storage, i), "ps_start", cnd, 0.0) ) for i in _PMs.ids(pm, nw, :storage)) + if bounded + for cnd in 1:ncnds + inj_lb, inj_ub = _PMs.ref_calc_storage_injection_bounds(_PMs.ref(pm, nw, :storage), _PMs.ref(pm, nw, :bus), cnd) + + for (i, strg) in _PMs.ref(pm, nw, :storage) + set_lower_bound.(ps[i], inj_lb[i]) + set_upper_bound.(ps[i], inj_ub[i]) + end + end + end + report && _PMs.sol_component_value(pm, nw, :storage, :ps, _PMs.ids(pm, nw, :storage), ps) end "Create variables for `reactive` storage injection" -function variable_mc_on_off_storage_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) +function variable_mc_on_off_storage_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = _PMs.conductor_ids(pm; nw=nw) ncnds = length(cnds) qs = _PMs.var(pm, nw)[:qs] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_qs_$(i)", - lower_bound = min(0, _PMs.ref(pm, nw, :storage, i, "qmin")[cnd]), - upper_bound = max(0, _PMs.ref(pm, nw, :storage, i, "qmax")[cnd]), start = comp_start_value(_PMs.ref(pm, nw, :storage, i), "qs_start", cnd, 0.0) ) for i in _PMs.ids(pm, nw, :storage)) + if bounded + for (i, strg) in _PMs.ref(pm, nw, :storage) + if haskey(strg, "qmin") + set_lower_bound.(qs[i], strg["qmin"]) + end + + if haskey(strg, "qmax") + set_upper_bound.(qs[i], strg["qmax"]) + end + end + end + report && _PMs.sol_component_value(pm, nw, :storage, :qs, _PMs.ids(pm, nw, :storage), qs) end "voltage variable magnitude squared (relaxed form)" -function variable_mc_voltage_magnitude_sqr_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) +function variable_mc_voltage_magnitude_sqr_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = _PMs.conductor_ids(pm; nw=nw) ncnds = length(cnds) w = _PMs.var(pm, nw)[:w] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_w_$(i)", - lower_bound = 0, - upper_bound = _PMs.ref(pm, nw, :bus, i, "vmax")[c]^2, start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "w_start", c, 1.001) ) for i in _PMs.ids(pm, nw, :bus)) + if bounded + for (i, bus) in _PMs.ref(pm, nw, :bus) + set_lower_bound.(w[i], 0.0) + + if haskey(bus, "vmax") + set_upper_bound.(w[i], bus["vmax"].^2) + end + end + end + report && _PMs.sol_component_value(pm, nw, :bus, :w, _PMs.ids(pm, nw, :bus), w) end "on/off voltage magnitude variable" -function variable_mc_voltage_magnitude_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) +function variable_mc_voltage_magnitude_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = _PMs.conductor_ids(pm; nw=nw) ncnds = length(cnds) vm = _PMs.var(pm, nw)[:vm] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_vm_$(i)", - lower_bound = 0, - upper_bound = _PMs.ref(pm, nw, :bus, i, "vmax")[c], start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "vm_start", c, 1.0) ) for i in _PMs.ids(pm, nw, :bus)) + if bounded + for (i, bus) in _PMs.ref(pm, nw, :bus) + set_lower_bound.(vm[i], 0.0) + + if haskey(bus, "vmax") + set_upper_bound.(vm[i], bus["vmax"]) + end + end + end + report && _PMs.sol_component_value(pm, nw, :bus, :vm, _PMs.ids(pm, nw, :bus), vm) end @@ -899,31 +928,51 @@ function variable_mc_generation_on_off(pm::_PMs.AbstractPowerModel; kwargs...) end -function variable_mc_active_generation_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) +function variable_mc_active_generation_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = _PMs.conductor_ids(pm; nw=nw) ncnds = length(_PMs.conductor_ids(pm, nw)) pg = _PMs.var(pm, nw)[:pg] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_pg_$(i)", - lower_bound = min(0, _PMs.ref(pm, nw, :gen, i, "pmin")[cnd]), - upper_bound = max(0, _PMs.ref(pm, nw, :gen, i, "pmax")[cnd]), start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "pg_start", cnd, 0.0) ) for i in _PMs.ids(pm, nw, :gen)) + if bounded + for (i, gen) in _PMs.ref(pm, nw, :gen) + if haskey(gen, "pmin") + set_lower_bound.(pg[i], gen["pmin"]) + end + + if haskey(gen, "pmax") + set_upper_bound.(pg[i], gen["pmax"]) + end + end + end + report && _PMs.sol_component_value(pm, nw, :gen, :pg, _PMs.ids(pm, nw, :gen), pg) end -function variable_mc_reactive_generation_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) +function variable_mc_reactive_generation_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = _PMs.conductor_ids(pm; nw=nw) ncnds = length(cnds) qg = _PMs.var(pm, nw)[:qg] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_qg_$(i)", - lower_bound = min(0, _PMs.ref(pm, nw, :gen, i, "qmin")[cnd]), - upper_bound = max(0, _PMs.ref(pm, nw, :gen, i, "qmax")[cnd]), start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "qg_start", cnd, 0.0) ) for i in _PMs.ids(pm, nw, :gen)) + if bounded + for (i, gen) in _PMs.ref(pm, nw, :gen) + if haskey(gen, "qmin") + set_lower_bound.(qg[i], gen["qmin"]) + end + + if haskey(gen, "qmax") + set_upper_bound.(qg[i], gen["qmax"]) + end + end + end + report && _PMs.sol_component_value(pm, nw, :gen, :qg, _PMs.ids(pm, nw, :gen), qg) end diff --git a/src/form/apo.jl b/src/form/apo.jl index a611ea914..fa1a7b825 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -16,8 +16,15 @@ function constraint_mc_generation_on_off(pm::_PMs.AbstractActivePowerModel, n::I pg = _PMs.var(pm, n, :pg, i) z = _PMs.var(pm, n, :z_gen, i) - JuMP.@constraint(pm.model, pg .<= pmax.*z) - JuMP.@constraint(pm.model, pg .>= pmin.*z) + for c in _PMs.conductor_ids(pm, n) + if isfinite(pmax[c]) + JuMP.@constraint(pm.model, pg[c] .<= pmax[c].*z) + end + + if isfinite(pmin[c]) + JuMP.@constraint(pm.model, pg[c] .>= pmin[c].*z) + end + end end diff --git a/test/mld.jl b/test/mld.jl index ad69dd391..6d3b0133a 100644 --- a/test/mld.jl +++ b/test/mld.jl @@ -73,14 +73,14 @@ @test isapprox(result["solution"]["load"]["1"]["status"], 0.544; atol = 1e-3) end - # @testset "transformer nfa mld" begin - # mp_data = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") - # result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) + @testset "transformer nfa mld" begin + mp_data = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) - # @test result["termination_status"] == PMs.LOCALLY_SOLVED - # @test isapprox(result["objective"], 0.411, atol = 1e-3) - # @test isapprox(result["solution"]["load"]["1"]["status"], 1.0, atol = 1e-3) - # end + @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test isapprox(result["objective"], 0.411, atol = 1e-3) + @test isapprox(result["solution"]["load"]["1"]["status"], 1.0, atol = 1e-3) + end @testset "5-bus lpubfdiag mld" begin mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5.m"); PMD.make_multiconductor!(mp_data, 3) @@ -104,14 +104,14 @@ @test isapprox(result["solution"]["load"]["1"]["status"], 0.313; atol = 1e-3) end - # @testset "transformer case" begin - # dss = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") - # result = run_mc_mld_bf(dss, LPUBFDiagPowerModel, ipopt_solver) + @testset "transformer case" begin + dss = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + result = run_mc_mld_bf(dss, LPUBFDiagPowerModel, ipopt_solver) - # @test result["termination_status"] == PMs.LOCALLY_SOLVED - # @test isapprox(result["objective"], 0.0; atol=1e-3) - # @test isapprox(result["solution"]["load"]["1"]["status"], 1.0; atol=1e-3) - # end + @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test isapprox(result["objective"], 0.0; atol=1e-3) + @test isapprox(result["solution"]["load"]["1"]["status"], 1.0; atol=1e-3) + end @testset "5-bus acp mld_uc" begin mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5.m"); PMD.make_multiconductor!(mp_data, 3) diff --git a/test/runtests.jl b/test/runtests.jl index 60a98c80b..3ccaa6535 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -55,13 +55,13 @@ include("common.jl") include("multinetwork.jl") # all passing - include("transformer.jl") + include("transformer.jl") # banked transformers failing - include("loadmodels.jl") + include("loadmodels.jl") # all passing include("delta_gens.jl") # all passing include("shunt.jl") # all passing - include("mld.jl") # only transformer tests failing + include("mld.jl") # all passing end From 860fcdcb83b8fb6f7cff9221e3924f326bc56fbd Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 12:54:14 -0600 Subject: [PATCH 075/224] FIX: _eng2math_switch! Changed to new `kron_reduced` pattern, deleted duplicate `_pad_properties!` function (the one with kwargs) --- src/data_model/eng2math.jl | 20 ++++++++++++++------ src/data_model/utils.jl | 25 ------------------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 5b3a574f2..25b01a70b 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -630,7 +630,7 @@ end "" -function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) # TODO enable real switches (right now only using vitual lines) for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) nphases = length(eng_obj["f_connections"]) @@ -691,11 +691,19 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A merge!(branch_obj, _branch_obj) - f_bus = data_eng["bus"][eng_obj["f_bus"]] - t_bus = data_eng["bus"][eng_obj["t_bus"]] - neutral = haskey(f_bus, "neutral") ? f_bus["neutral"] : haskey(t_bus, "neutral") ? t_bus["neutral"] : nphases+1 - - _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], eng_obj["f_connections"], collect(1:nconductors); neutral=neutral) + if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") + filter = _kron_reduce_branch!(branch_obj, + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], + eng_obj["f_connections"], kr_neutral + ) + _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) + connections = eng_obj["f_connections"][filter] + _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + else + branch_obj["f_connections"] = eng_obj["f_connections"] + branch_obj["f_connections"] = eng_obj["t_connections"] + end data_math["branch"]["$(branch_obj["index"])"] = branch_obj diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 5e79f91ab..26dafceae 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -387,31 +387,6 @@ function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::Str end -"" -function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; neutral::Int=4, kron_reduced::Bool=true) - if kron_reduced - pos = Dict((x,i) for (i,x) in enumerate(phases)) - inds = [pos[x] for x in connections[connections.!=neutral]] - else - # TODO - end - - for property in properties - if haskey(object, property) - if isa(object[property], Vector) - tmp = zeros(length(phases)) - tmp[inds] = object[property] - object[property] = tmp - elseif isa(object[property], Matrix) - tmp = zeros(length(phases), length(phases)) - tmp[inds, inds] = object[property] - object[property] = tmp - end - end - end -end - - "" function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}) @assert(all(c in phases for c in connections)) From 48e5b15005487cd7fa16b5a4bd859dc71c630d4b Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 13:00:45 -0600 Subject: [PATCH 076/224] WIP: ADD: transformer banking Adds logic for banking transformers in `_bank_transformers!`, and changes order of operations to have transformer banking happen after discovering terminals Moves function `_awaiting_ground` to `src/io/utils.jl` Renames `parse_opendss_dm` to `parse_opendss` Transformer bank test still not passing due to problem with building of unbanked transformer problem (line 61 in test/transformers.jl) --- src/io/common.jl | 2 +- src/io/opendss.jl | 18 ++++---------- src/io/utils.jl | 60 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/io/common.jl b/src/io/common.jl index fc7ebe1ce..b3f50721c 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -8,7 +8,7 @@ function parse_file(io::IO; data_model::String="mathematical", import_all::Bool= pmd_data = PowerModelsDistribution.parse_matlab(io) elseif filetype == "dss" Memento.warn(_LOGGER, "Not all OpenDSS features are supported, currently only minimal support for lines, loads, generators, and capacitors as shunts. Transformers and reactors as transformer branches are included, but value translation is not fully supported.") - pmd_data = PowerModelsDistribution.parse_opendss_dm(io; import_all=import_all, bank_transformers=bank_transformers) + pmd_data = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) elseif filetype == "json" pmd_data = PowerModels.parse_json(io; validate=false) else diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 5ca9e7c83..ae290d9b9 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -2,14 +2,6 @@ import LinearAlgebra: diagm -function _register_awaiting_ground!(bus, connections) - if !haskey(bus, "awaiting_ground") - bus["awaiting_ground"] = [] - end - push!(bus["awaiting_ground"], connections) -end - - "Parses buscoords [lon,lat] (if present) into their respective buses" function _dss2eng_buscoords!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}) for (name, coords) in get(data_dss, "buscoords", Dict{String,Any}()) @@ -803,15 +795,15 @@ end "Parses a DSS file into a PowerModels usable format" -function parse_opendss_dm(io::IOStream; import_all::Bool=false, bank_transformers::Bool=true)::Dict +function parse_opendss(io::IOStream; import_all::Bool=false, bank_transformers::Bool=true)::Dict data_dss = parse_dss(io) - return parse_opendss_dm(data_dss; import_all=import_all) + return parse_opendss(data_dss; import_all=import_all, bank_transformers=bank_transformers) end "Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" -function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, bank_transformers::Bool=true)::Dict{String,Any} +function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, bank_transformers::Bool=true)::Dict{String,Any} data_eng = Dict{String,Any}( "source_type" => data_dss["source_type"], "settings" => Dict{String,Any}(), @@ -867,11 +859,11 @@ function parse_opendss_dm(data_dss::Dict{String,<:Any}; import_all::Bool=false, _dss2eng_vsource!(data_eng, data_dss, import_all) + _discover_terminals!(data_eng) + if bank_transformers _bank_transformers!(data_eng) end - _discover_terminals!(data_eng) - return data_eng end diff --git a/src/io/utils.jl b/src/io/utils.jl index 73b824eec..c44926816 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -280,9 +280,57 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) total_phases = sum(Int[transformer["nphases"] for transformer in transformers]) - # TODO if !haskey(banked, bank) - banked[bank] = deepcopy(transformers[1]) + eng_obj = Dict{String,Any}( + "source_id" => "transformer.$bank" + ) + + for transformer in transformers + for key in ["polarity", "bus", "configuration", "status", "bank", "rs", "noloadloss", "tm_step", "xsc", "imag", "snom", "vnom"] + if !haskey(eng_obj, key) + eng_obj[key] = transformer[key] + else + @assert transformer[key] == eng_obj[key] "$key property of transformers does not match, cannot bank" + end + end + + if !haskey(eng_obj, "connections") + eng_obj["connections"] = transformer["connections"] + else + @assert length(transformer["connections"]) == length(eng_obj["connections"]) "inconsistent number of windings, cannot bank" + + for (i, wdg) in enumerate(transformer["connections"]) + for conn in wdg + if !(conn in eng_obj["connections"][i]) + push!(eng_obj["connections"][i], conn) + end + end + eng_obj["connections"][i] = _roll(sort(eng_obj["connections"][i]), count(j->j==0, eng_obj["connections"][i]); right=false) + end + end + + if !haskey(eng_obj, "nphases") + eng_obj["nphases"] = transformer["nphases"] + else + eng_obj["nphases"] += transformer["nphases"] + end + + for key in ["tm", "tm_max", "tm_min", "fixed"] + if !haskey(eng_obj, key) + eng_obj[key] = [zeros(3), zeros(3)] + end + + for (i, wdg) in enumerate(transformer[key]) + for (j, v) in enumerate(wdg) + @warn key i transformer[key] eng_obj[key] wdg v transformer["connections"][j] + eng_obj[key][i][transformer["connections"][i][j]] = v + end + end + end + + end + + banked[bank] = eng_obj end end @@ -739,3 +787,11 @@ function _rm_floating_cnd(cnds, Y, f) Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] return (P,Yrm) end + + +function _register_awaiting_ground!(bus, connections) + if !haskey(bus, "awaiting_ground") + bus["awaiting_ground"] = [] + end + push!(bus["awaiting_ground"], connections) +end From c282c525b9a50d7660ec5c0d8500348048061d2e Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 14:17:27 -0600 Subject: [PATCH 077/224] ADD: transformer banking Completes transformer banking Moves _kron_reduce_branch! and _kron_reduce_branch to src/data_model/util.jl Adds padding of properties on single phase transformers. this is currently a workaround that needs to be reviewed. --- src/data_model/eng2math.jl | 74 +++++++++++++------------------------- src/data_model/utils.jl | 44 +++++++++++++++++++++-- test/runtests.jl | 2 +- test/transformer.jl | 33 +++++++++-------- 4 files changed, 83 insertions(+), 70 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 25b01a70b..f564929aa 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -129,7 +129,7 @@ end "" -function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) +function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) # TODO fix vnom phases = get(eng_obj, "phases", [1, 2, 3]) @@ -187,7 +187,7 @@ end "" -function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) +function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) # TODO add delta loads for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("load", eng_obj, length(data_math["load"])+1) @@ -230,7 +230,7 @@ end "" -function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) +function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "capacitor", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("capacitor", eng_obj, length(data_math["shunt"])+1) @@ -298,7 +298,7 @@ end "" -function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) +function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "shunt", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("shunt", eng_obj, length(data_math["shunt"])+1) @@ -366,7 +366,7 @@ end "" -function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) +function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) math_obj = _init_math_obj("generator", eng_obj, length(data_math["gen"])+1) @@ -420,7 +420,7 @@ end "" -function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) +function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "pvsystem", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("pvsystem", eng_obj, length(data_math["gen"])+1) @@ -469,7 +469,7 @@ end "" -function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("storage", eng_obj, length(data_math["storage"])+1) @@ -497,7 +497,9 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: math_obj["ps"] = fill(0.0, phases) math_obj["qs"] = fill(0.0, phases) - _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, collect(1:nconductors)) + if kron_reduced + _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, kr_phases) + end data_math["storage"]["$(math_obj["index"])"] = math_obj @@ -513,7 +515,7 @@ end "" -function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Dict{String,Any}}()) if eng_obj["bus"] != data_eng["sourcebus"] end @@ -522,7 +524,7 @@ end "" -function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) +function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) if haskey(eng_obj, "linecode") linecode = data_eng["linecode"][eng_obj["linecode"]] @@ -593,44 +595,8 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any end -function _kron_reduce_branch!(obj, Zs_keys, Ys_keys, terminals, neutral) - Zs = [obj[k] for k in Zs_keys] - Ys = [obj[k] for k in Ys_keys] - Zs_kr, Ys_kr, terminals_kr = _kron_reduce_branch(Zs, Ys, terminals, neutral) - - for (i,k) in enumerate(Zs_keys) - obj[k] = Zs_kr[i] - end - - for (i,k) in enumerate(Ys_keys) - obj[k] = Ys_kr[i] - end - - return _get_idxs(terminals, terminals_kr) -end - - -function _kron_reduce_branch(Zs, Ys, terminals, neutral) - Zs_kr = [deepcopy(Z) for Z in Zs] - Ys_kr = [deepcopy(Y) for Y in Ys] - terminals_kr = deepcopy(terminals) - - while neutral in terminals_kr - n = _get_ilocs(terminals_kr, neutral)[1] - P = setdiff(collect(1:length(terminals_kr)), n) - - Zs_kr = [Z[P,P]-(1/Z[n,n])*Z[P,[n]]*Z[[n],P] for Z in Zs_kr] - Ys_kr = [Y[P,P] for Y in Ys_kr] - - terminals_kr = terminals_kr[P] - end - - return Zs_kr, Ys_kr, terminals_kr -end - - "" -function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases=[1, 2, 3], kr_neutral=4) +function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) # TODO enable real switches (right now only using vitual lines) for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) nphases = length(eng_obj["f_connections"]) @@ -732,7 +698,7 @@ end "" -function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "transformer", Dict{Any,Dict{String,Any}}()) # Build map first, so we can update it as we decompose the transformer map_idx = length(data_math["map"])+1 @@ -801,6 +767,16 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic end end + if kron_reduced + # TODO fix how padding works, this is a workaround to get bank working + if all(eng_obj["configuration"] .== "wye") + f_connections = transformer_2wa_obj["f_connections"] + _pad_properties!(transformer_2wa_obj, ["tm_min", "tm_max", "tm", "fixed"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) + + transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] + end + end + data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj push!(to_map, "transformer.$(transformer_2wa_obj["index"])") @@ -810,7 +786,7 @@ end "" -function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) # TODO create option for lossy vs lossless sourcebus connection for (name, eng_obj) in data_eng["voltage_source"] if eng_obj["bus"] == data_eng["sourcebus"] diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 26dafceae..3869ae274 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -388,18 +388,56 @@ end "" -function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}) +function _kron_reduce_branch!(obj, Zs_keys, Ys_keys, terminals, neutral) + Zs = [obj[k] for k in Zs_keys] + Ys = [obj[k] for k in Ys_keys] + Zs_kr, Ys_kr, terminals_kr = _kron_reduce_branch(Zs, Ys, terminals, neutral) + + for (i,k) in enumerate(Zs_keys) + obj[k] = Zs_kr[i] + end + + for (i,k) in enumerate(Ys_keys) + obj[k] = Ys_kr[i] + end + + return _get_idxs(terminals, terminals_kr) +end + + +"" +function _kron_reduce_branch(Zs, Ys, terminals, neutral) + Zs_kr = [deepcopy(Z) for Z in Zs] + Ys_kr = [deepcopy(Y) for Y in Ys] + terminals_kr = deepcopy(terminals) + + while neutral in terminals_kr + n = _get_ilocs(terminals_kr, neutral)[1] + P = setdiff(collect(1:length(terminals_kr)), n) + + Zs_kr = [Z[P,P]-(1/Z[n,n])*Z[P,[n]]*Z[[n],P] for Z in Zs_kr] + Ys_kr = [Y[P,P] for Y in Ys_kr] + + terminals_kr = terminals_kr[P] + end + + return Zs_kr, Ys_kr, terminals_kr +end + + +"" +function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; pad_value::Real=0.0) @assert(all(c in phases for c in connections)) inds = _get_idxs(phases, connections) for property in properties if haskey(object, property) if isa(object[property], Vector) - tmp = zeros(length(phases)) + tmp = fill(pad_value, length(phases)) tmp[inds] = object[property] object[property] = tmp elseif isa(object[property], Matrix) - tmp = zeros(length(phases), length(phases)) + tmp = fill(pad_value, length(phases), length(phases)) tmp[inds, inds] = object[property] object[property] = tmp end diff --git a/test/runtests.jl b/test/runtests.jl index 3ccaa6535..e916a298d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -55,7 +55,7 @@ include("common.jl") include("multinetwork.jl") # all passing - include("transformer.jl") # banked transformers failing + include("transformer.jl") # all passing include("loadmodels.jl") # all passing diff --git a/test/transformer.jl b/test/transformer.jl index 480db13af..52db6ada7 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -54,24 +54,23 @@ end end + @testset "2w transformer ac pf yy - banked transformers" begin + file = "../test/data/opendss/ut_trans_2w_yy_bank.dss" + pmd1 = PMD.parse_file(file) + pmd2 = PMD.parse_file(file; bank_transformers=false) + result1 = run_ac_mc_pf(pmd1, ipopt_solver) + result2 = run_ac_mc_pf(pmd2, ipopt_solver) - # @testset "2w transformer ac pf yy - banked transformers" begin - # file = "../test/data/opendss/ut_trans_2w_yy_bank.dss" - # pmd1 = PMD.parse_file(file) - # pmd2 = PMD.parse_file(file; bank_transformers=false) - # result1 = run_ac_mc_pf(pmd1, ipopt_solver) - # result2 = run_ac_mc_pf(pmd2, ipopt_solver) - # - # @test result1["termination_status"] == PMs.LOCALLY_SOLVED - # @test result2["termination_status"] == PMs.LOCALLY_SOLVED - # @test result1["solution"]["bus"] == result2["solution"]["bus"] - # @test result1["solution"]["gen"] == result2["solution"]["gen"] - # - # dss = PMD.parse_dss(file) - # PMD.parse_dss_with_dtypes!(dss, ["line", "load", "transformer"]) - # trans = PMD._create_transformer(dss["transformer"][1]["name"]; PMD._to_kwargs(dss["transformer"][1])...) - # @test all(trans["%rs"] .== [1.0, 2.0]) - # end + @test result1["termination_status"] == PMs.LOCALLY_SOLVED + @test result2["termination_status"] == PMs.LOCALLY_SOLVED + + # @test result1["solution"]["bus"] == result2["solution"]["bus"] # TODO need a new test, transformer model changed, use voltages on real bus + # @test result1["solution"]["gen"] == result2["solution"]["gen"] # TODO need a new test, transformer model changed, use voltages on real bus + + dss = PMD.parse_dss(file) + trans = PMD._create_transformer(dss["transformer"]["tx1"]["name"]; PMD._to_kwargs(dss["transformer"]["tx1"])...) + @test all(trans["%rs"] .== [1.0, 2.0]) + end @testset "three winding transformer pf" begin @testset "3w transformer ac pf dyy - all non-zero" begin From ba64c57d8562d604df5ef2dbb89835aafbd1d853 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 14:19:10 -0600 Subject: [PATCH 078/224] REF: make_per_unit Renames data_model_make_pu! -> make_per_unit! Adds other required sections to main function Renames lines to branches Adds angles to per-unit conversion --- src/data_model/data_model_test.jl | 2 +- src/data_model/units.jl | 44 +++++++++++++++++++++---------- src/io/common.jl | 2 +- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/data_model/data_model_test.jl b/src/data_model/data_model_test.jl index 60a21afbb..21353716d 100644 --- a/src/data_model/data_model_test.jl +++ b/src/data_model/data_model_test.jl @@ -134,7 +134,7 @@ data_model data_model_map!(data_model) #bsh = data_model["shunt"]["_virtual_1"]["b_sh"] ## -data_model_make_pu!(data_model, vbases=Dict("sourcebus"=>0.230)) +make_per_unit!(data_model, vbases=Dict("sourcebus"=>0.230)) # data_model_index!(data_model) data_model_make_compatible_v8!(data_model) diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 61c496eb7..403b07068 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -1,4 +1,3 @@ - "finds voltage zones" function _find_zones(data_model) unused_line_ids = Set(keys(data_model["branch"])) @@ -84,7 +83,7 @@ end "converts to per unit from SI" -function data_model_make_pu!(data_model; settings=missing, sbase=1.0, vbases=missing, v_var_scalar=missing) +function make_per_unit!(data_model; settings=missing, sbase=1.0, vbases=missing, v_var_scalar=missing) if ismissing(sbase) if !ismissing(settings) && haskey(settings, "set_sbase") sbase = settings["set_sbase"] @@ -121,7 +120,7 @@ function data_model_make_pu!(data_model; settings=missing, sbase=1.0, vbases=mis for (id, line) in data_model["branch"] vbase = line_vbase[id] - _rebase_pu_line!(line, line_vbase[id], sbase, sbase_old, v_var_scalar) + _rebase_pu_branch!(line, line_vbase[id], sbase, sbase_old, v_var_scalar) end for (id, shunt) in data_model["shunt"] @@ -136,11 +135,21 @@ function data_model_make_pu!(data_model; settings=missing, sbase=1.0, vbases=mis _rebase_pu_generator!(gen, bus_vbase[string(gen["gen_bus"])], sbase, sbase_old, v_var_scalar, data_model) end - for (id, trans) in data_model["transformer"] - # voltage base across transformer does not have to be consistent with the ratio! - f_vbase = bus_vbase[string(trans["f_bus"])] - t_vbase = bus_vbase[string(trans["t_bus"])] - _rebase_pu_transformer_2w_ideal!(trans, f_vbase, t_vbase, sbase_old, sbase, v_var_scalar) + for (id, storage) in data_model["storage"] + # TODO + end + + for (id, switch) in data_model["switch"] + # TODO are there any properties that need to be converted to pu? + end + + if haskey(data_model, "transformer") # transformers are not required by PowerModels + for (id, trans) in data_model["transformer"] + # voltage base across transformer does not have to be consistent with the ratio! + f_vbase = bus_vbase[string(trans["f_bus"])] + t_vbase = bus_vbase[string(trans["t_bus"])] + _rebase_pu_transformer_2w_ideal!(trans, f_vbase, t_vbase, sbase_old, sbase, v_var_scalar) + end end data_model["sbase"] = sbase @@ -176,14 +185,18 @@ function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) z_scale = z_old/z_new _scale_props!(bus, ["rg", "xg"], z_scale) + if haskey(bus, "va") + bus["va"] = deg2rad.(bus["va"]) + end + # save new vbase bus["vbase"] = vbase end -"per-unit conversion for lines" -function _rebase_pu_line!(line, vbase, sbase, sbase_old, v_var_scalar) - if !haskey(line, "vbase") +"per-unit conversion for branches" +function _rebase_pu_branch!(branch, vbase, sbase, sbase_old, v_var_scalar) + if !haskey(branch, "vbase") z_old = 1 else z_old = vbase_old^2/sbase_old*v_var_scalar @@ -193,11 +206,14 @@ function _rebase_pu_line!(line, vbase, sbase, sbase_old, v_var_scalar) z_scale = z_old/z_new y_scale = 1/z_scale - _scale_props!(line, ["br_r", "br_x"], z_scale) - _scale_props!(line, ["b_fr", "g_fr", "b_to", "g_to"], y_scale) + _scale_props!(branch, ["br_r", "br_x"], z_scale) + _scale_props!(branch, ["b_fr", "g_fr", "b_to", "g_to"], y_scale) + + branch["angmin"] = deg2rad.(branch["angmin"]) + branch["angmax"] = deg2rad.(branch["angmax"]) # save new vbase - line["vbase"] = vbase + branch["vbase"] = vbase end diff --git a/src/io/common.jl b/src/io/common.jl index b3f50721c..806613328 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -46,7 +46,7 @@ function transform_data_model(data::Dict{<:Any,<:Any}; kron_reduced::Bool=true) vbases = Dict(bus_indexed_id=>data["settings"]["set_vbase_val"]) sbase = data["settings"]["set_sbase_val"] - data_model_make_pu!(out, vbases=vbases, sbase=sbase, v_var_scalar=data["settings"]["v_var_scalar"]) + make_per_unit!(out, vbases=vbases, sbase=sbase, v_var_scalar=data["settings"]["v_var_scalar"]) return out elseif current_data_model == "mathematical" return _map_math2eng!(data) From 138a534b60ad4082348b5a05401ae80cd5ec3a64 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 14:19:30 -0600 Subject: [PATCH 079/224] RM: dangling @warn in bank_transformers --- src/io/utils.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/io/utils.jl b/src/io/utils.jl index c44926816..4fc325d24 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -322,7 +322,6 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) for (i, wdg) in enumerate(transformer[key]) for (j, v) in enumerate(wdg) - @warn key i transformer[key] eng_obj[key] wdg v transformer["connections"][j] eng_obj[key][i][transformer["connections"][i][j]] = v end end From cff9752c587a78a8a6fb239fea7b3d5c2438125a Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 14:30:52 -0600 Subject: [PATCH 080/224] FIX: remove deg2rad from _eng2math_bus! Was double converting to radians from degrees on buses. now, all deg2rad should happen in make_per_unit! --- src/data_model/eng2math.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index f564929aa..8d8947f50 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -150,7 +150,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, math_obj["vm"] = eng_obj["vm"] end if haskey(eng_obj, "va") - math_obj["va"] = deg2rad(eng_obj["va"]) + math_obj["va"] = eng_obj["va"] end math_obj["vmin"] = fill(0.0, length(terminals)) From b201aba9d7dd59335116f2f7682b0a1d02948c2d Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 15:36:14 -0600 Subject: [PATCH 081/224] ADD: support 'edit' in dss parser It is possible to edit components already in the dss circuit, adding support for this. In particular, there is the special case of `vsource.source`, which is the circuit definition. --- src/io/dss_parse.jl | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 59999d691..771e9169b 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -468,7 +468,7 @@ the existing `data_dss` structure. If a component of the same type has already been added to `data_dss`, the new component is appeneded to the existing array of components of that type, otherwise a new array is created. """ -function _add_component!(data_dss::Dict, obj_type_name::AbstractString, object::Dict) +function _add_component!(data_dss::Dict{String,<:Any}, obj_type_name::AbstractString, object::Dict{String,<:Any}) obj_type = split(obj_type_name, '.'; limit=2)[1] if obj_type == "circuit" if haskey(data_dss, "circuit") @@ -484,6 +484,32 @@ function _add_component!(data_dss::Dict, obj_type_name::AbstractString, object:: end +function _add_component_edits!(data_dss::Dict{String,<:Any}, obj_type_name::AbstractString, object::Dict{String,<:Any}) + obj_type = split(obj_type_name, '.'; limit=2)[1] + if obj_type == "vsource" && object["name"] == "source" + delete!(object, "name") + + if !haskey(data_dss, "circuit") + data_dss["circuit"] = object + else + merge!(data_dss["circuit"], object) + end + else + if !haskey(data_dss, obj_type) + data_dss[obj_type] = Dict{String,Any}( + object["name"] => object + ) + else + if !haskey(data_dss[obj_type], object["name"]) + data_dss[obj_type][object["name"]] = object + else + merge!(data_dss[obj_type][object["name"]], object) + end + end + end +end + + """ Adds a property to an existing component properties dictionary `object` given the `key` and `value` of the property. If a property of the same name already @@ -722,6 +748,12 @@ function parse_dss(io::IOStream)::Dict{String,Any} data_dss["options"]["$(property)"] = value continue + elseif cmd == "edit" + current_obj_type, current_obj = _parse_line([lowercase(line_element) for line_element in line_elements]; path=path) + + _add_component_edits!(data_dss, current_obj_type, current_obj) + continue + elseif cmd == "buscoords" file = line_elements[2] full_path = path == "" ? file : join([path, file], '/') From 23fd45b544eee27b84d8df346e4df453f2b644a9 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 15:37:12 -0600 Subject: [PATCH 082/224] FIX: parsing of circuit sourcebus sourcebus name parsing failed when specifying connections --- src/io/opendss.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index ae290d9b9..154180843 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -500,7 +500,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri # test if this transformer conforms with limitations if nphases<3 && "delta" in confs - Memento.error("Transformers with delta windings should have at least 3 phases to be well-defined.") + Memento.error("Transformers with delta windings should have at least 3 phases to be well-defined: $name.") end if nrw>3 # All of the code is compatible with any number of windings, @@ -768,7 +768,7 @@ function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) eng_obj = Dict{String,Any}( - "bus" => circuit["bus1"], + "bus" => _parse_busname(circuit["bus1"])[1], "connections" => collect(1:phases), "vm" => vm, "va" => va, From 922d78b9eabcf9eb46dca7899bffb4ee678869ca Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 9 Mar 2020 16:39:03 -0600 Subject: [PATCH 083/224] FIX: disable angle unit conversion on buses --- src/data_model/units.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 403b07068..409add15e 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -185,9 +185,10 @@ function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) z_scale = z_old/z_new _scale_props!(bus, ["rg", "xg"], z_scale) - if haskey(bus, "va") - bus["va"] = deg2rad.(bus["va"]) - end + # TODO fix + # if haskey(bus ,"va") + # bus["va"] = deg2rad.(bus["va"]) + # end # save new vbase bus["vbase"] = vbase From 3b4ea1922cab591f0ca7a299ddaa0728fc4e4bde Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 10 Mar 2020 11:21:30 +1100 Subject: [PATCH 084/224] banking improved --- src/io/utils.jl | 113 ++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 67 deletions(-) diff --git a/src/io/utils.jl b/src/io/utils.jl index 4fc325d24..3c783ff83 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -263,84 +263,63 @@ end "Combines transformers with 'bank' keyword into a single transformer" function _bank_transformers!(data_eng::Dict{String,<:Any}) - banks = Dict{String,Array{String,1}}() - for (name, transformer) in get(data_eng, "transformer", Dict{String,Any}()) - if haskey(transformer, "bank") - if !haskey(banks, transformer["bank"]) - banks[transformer["bank"]] = Array{String,1}([name]) - else - push!(banks[transformer["bank"]], name) + bankable_transformers = Dict() + for (id, tr) in data_eng["transformer"] + if haskey(tr, "bank") + bank = tr["bank"] + if !haskey(bankable_transformers, bank) + bankable_transformers[bank] = ([], []) end + push!(bankable_transformers[bank][1], id) + push!(bankable_transformers[bank][2], tr) end end - banked = Dict{String,Any}() - for (bank, names) in banks - transformers = [data_eng["transformer"][name] for name in names] - - total_phases = sum(Int[transformer["nphases"] for transformer in transformers]) - - if !haskey(banked, bank) - eng_obj = Dict{String,Any}( - "source_id" => "transformer.$bank" - ) - - for transformer in transformers - for key in ["polarity", "bus", "configuration", "status", "bank", "rs", "noloadloss", "tm_step", "xsc", "imag", "snom", "vnom"] - if !haskey(eng_obj, key) - eng_obj[key] = transformer[key] - else - @assert transformer[key] == eng_obj[key] "$key property of transformers does not match, cannot bank" - end - end - - if !haskey(eng_obj, "connections") - eng_obj["connections"] = transformer["connections"] - else - @assert length(transformer["connections"]) == length(eng_obj["connections"]) "inconsistent number of windings, cannot bank" - - for (i, wdg) in enumerate(transformer["connections"]) - for conn in wdg - if !(conn in eng_obj["connections"][i]) - push!(eng_obj["connections"][i], conn) - end - end - eng_obj["connections"][i] = _roll(sort(eng_obj["connections"][i]), count(j->j==0, eng_obj["connections"][i]); right=false) - end - end - - if !haskey(eng_obj, "nphases") - eng_obj["nphases"] = transformer["nphases"] - else - eng_obj["nphases"] += transformer["nphases"] - end + for (bank, (ids, trs)) in bankable_transformers + # across-phase properties should be the same to be eligible for banking + props = ["bus", "noloadloss", "xsc", "rs", "imag", "vnom", "snom", "polarity", "configuration"] + btrans = Dict{String, Any}(prop=>trs[1][prop] for prop in props) + if !all(tr[prop]==btrans[prop] for tr in trs, prop in props) + continue + end + nrw = length(btrans["bus"]) - for key in ["tm", "tm_max", "tm_min", "fixed"] - if !haskey(eng_obj, key) - eng_obj[key] = [zeros(3), zeros(3)] - end + # only attempt to bank wye-connected transformers + if !all(all(tr["configuration"].=="wye") for tr in trs) + continue + end + neutrals = [conns[end] for conns in trs[1]["connections"]] + # ensure all windings have the same neutral + if !all(all(conns[end]==neutrals[w] for (w, conns) in enumerate(tr["connections"])) for tr in trs) + continue + end - for (i, wdg) in enumerate(transformer[key]) - for (j, v) in enumerate(wdg) - eng_obj[key][i][transformer["connections"][i][j]] = v - end - end + # this will merge the per-phase properties in such a way that the + # f_connections will be sorted from small to large + f_phases_loc = Dict(hcat([[(c,(i,p)) for (p, c) in enumerate(tr["connections"][1][1:end-1])] for (i, tr) in enumerate(trs)]...)) + locs = [f_phases_loc[x] for x in sort(collect(keys(f_phases_loc)))] + props_merge = ["connections", "tm", "tm_max", "tm_min", "fixed"] + for prop in props_merge + btrans[prop] = [[trs[i][prop][w][p] for (i,p) in locs] for w in 1:nrw] + + # for the connections, also prefix the neutral per winding + if prop=="connections" + for w in 1:nrw + push!(btrans[prop][w], neutrals[w]) end - end - - banked[bank] = eng_obj end - end - for (bank, names) in banks - if haskey(banked, bank) - for name in names - delete!(data_eng["transformer"], name) - end + # edit the transformer dict + for id in ids + delete!(data_eng["transformer"], id) end - - data_eng["transformer"][bank] = banked[bank] + btrans_name = bank + if haskey(data_eng["transformer"], bank) + Memento.warn("The bank name ($bank) is already used for another transformer; using the name of the first transformer $(ids[1]) in the bank instead.") + btrans_name = ids[1] + end + data_eng["transformer"][btrans_name] = btrans end end From 12670f13025b9f614694feb202d0a6cac9ff11c0 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 10 Mar 2020 13:36:08 +1100 Subject: [PATCH 085/224] solution_math2eng added --- src/data_model/eng2math.jl | 2 +- src/data_model/math2eng.jl | 60 ++++++++++++++++---------------------- src/data_model/units.jl | 36 ++++++++++++++++++----- test/transformer.jl | 55 ++++++++++++++++++++-------------- 4 files changed, 88 insertions(+), 65 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 8d8947f50..3083123c2 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -177,7 +177,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => "bus.$name", - :to => ["bus.$(math_obj["index"])"], + :to => "bus.$(math_obj["index"])", :unmap_function => :_map_math2eng_bus!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["bus"]) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index 169a98b02..a8fe7fbcc 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -15,45 +15,35 @@ end # MAP SOLUTION UP "" -function solution_unmap!(solution::Dict, data_model::Dict) - for (name, data) in reverse(data_model["mappings"]) - if name=="decompose_transformer_nw" - for bus_id in values(data["vbuses"]) - delete!(solution["bus"], bus_id) - end +function solution_math2eng(sol_math::Dict, data_math::Dict; make_si::Bool=true, make_deg::Bool=true) + sol_eng = Dict{String, Any}() - for line_id in values(data["vlines"]) - delete!(solution["branch"], line_id) - end + if make_deg || make_si + sol_math = solution_make_si(sol_math, data_math; mult_vbase=make_si, mult_sbase=make_si, convert_rad2deg=make_deg) + end - pt = [solution["transformer"][tr_id]["pf"] for tr_id in data["trans_2wa"]] - qt = [solution["transformer"][tr_id]["qf"] for tr_id in data["trans_2wa"]] - for tr_id in data["trans_2wa"] - delete!(solution["transformer"], tr_id) + map_keys = sort(collect(keys(data_math["map"])); rev=true) + for map_key in map_keys + map = data_math["map"][map_key] + umap_type = map[:unmap_function] + + if umap_type==:_map_math2eng_sourcebus! + elseif umap_type==:_map_math2eng_transformer! + elseif umap_type==:_map_math2eng_root! + else + comp_type_eng, name = split(map[:from], ".") + comp_type_math, index = split(map[:to], ".") + if !haskey(sol_eng, comp_type_eng) + sol_eng[comp_type_eng] = Dict{String, Any}() + end + if haskey(sol_math, comp_type_math) + sol_eng[comp_type_eng][name] = sol_math[comp_type_math][index] + else + #TODO add empty dicts if math object has no solution object? + sol_eng[comp_type_eng][name] = Dict{String, Any}() end - - add_solution!(solution, "transformer_nw", data["trans"]["id"], Dict("pt"=>pt, "qt"=>qt)) - elseif name=="capacitor_to_shunt" - # shunt has no solutions defined - delete_solution!(solution, "shunt", data["shunt_id"]) - add_solution!(solution, "capacitor", data["capacitor"]["id"], Dict()) - elseif name=="load_to_shunt" - # shunt has no solutions, but a load should have! - delete!(solution, "shunt", data["shunt_id"]) - add_solution!(solution, "load", data["load"]["id"], Dict()) - elseif name=="decompose_voltage_source" - gen = solution["gen"][data["gen_id"]] - delete_solution!(solution, "gen", data["gen_id"]) - add_solution!(solution, "voltage_source", data["voltage_source"]["id"], Dict("pg"=>gen["pg"], "qg"=>gen["qg"])) - end end - # remove component dicts if empty - for (comp_type, comp_dict) in solution - if isa(comp_dict, Dict) && isempty(comp_dict) - delete!(solution, comp_type) - end - end + return sol_eng end - diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 409add15e..d55a31517 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -324,20 +324,42 @@ function add_big_M!(data_model; kwargs...) end +const _dimensionalize_math = Dict( + "bus" => Dict("rad2deg"=>["va"], "vbase"=>["vm", "vr", "vi"]), + "gen" => Dict("sbase"=>["pg", "qg", "pg_bus", "qg_bus"]), + "load" => Dict("sbase"=>["pd", "qd", "pd_bus", "qd_bus"]), + "line" => Dict("sbase"=>["pf", "qf", "pt", "qt"]), +) + + "" -function solution_make_si!(solution, data_model) - sbase = data_model["settings"]["sbase"] - for (comp_type, comp_dict) in [(x,y) for (x,y) in solution if isa(y, Dict)] +function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true, convert_rad2deg=true) + solution_si = deepcopy(solution) + + sbase = math_model["sbase"] + + for (comp_type, comp_dict) in [(x,y) for (x,y) in solution_si if isa(y, Dict)] + dimensionalize_math_comp = get(_dimensionalize_math, comp_type, Dict()) + vbase_props = mult_vbase ? get(dimensionalize_math_comp, "vbase", []) : [] + sbase_props = mult_sbase ? get(dimensionalize_math_comp, "sbase", []) : [] + rad2deg_props = convert_rad2deg ? get(dimensionalize_math_comp, "rad2deg", []) : [] + for (id, comp) in comp_dict + if !isempty(vbase_props) + vbase = math_model[comp_type][id]["vbase"] + end + for (prop, val) in comp - if any([occursin(x, prop) for x in ["p", "q"]]) + if prop in vbase_props + comp[prop] = val*vbase + elseif prop in sbase_props comp[prop] = val*sbase - elseif any([occursin(x, prop) for x in ["vm", "vr", "vi"]]) - comp[prop] = val*data_model[comp_type][id]["vbase"] + elseif prop in rad2deg_props + comp[prop] = _wrap_to_180(rad2deg.(val)) end end end end - return solution + return solution_si end diff --git a/test/transformer.jl b/test/transformer.jl index 52db6ada7..0124e0199 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -7,24 +7,27 @@ file = "../test/data/opendss/ut_trans_2w_yy.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end @testset "2w transformer acp pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([29.8, -90.4, 149.8]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[29.8, -90.4, 149.8], Inf) <= 0.1 end @testset "2w transformer acp pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 - @test norm(PMD._wrap_to_pi(sol["solution"]["bus"]["4"]["va"])-deg2rad.([-30.0, -150.4, 89.8]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[-30.0, -150.4, 89.8], Inf) <= 0.1 end end @@ -33,24 +36,27 @@ file = "../test/data/opendss/ut_trans_2w_yy.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([29.8, -90.4, 149.8]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[29.8, -90.4, 149.8], Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([-30.0, -150.4, 89.8]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[-30.0, -150.4, 89.8], Inf) <= 0.1 end end @@ -77,8 +83,9 @@ file = "../test/data/opendss/ut_trans_3w_dyy_1.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([30.1, -90.7, 151.2]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[30.1, -90.7, 151.2], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - some non-zero" begin @@ -86,24 +93,27 @@ pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) #@test isapprox(vm(sol, pmd_data, "3"), [0.93876, 0.90227, 0.90454], atol=1E-4) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.93876, 0.90227, 0.90454], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([31.6, -88.8, 153.3]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.93876, 0.90227, 0.90454], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[31.6, -88.8, 153.3], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - all zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_3.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([30.6, -90.0, 151.9]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[30.6, -90.0, 151.9], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - %loadloss=0" begin file = "../test/data/opendss/ut_trans_3w_dyy_3_loadloss.dss" pmd_data = PMD.parse_file(file) sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - @test norm(sol["solution"]["bus"]["4"]["vm"]-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([30.7, -90.0, 152.0]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 + @test norm(solution["bus"]["3"]["va"]-[30.7, -90.0, 152.0], Inf) <= 0.1 end end @@ -122,8 +132,9 @@ @test norm(tap(1,pm)-[0.95, 0.95, 0.95], Inf) <= 1E-4 @test norm(tap(2,pm)-[1.05, 1.05, 1.05], Inf) <= 1E-4 # then check whether voltage is what OpenDSS expects for those values - @test norm(sol["solution"]["bus"]["4"]["vm"]-[1.0352, 1.022, 1.0142], Inf) <= 1E-4 - @test norm(sol["solution"]["bus"]["4"]["va"]-deg2rad.([-0.1, -120.4, 119.8]), Inf) <= 0.1 + solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[1.0352, 1.022, 1.0142], Inf) <= 1E-4 + @test norm(solution["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end end end From 3d14434595032e14ba274c2791e90695efd42198 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 10 Mar 2020 14:33:38 +1100 Subject: [PATCH 086/224] transformer math2eng added --- src/data_model/math2eng.jl | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index a8fe7fbcc..dc9a022ee 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -15,11 +15,11 @@ end # MAP SOLUTION UP "" -function solution_math2eng(sol_math::Dict, data_math::Dict; make_si::Bool=true, make_deg::Bool=true) - sol_eng = Dict{String, Any}() +function solution_math2eng(solution_math::Dict, data_math::Dict; make_si::Bool=true, make_deg::Bool=true) + solution_eng = Dict{String, Any}() if make_deg || make_si - sol_math = solution_make_si(sol_math, data_math; mult_vbase=make_si, mult_sbase=make_si, convert_rad2deg=make_deg) + solution_math = solution_make_si(solution_math, data_math; mult_vbase=make_si, mult_sbase=make_si, convert_rad2deg=make_deg) end map_keys = sort(collect(keys(data_math["map"])); rev=true) @@ -28,22 +28,39 @@ function solution_math2eng(sol_math::Dict, data_math::Dict; make_si::Bool=true, umap_type = map[:unmap_function] if umap_type==:_map_math2eng_sourcebus! - elseif umap_type==:_map_math2eng_transformer! + # nothing to do for the solution elseif umap_type==:_map_math2eng_root! + # nothing to do for the solution + elseif umap_type==:_map_math2eng_transformer! + _, name = split(map[:from_id], ".") + trans_2wa_ids = [index for (comp_type, index) in split.(map[:to_id], ".", limit=2) if comp_type=="transformer"] + + if !haskey(solution_eng, "transformer") + solution_eng["transformer"] = Dict{String, Any}() + end + + trans = Dict() + prop_map = Dict("pf"=>"p", "qf"=>"q") + for (prop_from, prop_to) in prop_map + trans[prop_to] = [get(solution_math["transformer"][id], prop_from, NaN) for id in trans_2wa_ids] + end + + solution_eng["transformer"][name] = trans + else comp_type_eng, name = split(map[:from], ".") comp_type_math, index = split(map[:to], ".") - if !haskey(sol_eng, comp_type_eng) - sol_eng[comp_type_eng] = Dict{String, Any}() + if !haskey(solution_eng, comp_type_eng) + solution_eng[comp_type_eng] = Dict{String, Any}() end - if haskey(sol_math, comp_type_math) - sol_eng[comp_type_eng][name] = sol_math[comp_type_math][index] + if haskey(solution_math, comp_type_math) + solution_eng[comp_type_eng][name] = solution_math[comp_type_math][index] else #TODO add empty dicts if math object has no solution object? - sol_eng[comp_type_eng][name] = Dict{String, Any}() + solution_eng[comp_type_eng][name] = Dict{String, Any}() end end end - return sol_eng + return solution_eng end From 34eb43a5e53d363fcda877b7ea6d23ff2b9e21e9 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 10 Mar 2020 07:09:45 -0600 Subject: [PATCH 087/224] FIX: property assignment in dss parser Fixes #252 --- src/io/dss_parse.jl | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 771e9169b..3a23256d4 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -652,13 +652,9 @@ end Assigns a property with name `property_name` and value `property_value` to the component of type `obj_type` named `obj_name` in `data_dss`. """ -function _assign_property!(data_dss::Dict, obj_type::AbstractString, obj_name::AbstractString, property_name::AbstractString, property_value::Any) - if haskey(data_dss, obj_type) - for obj in data_dss[obj_type] - if obj["name"] == obj_name - obj[property_name] = property_value - end - end +function _assign_property!(data_dss::Dict{String,<:Any}, obj_type::AbstractString, obj_name::AbstractString, property_name::AbstractString, property_value::Any) + if haskey(data_dss, obj_type) && haskey(data_dss[obj_type], obj_name) + data_dss[obj_type][obj_name][property_name] = property_value else Memento.warn(_LOGGER, "Cannot find $obj_type object $obj_name.") end From 9507a458964ca738ef43eeff39c696f4f7a71da4 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 10 Mar 2020 13:24:54 -0600 Subject: [PATCH 088/224] REF: source parsing from dss, other parser upgrades technically, the `new circuit.name` command creates a vsource with name `source` in opendss, instead of creating a separate circuit object. The whole circuit has the name `name`, with the idea that eventually multiple circuits in a single file could be supported (but they never were). Now dss files result in a new vsource named `source` instead of creating the circuit dictionary that we were before. This also simplified the recently added `_add_component_edits!` function by removing the need to have a special case for circuit objects. Improved the parser robustness by enumerating commands that we absolutely don't support verses commands that we simply don't understand Prevents `redirects` to files we have already parsed Adds support for `enable` and `disable` dss commands. Adds support for `latloncoords` command, which is an alias to `buscoords` Adds all possible options in `_create_options` in order to automatically parse the data types of any possible options. This simplifies the parsing of the sourcebus by only having to support voltage sources, instead of also a separate circuit object for just the sourcebus. Instead of using `source_type`, we now standardize around `data_model` Adds some initial support for relay, recloser, fuse, swtcontrol, cnddata and tsdata by prodviding the ordered properties for parsing. Adds support for xfmrcode. xfmrcode has the same fields as a transformer with the exception of bus, buses, bank, and xfmrcode. All tests passing --- src/core/data.jl | 38 ++++--- src/data_model/eng2math.jl | 147 +++++++++++------------- src/io/dss_parse.jl | 201 +++++++++++++++++++++------------ src/io/dss_structs.jl | 221 +++++++++++++++++++++++++++++++++++++ src/io/opendss.jl | 93 ++++++++-------- src/io/utils.jl | 8 +- 6 files changed, 492 insertions(+), 216 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index a8f828e01..8bcdd01ba 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -1,3 +1,7 @@ +const _excluded_count_busname_patterns = Vector{Regex}([ + r"^_virtual.*", +]) + "wraps angles in degrees to 180" function _wrap_to_180(degrees) return degrees - 360*floor.((degrees .+ 180)/360) @@ -44,13 +48,13 @@ _replace_nan(v) = map(x -> isnan(x) ? zero(x) : x, v) function count_nodes(data::Dict{String,<:Any})::Int n_nodes = 0 - if get(data, "source_type", "none") == "dss" && !haskey(data, "data_model") - sourcebus = get(data["circuit"], "bus1", "sourcebus") + if get(data, "data_model", missing) == "dss" + sourcebus = get(data["vsource"]["source"], "bus1", "sourcebus") all_nodes = Dict() - for comp_type in values(data) - for comp in values(comp_type) - if isa(comp, Dict) && haskey(comp, "buses") - for busname in values(comp["buses"]) + for obj_type in values(data) + for object in values(obj_type) + if isa(object, Dict) && haskey(object, "buses") + for busname in values(object["buses"]) name, nodes = _parse_busname(busname) if !haskey(all_nodes, name) @@ -63,8 +67,8 @@ function count_nodes(data::Dict{String,<:Any})::Int end end end - elseif isa(comp, Dict) - for (prop, val) in comp + elseif isa(object, Dict) + for (prop, val) in object if startswith(prop, "bus") && prop != "buses" name, nodes = _parse_busname(val) @@ -88,19 +92,21 @@ function count_nodes(data::Dict{String,<:Any})::Int n_nodes += length(phases) end end - - elseif get(data, "source_type", "none") == "dss" && haskey(data, "data_model") - - if get(data, "data_model", "mathematical") == "mathematical" + elseif get(data, "data_model", missing) in ["mathematical", "engineering"] || (haskey(data, "source_type") && data["source_type"] == "matlab") + if get(data, "data_model", missing) == "mathematical" Memento.info(_LOGGER, "counting nodes from PowerModelsDistribution structure may not be as accurate as directly from `parse_dss` data due to virtual buses, etc.") end n_nodes = 0 - for bus in values(data["bus"]) - if get(data, "source_type", "none") == "matlab" + for (name, bus) in data["bus"] + if get(data, "data_model", missing) == "matpower" || (haskey(data, "source_type") && data["source_type"] == "matlab") n_nodes += sum(bus["vm"] .> 0.0) - elseif get(data, "source_type", "none") == "dss" - if !(data["source_type"] == "dss" && bus["name"] in ["_virtual_sourcebus", data["sourcebus"]]) && !(data["source_type"] == "dss" && startswith(bus["name"], "tr") && endswith(bus["name"], r"_b\d")) + else + if data["data_model"] == "mathematical" + name = bus["name"] + end + + if all(!occursin(pattern, name) for pattern in [_excluded_count_busname_patterns..., data["sourcebus"]]) n_nodes += sum(bus["vmax"] .> 0.0) end end diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 3083123c2..410df9178 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -46,7 +46,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) data_math = Dict{String,Any}( "name" => data_eng["name"], "per_unit" => get(data_eng, "per_unit", false), - "source_type" => get(data_eng, "source_type", "none"), + "data_model" => "mathematical", "sourcebus" => get(data_eng, "sourcebus", "sourcebus"), ) @@ -54,8 +54,6 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) data_math["basekv"] = data_eng["settings"]["set_vbase_val"] data_math["baseMVA"] = data_eng["settings"]["set_sbase_val"]*data_eng["settings"]["v_var_scalar"]/1E6 - data_math["data_model"] = "mathematical" - data_math["map"] = Dict{Int,Dict{Symbol,Any}}( 1 => Dict{Symbol,Any}( :component_type => "root", @@ -77,14 +75,12 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_pvsystem!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_vsource!(data_math, data_eng; kron_reduced=kron_reduced) # TODO + _map_eng2math_vsource!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) # TODO - - _map_eng2math_sourcebus!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) return data_math end @@ -334,7 +330,7 @@ end "" -function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("shunt_reactor", eng_obj, length(data_math["shunt"])+1) @@ -514,15 +510,6 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: end -"" -function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Dict{String,Any}}()) - if eng_obj["bus"] != data_eng["sourcebus"] - end - end -end - - "" function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) @@ -786,75 +773,73 @@ end "" -function _map_eng2math_sourcebus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) +function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) # TODO create option for lossy vs lossless sourcebus connection for (name, eng_obj) in data_eng["voltage_source"] - if eng_obj["bus"] == data_eng["sourcebus"] - nconductors = data_math["conductors"] - - # TODO fix per unit problem - bus_obj = Dict{String,Any}( - "bus_i" => length(data_math["bus"])+1, - "index" => length(data_math["bus"])+1, - "name" => "_virtual_sourcebus", - "bus_type" => 3, - "vm" => eng_obj["vm"], - "va" => deg2rad.(eng_obj["va"]), - "vmin" => eng_obj["vm"], - "vmax" => eng_obj["vm"], - "basekv" => data_math["basekv"] - ) + nconductors = data_math["conductors"] - data_math["bus"]["$(bus_obj["index"])"] = bus_obj - - gen_obj = Dict{String,Any}( - "gen_bus" => bus_obj["bus_i"], - "name" => "_virtual_sourcebus", - "gen_status" => 1, - "pg" => fill(0.0, nconductors), - "qg" => fill(0.0, nconductors), - "model" => 2, - "startup" => 0.0, - "shutdown" => 0.0, - "ncost" => 3, - "cost" => [0.0, 1.0, 0.0], - "configuration" => "wye", # TODO change name to configuration - "index" => length(data_math["gen"]) + 1, - "source_id" => "vsource._virtual_sourcebus" - ) + # TODO fix per unit problem + bus_obj = Dict{String,Any}( + "bus_i" => length(data_math["bus"])+1, + "index" => length(data_math["bus"])+1, + "name" => "_virtual_bus.voltage_source.$name", + "bus_type" => 3, + "vm" => eng_obj["vm"], + "va" => deg2rad.(eng_obj["va"]), + "vmin" => eng_obj["vm"], + "vmax" => eng_obj["vm"], + "basekv" => data_math["basekv"] + ) - data_math["gen"]["$(gen_obj["index"])"] = gen_obj - - branch_obj = Dict{String,Any}( - "name" => "_virtual_sourcebus", - "source_id" => "vsource._virtual_sourcebus", - "f_bus" => bus_obj["bus_i"], - "t_bus" => data_math["bus_lookup"][data_eng["sourcebus"]], - "angmin" => fill(-60.0, nconductors), - "angmax" => fill( 60.0, nconductors), - "shift" => fill(0.0, nconductors), - "tap" => fill(1.0, nconductors), - "tranformer" => false, - "switch" => false, - "br_status" => 1, - "br_r" => eng_obj["rs"], - "br_x" => eng_obj["xs"], - "g_fr" => zeros(nconductors, nconductors), - "g_to" => zeros(nconductors, nconductors), - "b_fr" => zeros(nconductors, nconductors), - "b_to" => zeros(nconductors, nconductors), - "index" => length(data_math["branch"])+1 - ) + data_math["bus"]["$(bus_obj["index"])"] = bus_obj + + gen_obj = Dict{String,Any}( + "gen_bus" => bus_obj["bus_i"], + "name" => "_virtual_gen.voltage_source.$name", + "gen_status" => 1, + "pg" => fill(0.0, nconductors), + "qg" => fill(0.0, nconductors), + "model" => 2, + "startup" => 0.0, + "shutdown" => 0.0, + "ncost" => 3, + "cost" => [0.0, 1.0, 0.0], + "configuration" => "wye", + "index" => length(data_math["gen"]) + 1, + "source_id" => "_virtual_branch.$(eng_obj["source_id"])" + ) + + data_math["gen"]["$(gen_obj["index"])"] = gen_obj + + branch_obj = Dict{String,Any}( + "name" => "_virtual_branch.voltage_source.$name", + "source_id" => "_virtual_branch.$(eng_obj["source_id"])", + "f_bus" => bus_obj["bus_i"], + "t_bus" => data_math["bus_lookup"][data_eng["sourcebus"]], + "angmin" => fill(-60.0, nconductors), + "angmax" => fill( 60.0, nconductors), + "shift" => fill(0.0, nconductors), + "tap" => fill(1.0, nconductors), + "tranformer" => false, + "switch" => false, + "br_status" => 1, + "br_r" => eng_obj["rs"], + "br_x" => eng_obj["xs"], + "g_fr" => zeros(nconductors, nconductors), + "g_to" => zeros(nconductors, nconductors), + "b_fr" => zeros(nconductors, nconductors), + "b_to" => zeros(nconductors, nconductors), + "index" => length(data_math["branch"])+1 + ) - data_math["branch"]["$(branch_obj["index"])"] = branch_obj + data_math["branch"]["$(branch_obj["index"])"] = branch_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from_id => "voltage_source.$name", - :to_id => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], - :unmap_function => :_map_math2eng_sourcebus!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) - ) - end + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from_id => "voltage_source.$name", + :to_id => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], + :unmap_function => :_map_math2eng_sourcebus!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) + ) end end diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 3a23256d4..ab7997892 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -1,3 +1,21 @@ +const _dss_unsupported_commands = Vector{String}([ + "cleanup", "connect", "disconnect", "_docontrolactions", "_initsnap", + "_samplecontrols", "_showcontrolqueue", "_solvedirect", "solvenocontrol", + "_solvepflow", "?", "about", "addbusmarker", "alignfile", "allocateloads", + "batchedit", "buildy", "calcvoltagebases", "capacity", "cd", "cktlosses", + "clearbusmarkers", "close", "closedi", "comparecases", "currents", + "di_plot", "distribute", "doscmd", "dump", "estimate", "export", + "fileedit", "finishtimestep", "formedit", "get", "guids", "help", "init", + "interpolate", "losses", "makebuslist", "makeposseq", "nodelist", + "nodediff", "obfuscate", "open", "phaselosses", "plot", "powers", + "pstcalc", "reconductor", "reduce", "relcalc", "remove", "rephase", + "reprocessbuses", "reset", "rotate", "sample", "save", "select", + "seqcurrents", "seqpowers", "seqvoltages", "setkvbase", "show", "solve", + "summary", "totals", "updatestorage", "var", "variable", "varnames", + "vdiff", "visualize", "voltages", "yearlycurves", "ysc", "zsc", "zsc10", + "zscrefresh" +]) + const _linecode_properties = Vector{String}([ "nphases", "r1", "x1", "r0","x0", "c1", "c0", "units", "rmatrix", "xmatrix", "cmatrix", "basefreq", "normamps", "emergamps", "faultrate", @@ -12,7 +30,7 @@ const _linegeometry_properties = Vector{String}([ ]) const _linespacing_properties = Vector{String}([ - "nconds", "nphases", "x", "h", "units" + "nconds", "nphases", "x", "h", "units", "like" ]) const _loadshape_properties = Vector{String}([ @@ -29,16 +47,31 @@ const _tcc_curve_properties = Vector{String}([ "npts", "c_array", "t_array", "like" ]) -const _cndata_properties = Vector{String}([]) +const _cndata_properties = Vector{String}([ + "diacable", "diains", "diam", "diastrand", "emergamps", "epsr", "gmrac", + "gmrstrang", "gmrunits", "inslayer", "k", "like", "normamps", "rac", + "radius", "radunits", "rdc", "rstrand", "runits" +]) -const _tsdata_properties = Vector{String}([]) +const _tsdata_properties = Vector{String}([ + "diacable", "diains", "diam", "diashield", "emergamps", "epsr", "gmrac", + "gmrunits", "inslayer", "like", "normamps", "rac", "radius", "radunits", + "rdc", "runits", "taplap", "taplayer" +]) const _wiredata_properties = Vector{String}([ "rdc", "rac", "runits", "gmrac", "gmrunits", "radius", "radunits", "normamps", "emergamps", "diam", "like" ]) -const _xfmrcode_properties = Vector{String}([]) +const _xfmrcode_properties = Vector{String}([ + "phases", "windings", "wdg", "conn", "kv", "kva", "tap", "%r", "rneut", + "xneut", "conns", "kvs", "kvas", "taps", "%rs", "xhl", "xlt", "xht", + "xscarray", "thermal", "n", "m", "flrise", "hsrise", "%loadloss", + "%noloadloss", "%imag", "ppm_antifloat", "normhkva", "emerghkva", "sub", + "maxtap", "mintap", "numtaps", "subname", "xrconst", "leadlag", + "faultrate", "basefreq", "enabled", "like" +]) const _vsource_properties = Vector{String}([ "bus1", "bus2", "basekv", "pu", "angle", "frequency", "phases", "mvasc3", @@ -162,11 +195,33 @@ const _pvsystem_properties = Vector{String}([ "tduty", "class", "usermodel", "userdata", "debugtrace", "spectrum" ]) -const _relay_properties = Vector{String}([]) +const _recloser_properties = Vector{String}([ + "monitoredobj", "monitoredterm", "switchedobj", "switchedterm", "numfast", + "phasefast", "phasedelayed", "groundfast", "grounddelayed", "phasetrip", + "groundtrip", "phaseinst", "groundinst", "reset", "shots", + "recloseintervals", "delay", "action", "tdphfast", "tdgrfast", + "tdphdelayed", "tdgrdelayed", "basefreq", "enabled", "like" +]) -const _recloser_properties = Vector{String}([]) +const _relay_properties = Vector{String}([ + "monitoredobj", "monitoredterm", "switchedobj", "switchedterm", "type", + "phasecurve", "groundcurve", "phasetrip", "groundtrip", "tdphase", + "tdground", "phaseinst", "groundinst", "reset", "shots", + "recloseintervals", "delay", "overvoltcurve", "undervoltcurve", + "kvbase", "47%pickup", "46baseamps", "46%pickup", "46isqt", + "variable", "overtrip", "undertrip", "breakertime", "action", "basefreq", + "enabled" +]) -const _fuse_properties = Vector{String}([]) +const _fuse_properties = Vector{String}([ + "monitoredobj", "monitoredterm", "switchedobj", "switchedterm", + "fusecurve", "ratedcurrent", "delay", "action", "basefreq", "enabled" +]) + +const _swtcontrol_properties = Vector{String}([ + "action", "basefreq", "delay", "enabled", "like", "lock", "normal", + "reset", "state", "switchedobj", "switchedterm" +]) const _dss_object_properties = Dict{String,Vector{String}}( "linecode" => _linecode_properties, @@ -199,7 +254,8 @@ const _dss_object_properties = Dict{String,Vector{String}}( "pvsystem" => _pvsystem_properties, "relay" => _relay_properties, "recloser" => _reactor_properties, - "fuse" => _fuse_properties + "fuse" => _fuse_properties, + "swtcontrol" => _swtcontrol_properties, ) @@ -486,25 +542,15 @@ end function _add_component_edits!(data_dss::Dict{String,<:Any}, obj_type_name::AbstractString, object::Dict{String,<:Any}) obj_type = split(obj_type_name, '.'; limit=2)[1] - if obj_type == "vsource" && object["name"] == "source" - delete!(object, "name") - - if !haskey(data_dss, "circuit") - data_dss["circuit"] = object - else - merge!(data_dss["circuit"], object) - end + if !haskey(data_dss, obj_type) + data_dss[obj_type] = Dict{String,Any}( + object["name"] => object + ) else - if !haskey(data_dss, obj_type) - data_dss[obj_type] = Dict{String,Any}( - object["name"] => object - ) + if !haskey(data_dss[obj_type], object["name"]) + data_dss[obj_type][object["name"]] = object else - if !haskey(data_dss[obj_type], object["name"]) - data_dss[obj_type][object["name"]] = object - else - merge!(data_dss[obj_type][object["name"]], object) - end + merge!(data_dss[obj_type][object["name"]], object) end end end @@ -610,8 +656,12 @@ OpenDSS commands inside the originating file. """ function _merge_dss!(dss_prime::Dict{String,<:Any}, dss_to_add::Dict{String,<:Any}) for (k, v) in dss_to_add - if k in keys(dss_prime) && isa(v, Dict) - merge!(dss_prime[k], v) + if k in keys(dss_prime) && (isa(v, Dict) || isa(v, Set)) + if isa(v, Dict) + merge!(dss_prime[k], v) + elseif isa(v, Set) + union!(dss_prime[k], v) + end else dss_prime[k] = v end @@ -678,12 +728,12 @@ Will also parse files defined inside of the originating DSS file via the """ function parse_dss(io::IOStream)::Dict{String,Any} filename = match(r"^$", io.name).captures[1] - Memento.info(_LOGGER, "Calling parse_dss on $filename") + # Memento.info(_LOGGER, "Calling parse_dss on $filename") current_file = split(filename, "/")[end] path = join(split(filename, '/')[1:end-1], '/') data_dss = Dict{String,Any}() - data_dss["filename"] = [string(current_file)] + data_dss["filename"] = Set{String}([string(current_file)]) current_obj = Dict{String,Any}() current_obj_type = "" @@ -697,7 +747,11 @@ function parse_dss(io::IOStream)::Dict{String,Any} real_line_num = findall(lines .== line)[1] line = _strip_comments(line) - if startswith(strip(line), '~') + if startswith(strip(line), '~') || startswith(strip(lowercase(line)), "more") + if startswith(strip(lowercase(line)), "more") + line = lowercase(strip(line)[5:end]) + end + current_obj = _parse_component(current_obj_type, strip(strip(lowercase(line)), '~'), current_obj) if n < nlines && startswith(strip(stripped_lines[n + 1]), '~') @@ -710,23 +764,23 @@ function parse_dss(io::IOStream)::Dict{String,Any} line_elements = split(line, r"\s+"; limit=3) cmd = lowercase(line_elements[1]) - if cmd == "clear" - Memento.info(_LOGGER, "`data_dss` has been reset with the \"clear\" command.") - data_dss = Dict{String,Any}("filename"=>data_dss["filename"]) - continue + if cmd in _dss_unsupported_commands + Memento.warn(_LOGGER, "Command \"$cmd\" on line $real_line_num in \"$current_file\" is not supported, skipping.") - elseif cmd == "redirect" - file = line_elements[2] - full_path = path == "" ? file : join([path, file], '/') - Memento.info(_LOGGER, "Redirecting to file \"$file\"") - _merge_dss!(data_dss, parse_dss(full_path)) + elseif cmd == "clear" + Memento.info(_LOGGER, "Circuit has been reset with the \"clear\" on line $real_line_num in \"$current_file\"") + data_dss = Dict{String,Any}("filename"=>data_dss["filename"]) continue - elseif cmd == "compile" + elseif cmd in ["redirect", "compile"] file = split(strip(line_elements[2], ['(',')']), '\\')[end] - full_path = path == "" ? file : join([path, file], '/') - Memento.info(_LOGGER, "Compiling file \"$file\"") - _merge_dss!(data_dss, parse_dss(full_path)) + + if !(file in data_dss["filename"]) + full_path = path == "" ? file : join([path, file], '/') + Memento.info(_LOGGER, "Redirecting to \"$file\" on line $real_line_num in \"$current_file\"") + _merge_dss!(data_dss, parse_dss(full_path)) + end + continue elseif cmd == "set" @@ -750,9 +804,18 @@ function parse_dss(io::IOStream)::Dict{String,Any} _add_component_edits!(data_dss, current_obj_type, current_obj) continue - elseif cmd == "buscoords" + elseif cmd in ["disable", "enable"] + current_obj_type, current_obj_name = split(join(line_element[2:end], ""), ".") + + enabled = cmd == "enable" ? "true" : "false" + + _add_component_edits!(data_dss, current_obj_type, Dict{String,Any}("name"=>current_obj_name, "enabled"=>enabled)) + continue + + elseif cmd in ["buscoords", "latloncoords"] file = line_elements[2] full_path = path == "" ? file : join([path, file], '/') + Memento.info(_LOGGER, "Reading Buscoords in \"$file\" on line $real_line_num in \"$current_file\"") data_dss["buscoords"] = _parse_buscoords_file(full_path) elseif cmd == "new" @@ -761,28 +824,34 @@ function parse_dss(io::IOStream)::Dict{String,Any} if startswith(current_obj_type, "loadshape") _parse_loadshape!(current_obj; path=path) end - else - try - obj_type, obj_name, props = split(lowercase(line), '.'; limit=3) - parsed_properties = _parse_properties(props) - wdg = "" - for prop in parsed_properties - property_name, property_value = split(prop, '=') - if obj_type == "transformer" - wdg = property_name == "wdg" && property_value != "1" ? property_value : property_name == "wdg" && property_value == "1" ? "" : wdg - - if property_name in ["wdg", "bus", "conn", "kv", "kva", "tap", "%r", "rneut", "xneut"] - property_name = join(filter(p->!isempty(p), [property_name, wdg]), "_") - end - - _assign_property!(data_dss, obj_type, obj_name, property_name, property_value) - else - _assign_property!(data_dss, obj_type, obj_name, property_name, property_value) + + if startswith(current_obj_type, "circuit") + current_obj_type = "vsource.source" + + data_dss["circuit"] = current_obj["name"] + + current_obj["name"] = "source" + end + elseif split(cmd, '.')[1] in keys(_dss_object_properties) + obj_type, obj_name, props = split(lowercase(line), '.'; limit=3) + parsed_properties = _parse_properties(props) + wdg = "" + for prop in parsed_properties + property_name, property_value = split(prop, '=') + if obj_type == "transformer" + wdg = property_name == "wdg" && property_value != "1" ? property_value : property_name == "wdg" && property_value == "1" ? "" : wdg + + if property_name in ["wdg", "bus", "conn", "kv", "kva", "tap", "%r", "rneut", "xneut"] + property_name = join(filter(p->!isempty(p), [property_name, wdg]), "_") end + + _assign_property!(data_dss, obj_type, obj_name, property_name, property_value) + else + _assign_property!(data_dss, obj_type, obj_name, property_name, property_value) end - catch - Memento.warn(_LOGGER, "Command \"$cmd\" on line $real_line_num in \"$current_file\" is not supported, skipping.") end + else + Memento.warn(_LOGGER, "Command \"$cmd\" on line $real_line_num in \"$current_file\" is not recognized, skipping.") end if n < nlines && startswith(strip(stripped_lines[n + 1]), '~') @@ -795,13 +864,9 @@ function parse_dss(io::IOStream)::Dict{String,Any} end end - Memento.info(_LOGGER, "Done parsing $filename") - parse_dss_with_dtypes!(data_dss) - parse_dss_options!(data_dss) - - data_dss["source_type"] = "dss" + data_dss["data_model"] = "dss" return data_dss end diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index 13fb1b7bb..882a1921d 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -898,6 +898,119 @@ function _create_transformer(name::AbstractString=""; kwargs...) end +"Transformer codes contain all of the same properties as a transformer except bus, buses, bank, xfmrcode" +function _create_xfmrcode(name::AbstractString=""; kwargs...) + kwargs = Dict{Symbol,Any}(kwargs) + windings = isempty(name) ? 3 : get(kwargs, :windings, 2) + phases = get(kwargs, :phases, 3) + + prcnt_rs = fill(0.2, windings) + if haskey(kwargs, Symbol("%rs")) + prcnt_rs = kwargs[Symbol("%rs")] + elseif haskey(kwargs, Symbol("%loadloss")) + prcnt_rs[1] = prcnt_rs[2] = kwargs[Symbol("%loadloss")] / 2.0 + end + + temp = Dict{String,Any}( + "taps" => get(kwargs, :taps, fill(1.0, windings)), + "conns" => get(kwargs, :conns, fill("wye", windings)), + "kvs" => get(kwargs, :kvs, fill(12.47, windings)), + "kvas" => get(kwargs, :kvas, fill(10.0, windings)), + "%rs" => prcnt_rs, + "rneuts" => fill(0.0, windings), + "xneuts" => fill(0.0, windings) + ) + + for wdg in [:wdg, :wdg_2, :wdg_3] + if haskey(kwargs, wdg) + suffix = kwargs[wdg] == 1 ? "" : "_$(kwargs[wdg])" + for key in [:bus, :tap, :conn, :kv, :kva, Symbol("%r"), :rneut, :xneut] + subkey = Symbol(string(key, suffix)) + if haskey(kwargs, subkey) + temp[string(key, "s")][kwargs[wdg]] = kwargs[subkey] + end + end + end + end + + xfmrcode = Dict{String,Any}( + "name" => name, + "phases" => phases, + "windings" => windings, + # Per wdg + "wdg" => 1, + "conn" => temp["conns"][1], + "kv" => temp["kvs"][1], + "kva" => temp["kvas"][1], + "tap" => temp["taps"][1], + "%r" => temp["%rs"][1], + "rneut" => temp["rneuts"][1], + "xneut" => temp["xneuts"][1], + + "wdg_2" => 2, + "conn_2" => temp["conns"][2], + "kv_2" => temp["kvs"][2], + "kva_2" => temp["kvas"][2], + "tap_2" => temp["taps"][2], + "%r_2" => temp["%rs"][2], + "rneut_2" => temp["rneuts"][2], + "xneut_2" => temp["xneuts"][2], + + # General + "conns" => temp["conns"], + "kvs" => temp["kvs"], + "kvas" => temp["kvas"], + "taps" => temp["taps"], + "xhl" => get(kwargs, :xhl, 7.0), + "xht" => get(kwargs, :xht, 35.0), + "xlt" => get(kwargs, :xlt, 30.0), + "xscarray" => get(kwargs, :xscarry, ""), + "thermal" => get(kwargs, :thermal, 2.0), + "n" => get(kwargs, :n, 0.8), + "m" => get(kwargs, :m, 0.8), + "flrise" => get(kwargs, :flrise, 65.0), + "hsrise" => get(kwargs, :hsrise, 15.0), + "%loadloss" => get(kwargs, Symbol("%loadloss"), sum(temp["%rs"][1:2])), + "%noloadloss" => get(kwargs, Symbol("%noloadloss"), 0.0), + "normhkva" => get(kwargs, :normhkva, 1.1 * temp["kvas"][1]), + "emerghkva" => get(kwargs, :emerghkva, 1.5 * temp["kvas"][1]), + "sub" => get(kwargs, :sub, false), + "maxtap" => get(kwargs, :maxtap, 1.10), + "mintap" => get(kwargs, :mintap, 0.90), + "numtaps" => get(kwargs, :numtaps, 32), + "subname" => get(kwargs, :subname, ""), + "%imag" => get(kwargs, Symbol("%imag"), 0.0), + "ppm_antifloat" => get(kwargs, :ppm_antifloat, 1.0), + "%rs" => temp["%rs"], + "xrconst" => get(kwargs, :xrconst, false), + "x12" => get(kwargs, :xhl, 7.0), + "x13" => get(kwargs, :xht, 35.0), + "x23" => get(kwargs, :xlt, 30.0), + "leadlag" => get(kwargs, :leadlag, "lag"), + # Inherited Properties + "faultrate" => get(kwargs, :faultrate, 0.1), + "basefreq" => get(kwargs, :basefreq, 60.0), + "enabled" => get(kwargs, :enabled, true) + ) + + if windings == 3 + xfmrcode3 = Dict{String,Any}( + "wdg_3" => 3, + "conn_3" => temp["conns"][3], + "kv_3" => temp["kvs"][3], + "kva_3" => temp["kvas"][3], + "tap_3" => temp["taps"][3], + "%r_3" => temp["%rs"][3], + "rneut_3" => temp["rneuts"][3], + "xneut_3" => temp["xneuts"][3], + ) + + merge!(xfmrcode, xfmrcode3) + end + + return xfmrcode +end + """ Creates a Dict{String,Any} containing all of the expected properties for a @@ -1076,5 +1189,113 @@ function _create_loadshape(name::AbstractString=""; kwargs...) end +"" +function _create_options(; kwargs...) + kwargs = Dict{Symbol,Any}(kwargs) + + Dict{String,Any}( + "%growth" => get(kwargs, Symbol("%growth"), 2.5), + "%mean" => get(kwargs, Symbol("%mean"), 65.0), + "%normal" => get(kwargs, Symbol("%normal"), 100.0), + "%stddev" => get(kwargs, Symbol("%stddev"), 9.0), + "addtype" => get(kwargs, :addtype, "generator"), + "algorithm" => get(kwargs, :algorithm, "newton"), + "allocationfactors" => get(kwargs, :allocationfactors, ""), + "allowduplicates" => get(kwargs, :allowduplicates, false), + "autobuslist" => get(kwargs, :autobuslist, Vector{String}([])), + "basefrequency" => get(kwargs, :basefrequency, 60.0), + "bus" => get(kwargs, :bus, ""), + "capkvar" => get(kwargs, :capkvar, 600.0), + "casename" => get(kwargs, :casename, ""), + "capmarkercode" => get(kwargs, :capmarkercode, 37), + "capmarkersize" => get(kwargs, :capmarkersize, 3), + "cfactors" => get(kwargs, :cfactors, 4.0), + "circuit" => get(kwargs, :circuit, ""), + "cktmodel" => get(kwargs, :cktmodel, "multiphase"), + "class" => get(kwargs, :class, ""), + "controlmode" => get(kwargs, :controlmode, "static"), + "datapath" => get(kwargs, :datapath, ""), + "defaultbasefrequency" => get(kwargs, :defaultbasefrequency, 60.0), + "defaultbasefreq" => get(kwargs, :defaultbasefreq, 60.0), # Alias to defaultbasefrequency + "defaultdaily" => get(kwargs, :defaultdaily, "default"), + "defaultyearly" => get(kwargs, :defaultyearly, "default"), + "demandinterval" => get(kwargs, :demandinterval, false), + "diverbose" => get(kwargs, :diverbose, false), + "dssvisualizationtool" => get(kwargs, :dssvisualizationtool, ""), + "earthmodel" => get(kwargs, :earthmodel, "deri"), + "editor" => get(kwargs, :editor, "notepad"), + "element" => get(kwargs, :element, ""), + "emergvmaxpu" => get(kwargs, :emergvmaxpu, 1.08), + "emergvminpu" => get(kwargs, :emergvminpu, 0.90), + "frequency" => get(kwargs, :frequency, 60.0), + "genkw" => get(kwargs, :genkw, 1000.0), + "genmult" => get(kwargs, :genmult, 1.0), + "h" => get(kwargs, :h, ""), + "harmonics" => get(kwargs, :harmonics, "all"), + "hour" => get(kwargs, :hour, 1.0), + "keeplist" => get(kwargs, :keeplist, Vector{String}([])), + "ldcurve" => get(kwargs, :ldcurve, "nil"), + "loadmodel" => get(kwargs, :loadmodel, "admittance"), + "loadmult" => get(kwargs, :loadmult, 1.0), + "log" => get(kwargs, :log, false), + "lossregs" => get(kwargs, :lossregs, 13), + "lossweight" => get(kwargs, :lossweight, 1.0), + "markercode" => get(kwargs, :markercode, 0), + "markswitches" => get(kwargs, :markswitches, false), + "markcapacitors" => get(kwargs, :markcapacitors, false), + "markpvsystems" => get(kwargs, :markpvsystems, false), + "markregulators" => get(kwargs, :markregulators, false), + "markstorage" => get(kwargs, :markstorage, false), + "marktransformers" => get(kwargs, :marktransformers, false), + "maxcontroliter" => get(kwargs, :maxcontroliter, 10), + "maxiter" => get(kwargs, :maxiter, 15), + "miniterations" => get(kwargs, :miniterations, 2), + "mode" => get(kwargs, :mode, "Snap"), + "name" => get(kwargs, :name, ""), + "nodewidth" => get(kwargs, :nodewidth, 1), + "normvmaxpu" => get(kwargs, :normvmaxpu, 1.05), + "normvminpu" => get(kwargs, :normvminpu, 0.95), + "numallociterations" => get(kwargs, :numallociterations, 2), + "number" => get(kwargs, :number, 0), + "object" => get(kwargs, :object, ""), + "overloadreport" => get(kwargs, :overloadreport, false), + "neglectloady" => get(kwargs, :neglectloady, false), + "pricecurve" => get(kwargs, :pricecurve, ""), + "pricesignal" => get(kwargs, :pricesignal, 25), + "pvmarkercode" => get(kwargs, :pvmarkercode, 15), + "pvmarkersize" => get(kwargs, :pvmarkersize, 1), + "random" => get(kwargs, :random, "uniform"), + "recorder" => get(kwargs, :recorder, false), + "reduceoption" => get(kwargs, :reduceoption, "default"), + "registryupdate" => get(kwargs, :registryupdate, true), + "regmarkercode" => get(kwargs, :regmarkercode, 47), + "regmarkersize" => get(kwargs, :regmarkersize, 1), + "sampleenergymeters" => get(kwargs, :sampleenergymeters, false), + "sec" => get(kwargs, :sec, 0.0), + "showexport" => get(kwargs, :showexport, false), + "stepsize" => get(kwargs, :stepsize, "1h"), + "switchmarkercode" => get(kwargs, :switchmarkercode, 4), + "terminal" => get(kwargs, :terminal, ""), + "time" => get(kwargs, :time, Vector{Float64}([0.0, 0.0])), + "tolerance" => get(kwargs, :tolerance, 0.0001), + "totaltime" => get(kwargs, :totaltime, 0.0), + "tracecontrol" => get(kwargs, :tracecontrol, false), + "transmarkercode" => get(kwargs, :transmarkercode, 35), + "transmarkersize" => get(kwargs, :transmarkersize, 1), + "storemarkercode" => get(kwargs, :storemarkercode, 9), + "storemarkersize" => get(kwargs, :storemarkersize, 1), + "trapezoidal" => get(kwargs, :trapezoidal, false), + "type" => get(kwargs, :type, ""), + "ueregs" => get(kwargs, :ueregs, 11), + "ueweight" => get(kwargs, :ueweight, 1.0), + "voltagebases" => get(kwargs, :voltagebases, Vector{Float64}([])), + "voltexceptionreport" => get(kwargs, :voltexceptionreport, false), + "year" => get(kwargs, :year, 0), + "zonelock" => get(kwargs, :zonelock, false), + ) +end + + + "Returns a Dict{String,Type} for the desired component `comp`, giving all of the expected data types" const _dss_parameter_data_types = Dict{String,Dict{String,Type}}((comp, Dict{String,Type}((k, typeof(v)) for (k,v) in @eval $(Symbol("_create_$comp"))())) for comp in _dss_supported_components) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 154180843..d4975a8c0 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -367,6 +367,43 @@ function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< if !haskey(data_eng, "vsource") data_eng["vsource"] = Dict{String,Dict{String,Any}}() + + defaults = _create_vsource(get(dss_obj, "bus1", "sourcebus"), name; _to_kwargs(dss_obj)...) + + ph1_ang = defaults["angle"] + vm_pu = defaults["pu"] + + phases = defaults["phases"] + vnom = data_eng["settings"]["set_vbase_val"] + + vm = fill(vm_pu, phases)*vnom + va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) + + eng_obj = Dict{String,Any}( + "bus" => _parse_busname(defaults["bus1"])[1], + "connections" => collect(1:phases), + "vm" => vm, + "va" => va, + "rs" => defaults["rmatrix"], + "xs" => defaults["xmatrix"], + "source_id" => "vsource.$name", + "status" => 1 + ) + + # if the ground is used directly, register load + if 0 in eng_obj["connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) + end + + if import_all + _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + end + + if !haskey(data_eng, "voltage_source") + data_eng["voltage_source"] = Dict{String,Any}() + end + + data_eng["voltage_source"][name] = eng_obj end data_eng["vsource"][name] = eng_obj @@ -753,47 +790,6 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< end -"Adds sourcebus as a voltage source to `data_eng` from `data_dss`" -function _dss2eng_sourcebus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) - circuit = _create_vsource(get(data_dss["circuit"], "bus1", "sourcebus"), data_dss["circuit"]["name"]; _to_kwargs(data_dss["circuit"])...) - - nodes = Array{Bool}([1 1 1 0]) - ph1_ang = circuit["angle"] - vm_pu = circuit["pu"] - - phases = circuit["phases"] - vnom = data_eng["settings"]["set_vbase_val"] - - vm = fill(vm_pu, phases)*vnom - va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) - - eng_obj = Dict{String,Any}( - "bus" => _parse_busname(circuit["bus1"])[1], - "connections" => collect(1:phases), - "vm" => vm, - "va" => va, - "rs" => circuit["rmatrix"], - "xs" => circuit["xmatrix"], - "status" => 1 - ) - - # if the ground is used directly, register load - if 0 in eng_obj["connections"] - _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) - end - - if import_all - _import_all!(eng_obj, circuit, data_dss["circuit"]["prop_order"]) - end - - if !haskey(data_eng, "voltage_source") - data_eng["voltage_source"] = Dict{String,Any}() - end - - data_eng["voltage_source"][circuit["name"]] = eng_obj -end - - "Parses a DSS file into a PowerModels usable format" function parse_opendss(io::IOStream; import_all::Bool=false, bank_transformers::Bool=true)::Dict data_dss = parse_dss(io) @@ -805,7 +801,7 @@ end "Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, bank_transformers::Bool=true)::Dict{String,Any} data_eng = Dict{String,Any}( - "source_type" => data_dss["source_type"], + "data_model" => "engineering", "settings" => Dict{String,Any}(), ) @@ -813,11 +809,11 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban data_eng["dss_options"] = data_dss["options"] end - if haskey(data_dss, "circuit") - circuit = data_dss["circuit"] - defaults = _create_vsource(get(circuit, "bus1", "sourcebus"), circuit["name"]; _to_kwargs(circuit)...) + if haskey(data_dss, "vsource") && haskey(data_dss["vsource"], "source") && haskey(data_dss, "circuit") + source = data_dss["vsource"]["source"] + defaults = _create_vsource(get(source, "bus1", "sourcebus"), source["name"]; _to_kwargs(source)...) - data_eng["name"] = circuit["name"] + data_eng["name"] = data_dss["circuit"] data_eng["sourcebus"] = defaults["bus1"] data_eng["data_model"] = "engineering" @@ -852,13 +848,12 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban _dss2eng_capacitor_to_shunt!(data_eng, data_dss, import_all) _dss2eng_shunt_reactor!(data_eng, data_dss, import_all) - _dss2eng_sourcebus!(data_eng, data_dss, import_all) + # _dss2eng_sourcebus!(data_eng, data_dss, import_all) + _dss2eng_vsource!(data_eng, data_dss, import_all) _dss2eng_generator!(data_eng, data_dss, import_all) _dss2eng_pvsystem!(data_eng, data_dss, import_all) _dss2eng_storage!(data_eng, data_dss, import_all) - _dss2eng_vsource!(data_eng, data_dss, import_all) - _discover_terminals!(data_eng) if bank_transformers diff --git a/src/io/utils.jl b/src/io/utils.jl index 3c783ff83..e059d02f4 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -2,7 +2,11 @@ const _dss_edge_components = ["line", "transformer", "reactor"] "components currently supported for automatic data type parsing" -const _dss_supported_components = ["line", "linecode", "load", "generator", "capacitor", "reactor", "circuit", "transformer", "pvsystem", "storage", "loadshape"] +const _dss_supported_components = [ + "line", "linecode", "load", "generator", "capacitor", "reactor", + "transformer", "pvsystem", "storage", "loadshape", "options", + "xfmrcode", "vsource", +] "two number operators for reverse polish notation" _double_operators = Dict( @@ -654,7 +658,7 @@ function parse_dss_with_dtypes!(data_dss::Dict{String,<:Any}, to_parse::Array{St for obj_type in to_parse if haskey(data_dss, obj_type) dtypes = _dss_parameter_data_types[obj_type] - if obj_type == "circuit" + if obj_type in ["circuit", "options"] _parse_obj_dtypes!(obj_type, data_dss[obj_type], dtypes) else for object in values(data_dss[obj_type]) From 009a097c180f6a2a93c7ad18389b0664a52cc95c Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 11 Mar 2020 11:15:19 -0600 Subject: [PATCH 089/224] FIX: opendss tests Fixes that enable the previous opendss tests to pass Some tests were no longer valid, and therefore removed. For the most part, the pointers to the correct items needed to be changed, but not the values. Still not passing are - "opendss parse load model errors" - "opendss parse generic branch values verification" - "test json parser" Adds line_reactor, which currently parses to a virutal branch --- src/data_model/eng2math.jl | 91 +++++- src/data_model/utils.jl | 10 +- src/io/dss_parse.jl | 4 +- src/io/dss_structs.jl | 53 ++-- src/io/opendss.jl | 93 +++---- src/io/utils.jl | 59 ++-- test/data/opendss/test2_Linecodes.dss | 4 +- test/data/opendss/test2_master.dss | 12 +- .../opendss/test_transformer_formatting.dss | 4 +- test/opendss.jl | 263 ++++++++---------- test/runtests.jl | 2 +- 11 files changed, 322 insertions(+), 273 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 410df9178..04e137430 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -4,13 +4,14 @@ import LinearAlgebra: diagm const _1to1_maps = Dict{String,Vector{String}}( "bus" => ["vm", "va", "vmin", "vmax"], "load" => ["model", "configuration", "status", "source_id"], - "capacitor" => ["status"], - "shunt" => ["status"], + "capacitor" => ["status", "source_id"], + "shunt" => ["status", "source_id"], "shunt_reactor" => ["status", "source_id"], "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id", "configuration"], "pvsystem" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], "storage" => ["status", "source_id"], "line" => ["source_id"], + "line_reactor" => ["source_id"], "switch" => ["source_id"], "line_reactor" => ["source_id"], "transformer" => ["source_id"], @@ -78,6 +79,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_vsource!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) @@ -202,7 +204,7 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any if kron_reduced if math_obj["configuration"]=="wye" @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pd", "qd"], connections[1:end-1], kr_phases) + _pad_properties!(math_obj, ["pd", "qd"], connections[connections.!=kr_neutral], kr_phases) else _pad_properties_delta!(math_obj, ["pd", "qd"], connections, kr_phases) end @@ -346,7 +348,13 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D math_obj["gs"] = fill(0.0, nphases, nphases) math_obj["bs"] = diagm(0=>fill(Gcap, nphases)) - _pad_properties!(math_obj, ["gs", "bs"], connections, collect(1:nconductors)) + if kron_reduced + if eng_obj["configuration"] == "wye" + _pad_properties!(math_obj, ["gs", "bs"], connections[1:end-1], kr_phases) + else + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + end + end data_math["shunt"]["$(math_obj["index"])"] = math_obj @@ -399,7 +407,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ # if PV generator mode convert attached bus to PV bus if eng_obj["control_model"] == 3 - data_math["bus"][data_math["bus_lookup"][eng_obj["bus"]]]["bus_type"] = 2 + data_math["bus"]["$(data_math["bus_lookup"][eng_obj["bus"]])"]["bus_type"] = 2 end data_math["gen"]["$(math_obj["index"])"] = math_obj @@ -513,7 +521,7 @@ end "" function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) - if haskey(eng_obj, "linecode") + if haskey(eng_obj, "linecode") && haskey(data_eng, "linecode") && haskey(data_eng["linecode"], eng_obj["linecode"]) linecode = data_eng["linecode"][eng_obj["linecode"]] for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] @@ -524,6 +532,7 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any end math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) + math_obj["name"] = name nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] @@ -549,7 +558,6 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any f_bus = data_eng["bus"][eng_obj["f_bus"]] t_bus = data_eng["bus"][eng_obj["t_bus"]] - # neutral = haskey(f_bus, "neutral") ? f_bus["neutral"] : haskey(t_bus, "neutral") ? t_bus["neutral"] : nphases+1 if kron_reduced @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") @@ -574,7 +582,7 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => "line.$name", :to => "branch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_load!, + :unmap_function => :_map_math2eng_line!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["line"]) ) @@ -582,6 +590,69 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any end +"" +function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + # TODO support line reactors natively, currently treated like branches + for (name, eng_obj) in get(data_eng, "line_reactor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) + math_obj["name"] = "_virtual_branch.$(eng_obj["source_id"])" + + nphases = length(eng_obj["f_connections"]) + nconductors = data_math["conductors"] + + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] + + math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] + math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] + + math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) + math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) + + math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) + math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) + + math_obj["angmin"] = fill(-60.0, nphases) + math_obj["angmax"] = fill( 60.0, nphases) + + math_obj["transformer"] = false + math_obj["shift"] = zeros(nphases) + math_obj["tap"] = ones(nphases) + + f_bus = data_eng["bus"][eng_obj["f_bus"]] + t_bus = data_eng["bus"][eng_obj["t_bus"]] + + if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") + filter = _kron_reduce_branch!(math_obj, + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], + eng_obj["f_connections"], kr_neutral + ) + _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) + connections = eng_obj["f_connections"][filter] + _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + else + math_obj["f_connections"] = eng_obj["f_connections"] + math_obj["f_connections"] = eng_obj["t_connections"] + end + + math_obj["switch"] = false + + math_obj["br_status"] = eng_obj["status"] + + data_math["branch"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => "line_reactor.$name", + :to => "branch.$(math_obj["index"])", + :unmap_function => :_map_math2eng_line_reactor!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["line_reactor"]) + ) + end +end + + "" function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) # TODO enable real switches (right now only using vitual lines) @@ -733,9 +804,9 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] - transformer_2wa_obj = Dict{String,Any}( "name" => "_virtual_transformer.$name.$w", + "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], "t_bus" => transformer_t_bus_w[w], "tm_nom" => tm_nom, @@ -806,7 +877,7 @@ function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<: "cost" => [0.0, 1.0, 0.0], "configuration" => "wye", "index" => length(data_math["gen"]) + 1, - "source_id" => "_virtual_branch.$(eng_obj["source_id"])" + "source_id" => "_virtual_gen.$(eng_obj["source_id"])" ) data_math["gen"]["$(gen_obj["index"])"] = gen_obj diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 3869ae274..a75060b06 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -375,7 +375,9 @@ function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::Str "angmin" => fill(-60.0, nphases), "angmax" => fill( 60.0, nphases), "shift" => zeros(nphases), - "tap" => ones(nphases) + "tap" => ones(nphases), + "switch" => false, + "transformer" => false, ) data_math["branch"]["$(branch_obj["index"])"] = branch_obj @@ -415,8 +417,10 @@ function _kron_reduce_branch(Zs, Ys, terminals, neutral) n = _get_ilocs(terminals_kr, neutral)[1] P = setdiff(collect(1:length(terminals_kr)), n) - Zs_kr = [Z[P,P]-(1/Z[n,n])*Z[P,[n]]*Z[[n],P] for Z in Zs_kr] - Ys_kr = [Y[P,P] for Y in Ys_kr] + if all(size(Z) == (length(terminals_kr), length(terminals_kr)) for Z in Zs_kr) + Zs_kr = [Z[P,P]-(1/Z[n,n])*Z[P,[n]]*Z[[n],P] for Z in Zs_kr] + Ys_kr = [Y[P,P] for Y in Ys_kr] + end terminals_kr = terminals_kr[P] end diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index ab7997892..bc11496cb 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -564,7 +564,7 @@ new value is appended to the end. """ function _add_property(object::Dict, key::AbstractString, value::Any)::Dict if !haskey(object, "prop_order") - object["prop_order"] = Array{String,1}(["name"]) + object["prop_order"] = Vector{String}(["name"]) end current_wdg = "wdg" in object["prop_order"] ? string(filter(p->occursin("wdg", p), object["prop_order"])[end][end]) : "" @@ -585,7 +585,7 @@ function _add_property(object::Dict, key::AbstractString, value::Any)::Dict end object[lowercase(key)] = value - push!(object["prop_order"], lowercase(key)) + push!(object["prop_order"], string(lowercase(key))) return object end diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index 882a1921d..afac36de3 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -78,32 +78,33 @@ function _create_linecode(name::AbstractString=""; kwargs...)::Dict{String,Any} units = get(kwargs, :units, "none") - return Dict{String,Any}("name" => name, - "nphases" => phases, - "r1" => r1 / _convert_to_meters[units], - "x1" => x1 / _convert_to_meters[units], - "r0" => r0 / _convert_to_meters[units], - "x0" => x0 / _convert_to_meters[units], - "c1" => c1 / _convert_to_meters[units], - "c0" => c0 / _convert_to_meters[units], - "units" => "m", - "rmatrix" => rmatrix / _convert_to_meters[units], - "xmatrix" => xmatrix / _convert_to_meters[units], - "cmatrix" => cmatrix / _convert_to_meters[units], - "basefreq" => basefreq, - "normamps" => get(kwargs, :normamps, 400.0), - "emergamps" => get(kwargs, :emergamps, 600.0), - "faultrate" => get(kwargs, :faultrate, 0.1), - "pctperm" => get(kwargs, :pctperm, 20.0), - "repair" => get(kwargs, :repair, 3.0), - "kron" => get(kwargs, :kron, false), - "rg" => get(kwargs, :rg, 0.01805), - "xg" => get(kwargs, :xg, 0.155081), - "rho" => get(kwargs, :rho, 100.0), - "neutral" => get(kwargs, :neutral, 3), - "b1" => b1 / _convert_to_meters[units], - "b0" => b0 / _convert_to_meters[units] - ) + Dict{String,Any}( + "name" => name, + "nphases" => phases, + "r1" => r1 / _convert_to_meters[units], + "x1" => x1 / _convert_to_meters[units], + "r0" => r0 / _convert_to_meters[units], + "x0" => x0 / _convert_to_meters[units], + "c1" => c1 / _convert_to_meters[units], + "c0" => c0 / _convert_to_meters[units], + "units" => "m", + "rmatrix" => rmatrix / _convert_to_meters[units], + "xmatrix" => xmatrix / _convert_to_meters[units], + "cmatrix" => cmatrix / _convert_to_meters[units], + "basefreq" => basefreq, + "normamps" => get(kwargs, :normamps, 400.0), + "emergamps" => get(kwargs, :emergamps, 600.0), + "faultrate" => get(kwargs, :faultrate, 0.1), + "pctperm" => get(kwargs, :pctperm, 20.0), + "repair" => get(kwargs, :repair, 3.0), + "kron" => get(kwargs, :kron, false), + "rg" => get(kwargs, :rg, 0.01805), + "xg" => get(kwargs, :xg, 0.155081), + "rho" => get(kwargs, :rho, 100.0), + "neutral" => get(kwargs, :neutral, 3), + "b1" => b1 / _convert_to_meters[units], + "b0" => b0 / _convert_to_meters[units] + ) end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index d4975a8c0..6355b1c14 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -50,7 +50,7 @@ function _dss2eng_loadshape!(data_eng::Dict{String,<:Any}, data_dss::Dict{String end if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end data_eng["loadshape"][name] = eng_obj @@ -111,7 +111,7 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An # connections bus = _parse_busname(defaults["bus1"])[1] connections_default = conf=="wye" ? [collect(1:nphases)..., 0] : nphases==1 ? [1,2] : [1,2,3] - connections = _get_conductors_ordered_dm(defaults["bus1"], default=connections_default, pad_ground=(conf=="wye")) + connections = _get_conductors_ordered(defaults["bus1"], default=connections_default, pad_ground=(conf=="wye")) @assert(length(unique(connections))==length(connections), "$name: connections cannot be made to a terminal more than once.") @@ -143,7 +143,7 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["qd"] = fill(defaults["kvar"]/nphases, nphases) if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if !haskey(data_eng, "load") @@ -172,9 +172,9 @@ function _dss2eng_capacitor_to_shunt!(data_eng::Dict{String,<:Any}, data_dss::Di Memento.error("Capacitor $(name): bus1 and bus2 should connect to the same bus.") end - f_terminals = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) + f_terminals = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) if conn=="wye" - t_terminals = _get_conductors_ordered_dm(defaults["bus2"], default=fill(0,nphases)) + t_terminals = _get_conductors_ordered(defaults["bus2"], default=fill(0,nphases)) else # if delta connected, ignore bus2 and generate t_terminals such that # it corresponds to a delta winding @@ -199,8 +199,10 @@ function _dss2eng_capacitor_to_shunt!(data_eng::Dict{String,<:Any}, data_dss::Di eng_obj = create_shunt(status=convert(Int, defaults["enabled"]), bus=bus_name, connections=terminals, g_sh=fill(0.0, size(B)...), b_sh=B) + eng_obj["source_id"] = "capacitor.$name" + if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if !haskey(data_eng, "shunt") @@ -229,9 +231,9 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String Memento.error(_LOGGER, "Capacitor $(name): bus1 and bus2 should connect to the same bus.") end - f_terminals = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) + f_terminals = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) if conn=="wye" - t_terminals = _get_conductors_ordered_dm(defaults["bus2"], default=fill(0,nphases)) + t_terminals = _get_conductors_ordered(defaults["bus2"], default=fill(0,nphases)) else # if delta connected, ignore bus2 and generate t_terminals such that # it corresponds to a delta winding @@ -257,7 +259,7 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String eng_obj["source_id"] = "capacitor.$name" if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if !haskey(data_eng, "capacitor") @@ -282,7 +284,7 @@ function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{St eng_obj["configuration"] = defaults["conn"] connections_default = eng_obj["configuration"] == "wye" ? [collect(1:eng_obj["phases"])..., 0] : collect(1:eng_obj["phases"]) - eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=connections_default, check_length=false) + eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], default=connections_default, check_length=false) eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] eng_obj["kvar"] = defaults["kvar"] @@ -295,7 +297,7 @@ function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{St end if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if !haskey(data_eng, "shunt_reactor") @@ -317,7 +319,7 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String eng_obj = Dict{String,Any}() eng_obj["phases"] = defaults["phases"] - eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], check_length=false) + eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)) eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] @@ -348,7 +350,7 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String eng_obj["source_id"] = "generator.$(name)" if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if !haskey(data_eng, "generator") @@ -396,7 +398,7 @@ function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< end if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if !haskey(data_eng, "voltage_source") @@ -449,7 +451,6 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An if haskey(dss_obj, "basefreq") && dss_obj["basefreq"] != data_eng["settings"]["basefreq"] Memento.warn(_LOGGER, "basefreq=$(dss_obj["basefreq"]) on line $(dss_obj["name"]) does not match circuit basefreq=$(data_eng["settings"]["basefreq"])") - dss_obj["circuit_basefreq"] = data_eng["settings"]["basefreq"] end defaults = _apply_ordered_properties(_create_line(dss_obj["bus1"], dss_obj["bus2"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) @@ -468,23 +469,23 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["linecode"] = dss_obj["linecode"] end - if any(haskey(dss_obj, key) for key in ["r0", "r1", "rg", "rmatrix"]) || !haskey(dss_obj, "linecode") + if any(haskey(dss_obj, key) && _is_after_linecode(dss_obj["prop_order"], key) for key in ["r0", "r1", "rg", "rmatrix"]) || !haskey(dss_obj, "linecode") eng_obj["rs"] = reshape(defaults["rmatrix"], nphases, nphases) end - if any(haskey(dss_obj, key) for key in ["x0", "x1", "xg", "xmatrix"]) || !haskey(dss_obj, "linecode") + if any(haskey(dss_obj, key) && _is_after_linecode(dss_obj["prop_order"], key) for key in ["x0", "x1", "xg", "xmatrix"]) || !haskey(dss_obj, "linecode") eng_obj["xs"] = reshape(defaults["xmatrix"], nphases, nphases) end - if any(haskey(dss_obj, key) for key in ["b0", "b1", "c0", "c1", "cmatrix"]) || !haskey(dss_obj, "linecode") + if any(haskey(dss_obj, key) && _is_after_linecode(dss_obj["prop_order"], key) for key in ["b0", "b1", "c0", "c1", "cmatrix"]) || !haskey(dss_obj, "linecode") eng_obj["b_fr"] = reshape(defaults["cmatrix"], nphases, nphases) ./ 2.0 eng_obj["b_to"] = reshape(defaults["cmatrix"], nphases, nphases) ./ 2.0 eng_obj["g_fr"] = fill(0.0, nphases, nphases) eng_obj["g_to"] = fill(0.0, nphases, nphases) end - eng_obj["f_connections"] = _get_conductors_ordered_dm(defaults["bus1"], default=collect(1:nphases)) - eng_obj["t_connections"] = _get_conductors_ordered_dm(defaults["bus2"], default=collect(1:nphases)) + eng_obj["f_connections"] = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) + eng_obj["t_connections"] = _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)) # if the ground is used directly, register if 0 in eng_obj["f_connections"] @@ -499,7 +500,7 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["source_id"] = "line.$(name)" if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if defaults["switch"] @@ -569,7 +570,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri terminals_default = conf=="wye" ? [1:nphases..., 0] : collect(1:nphases) # append ground if connections one too short - terminals_w = _get_conductors_ordered_dm(defaults["buses"][w], default=terminals_default, pad_ground=(conf=="wye")) + terminals_w = _get_conductors_ordered(defaults["buses"][w], default=terminals_default, pad_ground=(conf=="wye")) eng_obj["connections"][w] = terminals_w if 0 in terminals_w @@ -621,11 +622,10 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end if import_all - _import_all!(data_eng, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end data_eng["transformer"][name] = eng_obj - # add_virtual!(data_eng, "transformer", eng_obj) end end @@ -646,36 +646,31 @@ function _dss2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{Str eng_obj["f_bus"] = _parse_busname(defaults["bus1"])[1] eng_obj["t_bus"] = _parse_busname(defaults["bus2"])[1] - eng_obj["br_r"] = diagm(0 => fill(0.2, nphases)) - eng_obj["br_x"] = zeros(nphases, nphases) + eng_obj["length"] = 1.0 - eng_obj["g_fr"] = fill(0.0, nphases) - eng_obj["g_to"] = fill(0.0, nphases) - eng_obj["b_fr"] = fill(0.0, nphases) - eng_obj["b_to"] = fill(0.0, nphases) + eng_obj["rs"] = diagm(0 => fill(0.2, nphases)) - for key in ["g_fr", "g_to", "b_fr", "b_to"] - eng_obj[key] = diagm(0=>eng_obj[key]) + for key in ["xs", "g_fr", "g_to", "b_fr", "b_to"] + eng_obj[key] = zeros(nphases, nphases) end - eng_obj["c_rating_a"] = defaults["normamps"] - eng_obj["c_rating_b"] = defaults["emergamps"] - eng_obj["c_rating_c"] = defaults["emergamps"] - - eng_obj["tap"] = fill(1.0, nphases) - eng_obj["shift"] = fill(0.0, nphases) - - eng_obj["br_status"] = convert(Int, defaults["enabled"]) + eng_obj["f_connections"] = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) + eng_obj["t_connections"] = _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)) - eng_obj["angmin"] = fill(-60.0, nphases) - eng_obj["angmax"] = fill( 60.0, nphases) + # if the ground is used directly, register + if 0 in eng_obj["f_connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["f_bus"]], eng_obj["f_connections"]) + end + if 0 in eng_obj["t_connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["t_bus"]], eng_obj["t_connections"]) + end - eng_obj["transformer"] = true + eng_obj["status"] = convert(Int, defaults["enabled"]) eng_obj["source_id"] = "reactor.$(name)" if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if !haskey(data_eng, "line_reactor") @@ -705,7 +700,7 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["configuration"] = "wye" - eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)) + eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)) # if the ground is used directly, register load if 0 in eng_obj["connections"] @@ -726,7 +721,7 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["source_id"] = "pvsystem.$(name)" if import_all - _import_all!(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if !haskey(data_eng, "pvsystem") @@ -747,7 +742,7 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< eng_obj = Dict{String,Any}() eng_obj["phases"] = defaults["phases"] - eng_obj["connections"] = _get_conductors_ordered_dm(defaults["bus1"], check_length=false) + eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], check_length=false) eng_obj["name"] = name eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] @@ -814,7 +809,7 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban defaults = _create_vsource(get(source, "bus1", "sourcebus"), source["name"]; _to_kwargs(source)...) data_eng["name"] = data_dss["circuit"] - data_eng["sourcebus"] = defaults["bus1"] + data_eng["sourcebus"] = _parse_busname(defaults["bus1"])[1] data_eng["data_model"] = "engineering" # TODO rename fields @@ -824,7 +819,7 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban data_eng["settings"]["set_vbase_val"] = defaults["basekv"]/sqrt(3) data_eng["settings"]["set_vbase_bus"] = data_eng["sourcebus"] data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1E3 - data_eng["settings"]["basefreq"] = get(data_dss["options"], "defaultbasefreq", 60.0) + data_eng["settings"]["basefreq"] = get(get(data_dss, "options", Dict{String,Any}()), "defaultbasefreq", 60.0) data_eng["files"] = data_dss["filename"] else diff --git a/src/io/utils.jl b/src/io/utils.jl index e059d02f4..c875a23de 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -1,5 +1,5 @@ "all edge types that can help define buses" -const _dss_edge_components = ["line", "transformer", "reactor"] +const _dss_edge_components = ["line", "transformer", "reactor", "capacitor"] "components currently supported for automatic data type parsing" const _dss_supported_components = [ @@ -314,6 +314,8 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) end end + btrans["source_id"] = "transformer.$bank" + # edit the transformer dict for id in ids delete!(data_eng["transformer"], id) @@ -332,10 +334,12 @@ end function _discover_terminals!(data_eng::Dict{String,<:Any}) terminals = Dict{String, Set{Int}}([(name, Set{Int}()) for (name,bus) in data_eng["bus"]]) - for (_,eng_obj) in data_eng["line"] - # ignore 0 terminal - push!(terminals[eng_obj["f_bus"]], setdiff(eng_obj["f_connections"], [0])...) - push!(terminals[eng_obj["t_bus"]], setdiff(eng_obj["t_connections"], [0])...) + if haskey(data_eng, "line") + for (_,eng_obj) in data_eng["line"] + # ignore 0 terminal + push!(terminals[eng_obj["f_bus"]], setdiff(eng_obj["f_connections"], [0])...) + push!(terminals[eng_obj["t_bus"]], setdiff(eng_obj["t_connections"], [0])...) + end end if haskey(data_eng, "transformer") @@ -428,7 +432,7 @@ end "Returns an ordered list of defined conductors. If ground=false, will omit any `0`" -function _get_conductors_ordered_dm(busname::AbstractString; default::Array=[], check_length::Bool=true, pad_ground::Bool=false)::Array +function _get_conductors_ordered(busname::AbstractString; default::Array=[], check_length::Bool=true, pad_ground::Bool=false)::Array parts = split(busname, '.'; limit=2) ret = [] if length(parts)==2 @@ -451,7 +455,7 @@ end "" -function _import_all!(component::Dict{String,<:Any}, defaults::Dict{String,<:Any}, prop_order::Array{<:AbstractString,1}) +function _import_all!(component::Dict{String,<:Any}, defaults::Dict{String,<:Any}, prop_order::Vector{String}) component["dss"] = Dict{String,Any}((key, defaults[key]) for key in prop_order) end @@ -539,25 +543,6 @@ function _parse_busname(busname::AbstractString) end -"Returns an ordered list of defined conductors. If ground=false, will omit any `0`" -function _get_conductors_ordered(busname::AbstractString; neutral::Bool=true, nconductors::Int=3)::Array - parts = split(busname, '.'; limit=2) - ret = [] - if length(parts)==2 - conductors_string = split(parts[2], '.') - if neutral - ret = [parse(Int, i) for i in conductors_string] - else - ret = [parse(Int, i) for i in conductors_string if i != "0"] - end - else - ret = collect(1:nconductors) - end - - return ret -end - - "converts Dict{String,Any} to Dict{Symbol,Any} for passing as kwargs" function _to_kwargs(data::Dict{String,Any})::Dict{Symbol,Any} return Dict{Symbol,Any}((Symbol(k), v) for (k, v) in data) @@ -584,7 +569,7 @@ end function _apply_like!(raw_dss, data_dss, comp_type) links = ["like"] if any(link in raw_dss["prop_order"] for link in links) - new_prop_order = [] + new_prop_order = Vector{String}([]) raw_dss_copy = deepcopy(raw_dss) for prop in raw_dss["prop_order"] @@ -617,7 +602,7 @@ function _apply_like!(raw_dss, data_dss, comp_type) end end - final_prop_order = [] + final_prop_order = Vector{String}([]) while !isempty(new_prop_order) prop = popfirst!(new_prop_order) if !(prop in new_prop_order) @@ -771,9 +756,27 @@ function _rm_floating_cnd(cnds, Y, f) end +"" function _register_awaiting_ground!(bus, connections) if !haskey(bus, "awaiting_ground") bus["awaiting_ground"] = [] end push!(bus["awaiting_ground"], connections) end + + +"" +function _is_after_linecode(prop_order::Vector{String}, property::String)::Bool + linecode_idx = 0 + property_idx = 0 + + for (i, prop) in enumerate(prop_order) + if prop == "linecode" + linecode_idx = i + elseif prop == property + property_idx = i + end + end + + return linecode_idx < property_idx +end diff --git a/test/data/opendss/test2_Linecodes.dss b/test/data/opendss/test2_Linecodes.dss index 92341b39c..31ba114f9 100644 --- a/test/data/opendss/test2_Linecodes.dss +++ b/test/data/opendss/test2_Linecodes.dss @@ -30,4 +30,6 @@ New Linecode.lc8 nphases=3 units=km b1=(3.4 2 pi 60.0 * * *) New Linecode.lc9 nphases=3 b1=(3.4 2 pi 60.0 * * *) b0=(1.6 2 pi 60 * * *) -new linecode.lc10 like=lc/2 \ No newline at end of file +new linecode.lc10 like=lc/2 + +new linecode.300 like=lc/2 \ No newline at end of file diff --git a/test/data/opendss/test2_master.dss b/test/data/opendss/test2_master.dss index 963dbd3aa..445ab0ce2 100644 --- a/test/data/opendss/test2_master.dss +++ b/test/data/opendss/test2_master.dss @@ -15,12 +15,12 @@ New Line.L1-2 bus1=b3-1.2 bus2=b4.2 length=0.032175613 units=km Linecode=lc1 New Line.L2 bus1=b5 bus2=b6_check-chars length=0.013516796 units=none linecode=lc/2 New "Line.L3" phases=3 bus1=b7.1.2.3 bus2=b9.1.2.3 linecode=300 normamps=400 emergamps=600 faultrate=0.1 pctperm=20 repair=0 length=2.58 New "Line._L4" phases=3 bus1=b8.1.2.3 bus2=b10.1.2.3 linecode=300 normamps=400 emergamps=600 faultrate=0.1 pctperm=20 repair=0 length=1.73 switch=y -New line.l5 phases=3 bus1=_b2.1.2.3.0 bus2=b7.1.2.3.0 linecode=lc8 +New line.l5 phases=3 bus1=_b2.1.2.3 bus2=b7.1.2.3 linecode=lc8 New line.l6 phases=3 bus1=b1.1.2.3 bus2=b10.1.2.3 linecode=lc9 new line.l7 bus1=_b2 bus2=b10 like=L2 linecode=lc10 ! Loads -New Load.ld1 phases=1 Bus1=b7.1.2 kv=0.208 status=variable model=1 conn=wye kW=3.89 pf=0.97 Vminpu=.88 +New Load.ld1 phases=1 Bus1=b7.1.0 kv=0.208 status=variable model=1 conn=wye kW=3.89 pf=0.97 Vminpu=.88 New "Load.ld2" bus1=b9 phases=3 conn=Wye model=5 kV=24.9 kW=27 kvar=21 Vminpu=.85 New "Load.ld3" bus1=b10 phases=3 conn=Wye model=2 kV=24.9 kW=405 kvar=315 Vminpu=.85 yearly=(sngfile=load_profile.sng) new load.ld4 bus1=b1 like=ld2 @@ -52,13 +52,13 @@ new transformer.t5 buses=(b3-1, b5) like=t4 ! Generators New Generator.g1 Bus1=b1 kV= 150 kW=1 Model=3 Vpu=1.05 Maxkvar=70000 Minkvar=10000 ! kvar=50000 -New Generator.g2 Bus1=testsource kV= 120 kW=1 Phases=1 Model=3 Vpu=1.05 Maxkvar=70000 Minkvar=10000 ! kvar=60000 -new generator.g3 bus1=b7 like=g2 +New Generator.g2 Bus1=testsource.1 kV= 120 kW=1 Phases=1 Model=3 Vpu=1.05 Maxkvar=70000 Minkvar=10000 ! kvar=60000 +new generator.g3 bus1=b7.1 phases=1 like=g2 Set voltagebases=[115, 12.47, 0.48, 0.208] -Load.s860.vminpu=.85 -Load.s840.vminpu=.85 +Load.ld1.vminpu=.85 +Load.ld3.vminpu=.85 Load.s844.vminpu=.85 Transformer.t1.wdg=2 Tap=(0.01000 15 * 1 +) ! Tap diff --git a/test/data/opendss/test_transformer_formatting.dss b/test/data/opendss/test_transformer_formatting.dss index c86bdf9b1..a9c5afbb8 100644 --- a/test/data/opendss/test_transformer_formatting.dss +++ b/test/data/opendss/test_transformer_formatting.dss @@ -13,5 +13,5 @@ transformer.transformer_test.wdg=1 tap=(0.00625 12 * 1 +) transformer.transformer_test.wdg=3 tap=0.9 bus=1.1.2.3 new transformer.reg4a phases=1 windings=2 bank=reg4 buses=[1.1 2.1] conns=[wye wye] kvs=[2.402 2.402] kvas=[2000 2000] XHL=.01 %LoadLoss=0.00001 ppm=0.0 -new transformer.reg4b like=reg4a bank=reg4 buses=[1.2 2.2] ppm=0.0 -new transformer.reg4c like=reg4a bank=reg4 buses=[1.3 2.3] ppm=0.0 +new transformer.reg4b phases=1 like=reg4a bank=reg4 buses=[1.2 2.2] ppm=0.0 +new transformer.reg4c phases=1 like=reg4a bank=reg4 buses=[1.3 2.3] ppm=0.0 diff --git a/test/opendss.jl b/test/opendss.jl index db63042ca..e24e64019 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -4,11 +4,10 @@ @testset "loadshape parsing" begin dss = PMD.parse_dss("../test/data/opendss/loadshapes.dss") - PMD.parse_dss_with_dtypes!(dss, ["loadshape"]) loadshapes = Dict{String,Any}() - for ls in dss["loadshape"] - loadshapes[ls["name"]] = PMD._create_loadshape(ls["name"]; PMD._to_kwargs(ls)...) + for (name, ls) in dss["loadshape"] + loadshapes[name] = PMD._create_loadshape(name; PMD._to_kwargs(ls)...) end @test isapprox(loadshapes["1"]["interval"], 1.0/60) @@ -18,13 +17,13 @@ end @testset "arrays from files" begin - dss = PMD.parse_file("../test/data/opendss/test2_master.dss"; import_all=true) + dss = PMD.parse_dss("../test/data/opendss/test2_master.dss") - @test isa(dss["load"]["3"]["yearly"], Array) - @test isa(dss["load"]["4"]["daily"], Array) + @test isa(dss["load"]["ld3"]["yearly"], Vector) + @test isa(dss["load"]["ld2"]["daily"], Vector) - @test length(dss["load"]["3"]["yearly"]) == 10 - @test length(dss["load"]["4"]["daily"]) == 10 + @test length(dss["load"]["ld3"]["yearly"]) == 10 + @test length(dss["load"]["ld2"]["daily"]) == 10 end @testset "reverse polish notation" begin @@ -50,18 +49,21 @@ Memento.setlevel!(TESTLOG, "error") end - @testset "opendss parse load model errors" begin - for load in 1:5 - dss = PMD.parse_dss("../test/data/opendss/loadparser_error.dss") - dss["load"] = [dss["load"][load]] - @test_throws(TESTLOG, ErrorException, PMD.parse_opendss(dss)) - end - end + # TODO fix, do we support these previously erroring cases now? + # @testset "opendss parse load model errors" begin + # dss = PMD.parse_dss("../test/data/opendss/loadparser_error.dss") + # for (name, load) in dss["load"] + # _dss = deepcopy(dss) + # _dss["load"] = Dict{String,Any}(name => load) + + # @test_throws(TESTLOG, AssertionError, PMD.parse_opendss(_dss)) + # end + # end @testset "opendss parse load model warnings" begin for model in [3, 4, 7, 8] dss = PMD.parse_dss("../test/data/opendss/loadparser_warn_model.dss") - dss["load"] = [l for l in dss["load"] if l["name"]=="d1phm$model"] + dss["load"] = Dict{String,Any}((n,l) for (n,l) in dss["load"] if l["name"]=="d1phm$model") Memento.setlevel!(TESTLOG, "info") @test_warn(TESTLOG, ": load model $model not supported. Treating as model 1.", PMD.parse_opendss(dss)) Memento.setlevel!(TESTLOG, "error") @@ -75,15 +77,6 @@ @test_warn(TESTLOG, "Not all OpenDSS features are supported, currently only minimal support for lines, loads, generators, and capacitors as shunts. Transformers and reactors as transformer branches are included, but value translation is not fully supported.", PMD.parse_file("../test/data/opendss/test_simple.dss")) - Memento.TestUtils.@test_log(TESTLOG, "info", "Calling parse_dss on ../test/data/opendss/test_simple.dss", - PMD.parse_file("../test/data/opendss/test_simple.dss")) - - Memento.TestUtils.@test_log(TESTLOG, "info", "Done parsing ../test/data/opendss/test_simple.dss", - PMD.parse_file("../test/data/opendss/test_simple.dss")) - - @test_throws(TESTLOG, ErrorException, - PMD.parse_file("../test/data/opendss/test_simple2.dss")) - @test_throws(TESTLOG, ErrorException, PMD.parse_file("../test/data/opendss/test_simple2.dss")) @@ -93,7 +86,7 @@ @test_warn(TESTLOG, "Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.", PMD.parse_file("../test/data/opendss/test2_master.dss")) - @test_warn(TESTLOG, "reactors as constant impedance elements is not yet supported, treating like line", + @test_warn(TESTLOG, "reactors as constant impedance elements is not yet supported, treating reactor.reactor1 like line", PMD.parse_file("../test/data/opendss/test2_master.dss")) @test_warn(TESTLOG, "line.l1: like=something cannot be found", @@ -102,36 +95,25 @@ @test_warn(TESTLOG, "Rg,Xg are not fully supported", PMD.parse_file("../test/data/opendss/test2_master.dss")) - @test_warn(TESTLOG, "Could not find line \"something\"", - PMD.parse_file("../test/data/opendss/test2_master.dss")) - - @test_warn(TESTLOG, "The neutral impedance, (rneut and xneut properties), is ignored; the neutral (for wye and zig-zag windings) is connected directly to the ground.", - PMD.parse_file("../test/data/opendss/test2_master.dss")) - - @test_warn(TESTLOG, "Only three-phase transformers are supported. The bus specification b7.1 is treated as b7 instead.", - PMD.parse_file("../test/data/opendss/test2_master.dss")) - - @test_warn(TESTLOG, "Only three-phase transformers are supported. The bus specification b7.1 is treated as b7 instead.", - PMD.parse_file("../test/data/opendss/test2_master.dss")) - - Memento.TestUtils.@test_log(TESTLOG, "info", "`dss_data` has been reset with the \"clear\" command.", + Memento.TestUtils.@test_log(TESTLOG, "info", "Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"", PMD.parse_file("../test/data/opendss/test2_master.dss")) - Memento.TestUtils.@test_log(TESTLOG, "info", "Redirecting to file \"test2_Linecodes.dss\"", + Memento.TestUtils.@test_log(TESTLOG, "info", "Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"", PMD.parse_file("../test/data/opendss/test2_master.dss")) - Memento.TestUtils.@test_log(TESTLOG, "info", "Compiling file \"test2_Loadshape.dss\"", + Memento.TestUtils.@test_log(TESTLOG, "info", "Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"", PMD.parse_file("../test/data/opendss/test2_master.dss")) Memento.setlevel!(TESTLOG, "error") end + eng = PMD.parse_file("../test/data/opendss/test2_master.dss"; data_model="engineering") pmd = PMD.parse_file("../test/data/opendss/test2_master.dss") len = 0.013516796 - rmatrix=PMD._parse_matrix(Float64, "[1.5000 |0.200000 1.50000 |0.250000 0.25000 2.00000 ]") * 3 - xmatrix=PMD._parse_matrix(Float64, "[1.0000 |0.500000 0.50000 |0.500000 0.50000 1.000000 ]") * 3 - cmatrix = PMD._parse_matrix(Float64, "[8.0000 |-2.00000 9.000000 |-1.75000 -2.50000 8.00000 ]") / 3 + rmatrix=PMD._parse_matrix(Float64, "[1.5000 |0.200000 1.50000 |0.250000 0.25000 2.00000 ]") + xmatrix=PMD._parse_matrix(Float64, "[1.0000 |0.500000 0.50000 |0.500000 0.50000 1.000000 ]") + cmatrix = PMD._parse_matrix(Float64, "[8.0000 |-2.00000 9.000000 |-1.75000 -2.50000 8.00000 ]") @testset "buscoords automatic parsing" begin @test all(haskey(bus, "lon") && haskey(bus, "lat") for bus in values(pmd["bus"]) if "bus_i" in 1:10) @@ -142,8 +124,8 @@ @test pmd["name"] == "test2" - @test length(pmd) == 21 - @test length(dss) == 12 + @test length(pmd) == 19 + @test length(dss) == 14 for (key, len) in zip(["bus", "load", "shunt", "branch", "gen", "dcline", "transformer"], [33, 4, 5, 27, 4, 0, 10]) @test haskey(pmd, key) @@ -153,84 +135,84 @@ @test all(haskey(dss, key) for key in ["loadshape", "linecode", "buscoords", "options", "filename"]) end - @testset "opendss parse generic branch values verification" begin - basekv_br3 = pmd["bus"][string(pmd["branch"]["3"]["f_bus"])]["base_kv"] - @test all(isapprox.(pmd["branch"]["3"]["br_r"], rmatrix * len / basekv_br3^2 * pmd["baseMVA"]; atol=1e-6)) - @test all(isapprox.(pmd["branch"]["3"]["br_x"], xmatrix * len / basekv_br3^2 * pmd["baseMVA"]; atol=1e-6)) - @test all(isapprox.(pmd["branch"]["3"]["b_fr"], diag(basekv_br3^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 * cmatrix * len / 1e9) / 2.0; atol=1e-6)) - - for i in 6:7 - basekv_bri = pmd["bus"][string(pmd["branch"]["$i"]["f_bus"])]["base_kv"] - @test all(isapprox.(diag(pmd["branch"]["$i"]["b_fr"]), (3.4 * 2.0 + 1.6) / 3.0 * (basekv_bri^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 / 1e9) / 2.0 / 3; atol=1e-6)) - end - - @test all(isapprox.(pmd["branch"]["1"]["br_r"].*(115/69)^2, diagm(0 => fill(6.3012e-8, 3)); atol=1e-12)) - @test all(isapprox.(pmd["branch"]["1"]["br_x"].*(115/69)^2, diagm(0 => fill(6.3012e-7, 3)); atol=1e-12)) - - for k in ["qd", "pd"] - @test all(isapprox.(pmd["load"]["4"][k], pmd["load"]["2"][k]; atol=1e-12)) - end - - for k in ["gs", "bs"] - @test all(isapprox.(pmd["shunt"]["2"][k], pmd["shunt"]["3"][k]; atol=1e-12)) - @test all(isapprox.(pmd["shunt"]["4"][k], pmd["shunt"]["5"][k]; atol=1e-12)) - end - - for k in keys(pmd["gen"]["3"]) - if !(k in ["gen_bus", "index", "name", "source_id", "active_phases"]) - @test all(isapprox.(pmd["gen"]["4"][k], pmd["gen"]["3"][k]; atol=1e-12)) - end - end - - for k in keys(pmd["branch"]["11"]) - if !(k in ["f_bus", "t_bus", "index", "name", "linecode", "source_id", "active_phases"]) - mult = 1.0 - if k in ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to"] - # compensation for the different voltage base - basekv_br3 = pmd["bus"][string(pmd["branch"]["3"]["f_bus"])]["base_kv"] - basekv_br8 = pmd["bus"][string(pmd["branch"]["8"]["f_bus"])]["base_kv"] - zmult = (basekv_br3/basekv_br8)^2 - mult = (k in ["br_r", "br_x"]) ? zmult : 1/zmult - end - @test all(isapprox.(pmd["branch"]["3"][k].*mult, pmd["branch"]["8"][k]; atol=1e-12)) - end - end - end + # TODO fix, the way we calculate voltage bases changed + # @testset "opendss parse generic branch values verification" begin + # basekv_br5 = pmd["bus"][string(pmd["branch"]["5"]["f_bus"])]["base_kv"] + # @test all(isapprox.(pmd["branch"]["5"]["br_r"], rmatrix * len / basekv_br5^2 * pmd["baseMVA"]; atol=1e-6)) + # @test all(isapprox.(pmd["branch"]["5"]["br_x"], xmatrix * len / basekv_br5^2 * pmd["baseMVA"]; atol=1e-6)) + # @test all(isapprox.(pmd["branch"]["5"]["b_fr"], diag(basekv_br5^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 * cmatrix * len / 1e9) / 2.0; atol=1e-6)) + + # for i in [6, 3] + # basekv_bri = pmd["bus"][string(pmd["branch"]["$i"]["f_bus"])]["base_kv"] + # @test all(isapprox.(diag(pmd["branch"]["$i"]["b_fr"]), (3.4 * 2.0 + 1.6) / 3.0 * (basekv_bri^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 / 1e9) / 2.0; atol=1e-6)) + # end + + # @test all(isapprox.(pmd["branch"]["9"]["br_r"].*(115/69)^2, diagm(0 => fill(6.3012e-8, 3)); atol=1e-12)) + # @test all(isapprox.(pmd["branch"]["9"]["br_x"].*(115/69)^2, diagm(0 => fill(6.3012e-7, 3)); atol=1e-12)) + + # for k in ["qd", "pd"] + # @test all(isapprox.(pmd["load"]["2"][k], pmd["load"]["2"][k]; atol=1e-12)) + # end + + # for k in ["gs", "bs"] + # @test all(isapprox.(pmd["shunt"]["1"][k], pmd["shunt"]["3"][k]; atol=1e-12)) + # @test all(isapprox.(pmd["shunt"]["4"][k], pmd["shunt"]["5"][k]; atol=1e-12)) + # end + + # for k in keys(pmd["gen"]["1"]) + # if !(k in ["gen_bus", "index", "name", "source_id", "connections"]) + # @test all(isapprox.(pmd["gen"]["3"][k], pmd["gen"]["1"][k]; atol=1e-12)) + # end + # end + + # for k in keys(pmd["branch"]["11"]) + # if !(k in ["f_bus", "t_bus", "index", "name", "linecode", "source_id", "t_connections", "f_connections"]) + # mult = 1.0 + # if k in ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to"] + # # compensation for the different voltage base + # basekv_br5 = pmd["bus"][string(pmd["branch"]["5"]["f_bus"])]["base_kv"] + # basekv_br2 = pmd["bus"][string(pmd["branch"]["2"]["f_bus"])]["base_kv"] + # zmult = (basekv_br5/basekv_br2)^2 + # mult = (k in ["br_r", "br_x"]) ? zmult : 1/zmult + # end + # @test all(isapprox.(pmd["branch"]["5"][k].*mult, pmd["branch"]["2"][k]; atol=1e-12)) + # end + # end + # end @testset "opendss parse length units" begin - @test pmd["branch"]["9"]["length"] == 1000.0 * len - basekv_br9 = pmd["bus"][string(pmd["branch"]["9"]["f_bus"])]["base_kv"] - @test all(isapprox.(pmd["branch"]["9"]["br_r"], rmatrix * len / basekv_br9^2 * pmd["baseMVA"]; atol=1e-6)) - @test all(isapprox.(pmd["branch"]["9"]["br_x"], xmatrix * len / basekv_br9^2 * pmd["baseMVA"]; atol=1e-6)) - @test all(isapprox.(pmd["branch"]["9"]["b_fr"], diag(basekv_br9^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 * cmatrix * len / 1e9) / 2.0 / 3; atol=1e-6)) + @test eng["line"]["l8"]["length"] == 1000.0 * len + basekv_br4 = pmd["bus"][string(pmd["branch"]["4"]["f_bus"])]["base_kv"] + @test all(isapprox.(pmd["branch"]["4"]["br_r"], rmatrix * len / basekv_br4^2 * pmd["baseMVA"]; atol=1e-6)) + @test all(isapprox.(pmd["branch"]["4"]["br_x"], xmatrix * len / basekv_br4^2 * pmd["baseMVA"]; atol=1e-6)) + @test all(isapprox.(pmd["branch"]["4"]["b_fr"], diag(basekv_br4^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 * cmatrix * len / 1e9) / 2.0 / 3; atol=1e-6)) end @testset "opendss parse switch length verify" begin @testset "branches with switches" begin - @test pmd["branch"]["5"]["switch"] - @test pmd["branch"]["5"]["length"] == 0.001 - @test all([pmd["branch"]["$i"]["switch"] == false for i in 1:4]) + @test eng["switch"]["_l4"]["length"] == 0.001 + @test !all(get(br, "switch", false) for (_,br) in pmd["branch"] if !startswith(br["name"],"_virtual_branch.switch")) end end @testset "opendss parse transformer parsing verify" begin dss_data = PMD.parse_dss("../test/data/opendss/test_transformer_formatting.dss") - transformer = dss_data["transformer"][1] - @test transformer["phases"] == "3" - @test transformer["tap"] == "(0.00625 12 * 1 +)" - @test transformer["tap_2"] == "1.5" - @test transformer["%loadloss"] == "0.01" - @test transformer["xhl"] == "0.02" - @test transformer["kv_2"] == "12.47" + transformer = dss_data["transformer"]["transformer_test"] + @test transformer["phases"] == 3 + @test transformer["tap"] == PMD._parse_rpn("(0.00625 12 * 1 +)") + @test transformer["tap_2"] == 1.5 + @test transformer["%loadloss"] == 0.01 + @test transformer["xhl"] == 0.02 + @test transformer["kv_2"] == 12.47 @test transformer["conn_2"] == "wye" - @test transformer["tap_3"] == "0.9" - @test transformer["wdg_3"] == "3" + @test transformer["tap_3"] == 0.9 + @test transformer["wdg_3"] == 3 - PMD._apply_like!(dss_data["transformer"][3], dss_data, "transformer") - @test dss_data["transformer"][3]["%loadloss"] == dss_data["transformer"][2]["%loadloss"] + PMD._apply_like!(dss_data["transformer"]["reg4b"], dss_data, "transformer") + @test dss_data["transformer"]["reg4b"]["%loadloss"] == dss_data["transformer"]["reg4a"]["%loadloss"] - pmd_data = PMD.parse_file("../test/data/opendss/test_transformer_formatting.dss") - @test all(all(pmd_data["transformer"]["$n"]["tm"] .== tm) for (n, tm) in zip(1:3, [1.075, 1.5, 0.9])) + eng_data = PMD.parse_file("../test/data/opendss/test_transformer_formatting.dss"; data_model="engineering") + @test all(all(eng_data["transformer"]["$n"]["tm"] .== tm) for (n, tm) in zip(["transformer_test", "reg4"], [[fill(1.075, 3), fill(1.5, 3), fill(0.9, 3)], [ones(3), ones(3)]])) end @testset "opendss parse storage" begin @@ -238,7 +220,7 @@ for bat in values(pmd_storage["storage"]) for key in ["energy", "storage_bus", "energy_rating", "charge_rating", "discharge_rating", "charge_efficiency", "discharge_efficiency", "thermal_rating", "qmin", "qmax", - "r", "x", "p_loss", "q_loss", "status", "source_id", "active_phases"] + "r", "x", "p_loss", "q_loss", "status", "source_id"] @test haskey(bat, key) if key in ["x", "r", "qmin", "qmax", "thermal_rating"] @test isa(bat[key], Vector) @@ -246,7 +228,7 @@ end end - @test pmd_storage["storage"]["1"]["source_id"] == "storage.s1" && length(pmd_storage["storage"]["1"]["active_phases"]) == 3 + @test pmd_storage["storage"]["1"]["source_id"] == "storage.s1" end @testset "opendss parse pvsystem" begin @@ -257,42 +239,41 @@ end @testset "opendss parse verify source_id" begin - @test pmd["shunt"]["1"]["source_id"] == "capacitor.c1" && length(pmd["shunt"]["1"]["active_phases"]) == 3 - @test pmd["shunt"]["4"]["source_id"] == "reactor.reactor3" && length(pmd["shunt"]["4"]["active_phases"]) == 3 + @test pmd["shunt"]["2"]["source_id"] == "capacitor.c1" + @test pmd["shunt"]["4"]["source_id"] == "reactor.reactor3" - @test pmd["branch"]["1"]["source_id"] == "line.l1" && length(pmd["branch"]["1"]["active_phases"]) == 3 - @test pmd["transformer"]["1"]["source_id"] == "transformer.t4_1" # winding indicated by _1 - @test pmd["branch"]["10"]["source_id"] == "reactor.reactor1" && length(pmd["branch"]["10"]["active_phases"]) == 3 + @test pmd["branch"]["9"]["source_id"] == "line.l1" + @test pmd["transformer"]["9"]["source_id"] == "_virtual_transformer.transformer.t4.1" # winding indicated by .1 + @test pmd["branch"]["10"]["source_id"] == "reactor.reactor1" - @test pmd["gen"]["1"]["source_id"] == "vsource.sourcegen" && length(pmd["gen"]["1"]["active_phases"]) == 3 - @test pmd["gen"]["2"]["source_id"] == "generator.g1" && length(pmd["gen"]["2"]["active_phases"]) == 3 + @test pmd["gen"]["4"]["source_id"] == "_virtual_gen.vsource.source" + @test pmd["gen"]["1"]["source_id"] == "generator.g2" - source_id = PMD._parse_dss_source_id(pmd["load"]["1"]) - @test source_id.dss_type == "load" - @test source_id.dss_name == "ld1" - @test all([n in source_id.active_phases for n in 1:2]) + @test pmd["load"]["1"]["source_id"] == "load.ld1" - @test all(haskey(component, "source_id") && haskey(component, "active_phases") for component_type in ["load", "branch", "shunt", "gen", "storage", "pvsystem"] for component in values(get(pmd, component_type, Dict()))) + @test all(haskey(component, "source_id") for component_type in PMD._dss_supported_components for component in values(get(pmd, component_type, Dict())) if component_type != "bus") end @testset "opendss parse verify order of properties on line" begin pmd1 = PMD.parse_file("../test/data/opendss/case3_balanced.dss") pmd2 = PMD.parse_file("../test/data/opendss/case3_balanced_prop-order.dss") + delete!(pmd1, "map") + delete!(pmd2, "map") + @test pmd1 == pmd2 dss1 = PMD.parse_dss("../test/data/opendss/case3_balanced.dss") dss2 = PMD.parse_dss("../test/data/opendss/case3_balanced_prop-order.dss") @test dss1 != dss2 - @test all(a == b for (a, b) in zip(dss2["line"][1]["prop_order"],["name", "bus1", "bus2", "linecode", "rmatrix", "length"])) - @test all(a == b for (a, b) in zip(dss2["line"][2]["prop_order"],["name", "bus1", "bus2", "like", "linecode", "length"])) + @test all(a == b for (a, b) in zip(dss2["line"]["ohline"]["prop_order"],["name", "bus1", "bus2", "linecode", "rmatrix", "length"])) + @test all(a == b for (a, b) in zip(dss2["line"]["quad"]["prop_order"],["name", "bus1", "bus2", "like", "linecode", "length"])) end @testset "opendss parse verify mvasc3/mvasc1 circuit parse" begin dss = PMD.parse_dss("../test/data/opendss/test_simple.dss") - PMD.parse_dss_with_dtypes!(dss, ["circuit"]) - circuit = PMD._create_vsource("sourcebus", "simple"; PMD._to_kwargs(dss["circuit"][1])...) + circuit = PMD._create_vsource("sourcebus", "source"; PMD._to_kwargs(dss["vsource"]["source"])...) @test circuit["mvasc1"] == 2100.0 @test circuit["mvasc3"] == 1900.0 @@ -300,8 +281,7 @@ @test isapprox(circuit["isc1"], 10543.0; atol=1e-1) dss = PMD.parse_dss("../test/data/opendss/test_simple3.dss") - PMD.parse_dss_with_dtypes!(dss, ["circuit"]) - circuit = PMD._create_vsource("sourcebus", "simple"; PMD._to_kwargs(dss["circuit"][1])...) + circuit = PMD._create_vsource("sourcebus", "source"; PMD._to_kwargs(dss["vsource"]["source"])...) @test circuit["mvasc1"] == 2100.0 @test isapprox(circuit["mvasc3"], 1900.0; atol=1e-1) @@ -309,28 +289,21 @@ @test isapprox(circuit["isc1"], 10543.0; atol=1e-1) dss = PMD.parse_dss("../test/data/opendss/test_simple4.dss") - PMD.parse_dss_with_dtypes!(dss, ["circuit"]) - circuit = PMD._create_vsource("sourcebus", "simple"; PMD._to_kwargs(dss["circuit"][1])...) + circuit = PMD._create_vsource("sourcebus", "source"; PMD._to_kwargs(dss["vsource"]["source"])...) @test isapprox(circuit["mvasc1"], 2091.5; atol=1e-1) @test circuit["mvasc3"] == 2000.0 @test circuit["isc3"] == 10041.0 @test circuit["isc1"] == 10500.0 end - - @testset "opendss verify sourcebus_vbranch rate_a" begin - data = PMD.parse_file("../test/data/opendss/case3_balanced.dss") - vbranch = [br for (id,br) in data["branch"] if br["name"]=="sourcebus_vbranch"][1] - @test haskey(vbranch, "rate_a") - end end -@testset "test json parser" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") +# @testset "test json parser" begin +# pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") - io = PipeBuffer() - PMD.print_file(io, pmd) - pmd_json_file = PMD.parse_file(io) +# io = PipeBuffer() +# PMD.print_file(io, pmd) +# pmd_json_file = PMD.parse_file(io) - @test pmd == pmd_json_file -end +# @test pmd == pmd_json_file +# end diff --git a/test/runtests.jl b/test/runtests.jl index e916a298d..d6b1903b7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -35,7 +35,7 @@ include("common.jl") @testset "PowerModelsDistribution" begin - # include("opendss.jl") + include("opendss.jl") # three tests disabled temporarily include("data.jl") # all passing From 11bc95d96206ae3b7db572e6626fbd355a159eab Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 11 Mar 2020 11:15:44 -0600 Subject: [PATCH 090/224] FIX: ACP MLD formulation --- src/form/acp.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/form/acp.jl b/src/form/acp.jl index 12b0ed672..aa40566e6 100644 --- a/src/form/acp.jl +++ b/src/form/acp.jl @@ -138,13 +138,19 @@ function constraint_mc_power_balance_shed(pm::_PMs.AbstractACPModel, nw::Int, i: z_demand = _PMs.var(pm, nw, :z_demand) z_shunt = _PMs.var(pm, nw, :z_shunt) + cnds = _PMs.conductor_ids(pm; nw=nw) + ncnds = length(cnds) + + Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) + Bt = isempty(bus_bs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_bs)) + cstr_p = [] cstr_q = [] bus_GsBs = [(n,bus_gs[n], bus_bs[n]) for n in keys(bus_gs)] for c in _PMs.conductor_ids(pm; nw=nw) - cp = JuMP.@constraint(pm.model, + cp = JuMP.@NLconstraint(pm.model, sum(p[a][c] for a in bus_arcs) + sum(psw[a_sw][c] for a_sw in bus_arcs_sw) + sum(pt[a_trans][c] for a_trans in bus_arcs_trans) @@ -161,7 +167,7 @@ function constraint_mc_power_balance_shed(pm::_PMs.AbstractACPModel, nw::Int, i: ) push!(cstr_p, cp) - cq = JuMP.@constraint(pm.model, + cq = JuMP.@NLconstraint(pm.model, sum(q[a][c] for a in bus_arcs) + sum(qsw[a_sw][c] for a_sw in bus_arcs_sw) + sum(qt[a_trans][c] for a_trans in bus_arcs_trans) From 1061592738473ca80543e4355860c90d7a398ea0 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 12 Mar 2020 14:47:18 -0600 Subject: [PATCH 091/224] REF: misc cleanup Adds typing and deletes some unused functions --- src/data_model/components.jl | 18 +++ src/data_model/eng2math.jl | 2 +- src/data_model/utils.jl | 283 ++++------------------------------- src/io/utils.jl | 100 +++++++------ 4 files changed, 104 insertions(+), 299 deletions(-) diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 2b6363666..f14bd1f53 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -682,3 +682,21 @@ end for comp in keys(_eng_model_dtypes) eval(Meta.parse("add_$(comp)!(data_model, name; kwargs...) = add!(data_model, \"$comp\", name, create_$comp(; kwargs...))")) end + + +"" +function delete_component!(data_eng, comp_type, comp::Dict) + delete!(data_eng[comp_type], comp["id"]) + if isempty(data_eng[comp_type]) + delete!(data_eng, comp_type) + end +end + + +"" +function delete_component!(data_eng, comp_type, id::Any) + delete!(data_eng[comp_type], id) + if isempty(data_eng[comp_type]) + delete!(data_eng, comp_type) + end +end diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 04e137430..08b7fb0d3 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -309,7 +309,7 @@ function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An if kron_reduced filter = _kron_reduce_branch!(math_obj, - [], ["gs", "bs"], + Vector{String}([]), ["gs", "bs"], eng_obj["connections"], kr_neutral ) connections = eng_obj["connections"][filter] diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index a75060b06..d16eb12bc 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -1,7 +1,5 @@ -#import PowerModelsDistribution -#get = PowerModelsDistribution.get - +"" function scale(dict, key, scale) if haskey(dict, key) dict[key] *= scale @@ -9,47 +7,7 @@ function scale(dict, key, scale) end -function add_virtual!(data_model, comp_type, comp) - if !haskey(data_model, comp_type) - data_model[comp_type] = Dict{Any, Any}() - end - comp_dict = data_model[comp_type] - virtual_ids = [parse(Int, x[1]) for x in [match(r"_virtual_([1-9]{1}[0-9]*)", id) for id in keys(comp_dict) if isa(id, AbstractString)] if x!=nothing] - if isempty(virtual_ids) - id = "_virtual_1" - else - id = "_virtual_$(maximum(virtual_ids)+1)" - end - comp["id"] = id - comp_dict[id] = comp - return comp -end - -add_virtual_get_id!(data_model, comp_type, comp) = add_virtual!(data_model, comp_type, comp)["id"] - -function delete_component!(data_model, comp_type, comp::Dict) - delete!(data_model[comp_type], comp["id"]) - if isempty(data_model[comp_type]) - delete!(data_model, comp_type) - end -end - -function delete_component!(data_model, comp_type, id::Any) - delete!(data_model[comp_type], id) - if isempty(data_model[comp_type]) - delete!(data_model, comp_type) - end -end - -function add_mappings!(data_model::Dict{String, Any}, mapping_type::String, mappings::Vector) - if !haskey(data_model, "mappings") - data_model["mappings"] = [] - end - - append!(data_model["mappings"], [(mapping_type, mapping) for mapping in mappings]) -end - - +"" function _get_next_index(last_index, presets) new_index = last_index+1 while new_index in presets @@ -59,56 +17,7 @@ function _get_next_index(last_index, presets) end -function data_model_index!(data_model; components=["bus", "line", "shunt", "generator", "load", "transformer_2wa"], index_presets=Dict()) - comp_id2ind = Dict() - - # bus should be the first component, because we want to - for comp_type in components - comp_dict = Dict{String, Any}() - - if !haskey(index_presets, comp_type) - for (i,(id,comp)) in enumerate(data_model[comp_type]) - @assert(!haskey(comp, "index"), "$comp_type $id: component already has an index.") - comp["index"] = i - comp["id"] = id - comp_dict["$i"] = comp - end - else - last_index = 0 - - for (id, comp) in data_model[comp_type] - @assert(!haskey(comp, "index"), "$comp_type $id: component already has an index.") - if haskey(index_presets[comp_type], id) - comp["index"] = index_presets[comp_type][id] - else - comp["index"] = _get_next_index(last_index, values(index_presets[comp_type])) - last_index = comp["index"] - end - - comp["id"] = id - comp_dict["$(comp["index"])"] = comp - end - end - - data_model[comp_type] = comp_dict - comp_id2ind[comp_type] = Dict(comp["id"]=>comp["index"] for comp in values(comp_dict)) - end - - # update bus references - for comp_type in components - for (_, comp) in data_model[comp_type] - for bus_key in ["f_bus", "t_bus", "bus"] - if haskey(comp, bus_key) - comp[bus_key] = comp_id2ind["bus"][comp[bus_key]] - end - end - end - end - - return data_model -end - - +"" function solution_identify!(solution, data_model; id_prop="id") for comp_type in keys(solution) if isa(solution[comp_type], Dict) @@ -124,6 +33,8 @@ function solution_identify!(solution, data_model; id_prop="id") return solution end + +"" function add_solution!(solution, comp_type, id, data) if !haskey(solution, comp_type) solution[comp_type] = Dict() @@ -139,6 +50,7 @@ function add_solution!(solution, comp_type, id, data) end +"" function delete_solution!(solution, comp_type, id, props) if haskey(solution, comp_type) if haskey(solution[comp_type], id) @@ -150,6 +62,7 @@ function delete_solution!(solution, comp_type, id, props) end +"" function delete_solution!(solution, comp_type, id) if haskey(solution, comp_type) if haskey(solution[comp_type], id) @@ -158,7 +71,8 @@ function delete_solution!(solution, comp_type, id) end end -## + +"" function _get_new_ground(terminals) if isa(terminals, Vector{Int}) return maximum(terminals)+1 @@ -174,7 +88,8 @@ function _get_new_ground(terminals) end -function _get_ground!(bus) +"" +function _get_ground!(bus::Dict{String,<:Any}) # find perfect groundings (true ground) grounded_perfect = [] for i in 1:length(bus["grounded"]) @@ -201,7 +116,7 @@ Reference: R. C. Dugan, “A perspective on transformer modeling for distribution system analysis,” in 2003 IEEE Power Engineering Society General Meeting (IEEE Cat. No.03CH37491), 2003, vol. 1, pp. 114-119 Vol. 1. """ -function _sc2br_impedance(Zsc) +function _sc2br_impedance(Zsc::Dict{Tuple{Int,Int},Complex{Float64}})::Dict{Tuple{Int,Int},Complex} N = maximum([maximum(k) for k in keys(Zsc)]) # check whether no keys are missing # Zsc should contain tupples for upper triangle of NxN @@ -239,7 +154,7 @@ function _sc2br_impedance(Zsc) Y = [-Y*ones(N-1) Y] Y = [-ones(1,N-1)*Y; Y] # extract elements - Zbr = Dict() + Zbr = Dict{Tuple{Int,Int},Complex}() for k in keys(Zsc) Zbr[k] = (abs(Y[k...])==0) ? Inf : -1/Y[k...] end @@ -247,10 +162,8 @@ function _sc2br_impedance(Zsc) end - - -"" -function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::String, to_map::Vector{String}, r_s::Vector{Float64}, zsc::Dict{Tuple{Int,Int},Complex{Float64}}, ysh::Complex{Float64}; nphases::Int=3) +"loss model builder for transformer decomposition" +function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::String, to_map::Vector{String}, r_s::Vector{Float64}, zsc::Dict{Tuple{Int,Int},Complex{Float64}}, ysh::Complex{Float64}; nphases::Int=3)::Vector{Int} # precompute the minimal set of buses and lines N = length(r_s) tr_t_bus = collect(1:N) @@ -319,7 +232,7 @@ function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::Str end end - bus_ids = Dict() + bus_ids = Dict{Int,Int}() for bus in buses bus_obj = Dict{String,Any}( "name" => "_virtual_bus.transformer.$(transformer_name)_$(bus)", @@ -385,22 +298,22 @@ function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::Str push!(to_map, "branch.$(branch_obj["index"])") end - return [bus_ids[bus] for bus in tr_t_bus] + return Vector{Int}([bus_ids[bus] for bus in tr_t_bus]) end "" -function _kron_reduce_branch!(obj, Zs_keys, Ys_keys, terminals, neutral) - Zs = [obj[k] for k in Zs_keys] - Ys = [obj[k] for k in Ys_keys] +function _kron_reduce_branch!(object::Dict{String,<:Any}, Zs_keys::Vector{String}, Ys_keys::Vector{String}, terminals::Vector{Int}, neutral::Int)::Vector{Int} + Zs = Vector{Matrix}([object[k] for k in Zs_keys]) + Ys = Vector{Matrix}([object[k] for k in Ys_keys]) Zs_kr, Ys_kr, terminals_kr = _kron_reduce_branch(Zs, Ys, terminals, neutral) for (i,k) in enumerate(Zs_keys) - obj[k] = Zs_kr[i] + object[k] = Zs_kr[i] end for (i,k) in enumerate(Ys_keys) - obj[k] = Ys_kr[i] + object[k] = Ys_kr[i] end return _get_idxs(terminals, terminals_kr) @@ -408,9 +321,9 @@ end "" -function _kron_reduce_branch(Zs, Ys, terminals, neutral) - Zs_kr = [deepcopy(Z) for Z in Zs] - Ys_kr = [deepcopy(Y) for Y in Ys] +function _kron_reduce_branch(Zs::Vector{Matrix}, Ys::Vector{Matrix}, terminals::Vector{Int}, neutral::Int)::Tuple{Vector{Matrix}, Vector{Matrix}, Vector{Int}} + Zs_kr = Vector{Matrix}([deepcopy(Z) for Z in Zs]) + Ys_kr = Vector{Matrix}([deepcopy(Y) for Y in Ys]) terminals_kr = deepcopy(terminals) while neutral in terminals_kr @@ -418,8 +331,8 @@ function _kron_reduce_branch(Zs, Ys, terminals, neutral) P = setdiff(collect(1:length(terminals_kr)), n) if all(size(Z) == (length(terminals_kr), length(terminals_kr)) for Z in Zs_kr) - Zs_kr = [Z[P,P]-(1/Z[n,n])*Z[P,[n]]*Z[[n],P] for Z in Zs_kr] - Ys_kr = [Y[P,P] for Y in Ys_kr] + Zs_kr = Vector{Matrix}([Z[P,P]-(1/Z[n,n])*Z[P,[n]]*Z[[n],P] for Z in Zs_kr]) + Ys_kr = Vector{Matrix}([Y[P,P] for Y in Ys_kr]) end terminals_kr = terminals_kr[P] @@ -451,7 +364,7 @@ end "" -function _pad_properties_delta!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; invert=false) +function _pad_properties_delta!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; invert::Bool=false) @assert(all(c in phases for c in connections)) @assert(length(connections) in [2, 3], "A delta configuration has to have at least 2 or 3 connections!") @assert(length(phases)==3, "Padding only possible to a |phases|==3!") @@ -480,6 +393,7 @@ function _pad_properties_delta!(object::Dict{<:Any,<:Any}, properties::Vector{St end +"" function _apply_filter!(obj, properties, filter) for property in properties if haskey(obj, property) @@ -488,7 +402,7 @@ function _apply_filter!(obj, properties, filter) elseif isa(obj[property], Matrix) obj[property] = obj[property][filter, filter] else - error("The property $property is not a Vector or a Matrix!") + Memento.error(_LOGGER, "The property $property is not a Vector or a Matrix!") end end end @@ -500,7 +414,7 @@ Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the conductors 't_cnds', this method will return a list of conductors 'cnd' and a matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. """ -function calc_shunt(f_cnds::Vector{Int}, t_cnds::Vector{Int}, y::Vector{Float64}) +function calc_shunt(f_cnds::Vector{Int}, t_cnds::Vector{Int}, y::Vector{T})::Tuple{Vector{Int}, Matrix{T}} where T <: Number cnds = unique([f_cnds..., t_cnds...]) e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) @@ -514,7 +428,7 @@ Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this method will calculate the reduced addmittance matrix if terminal 'ground' is grounded. """ -function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{Float64}, ground::Int) +function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{T}, ground::Int)::Tuple{Vector{Int}, Matrix{T}} where T <: Number # TODO add types if ground in cnds cndsr = setdiff(cnds, ground) @@ -525,136 +439,3 @@ function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{Float return cnds, Y end end - - -"" -function _rm_floating_cnd(cnds::Vector{Int}, Y::Matrix{Float64}, f::Int) - P = setdiff(cnds, f) - f_inds = _get_idxs(cnds, [f]) - P_inds = _get_idxs(cnds, P) - Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] - return (P,Yrm) -end - - -"" -function _expand_linecode!(data_model) - # expand line codes - for (id, line) in data_model["line"] - if haskey(line, "linecode") - linecode = data_model["linecode"][line["linecode"]] - for key in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - line[key] = line["length"]*linecode[key] - end - delete!(line, "linecode") - delete!(line, "length") - end - end - delete!(data_model, "linecode") -end - - -"" -function _lossy_ground_to_shunt!(data_model) - mappings = [] - - if haskey(data_model, "bus") - for (id, bus) in data_model["bus"] - grounding_lossy_inds = [i for (i,t) in enumerate(bus["grounded"]) if bus["rg"][i]!=0 || bus["xg"][i]!=0] - grounding_lossy = bus["grounded"][grounding_lossy_inds] - grounding_perfect = bus["grounded"][setdiff(1:length(bus["grounded"]), grounding_lossy_inds)] - - if !isempty(grounding_lossy) - zg = bus["rg"][grounding_lossy_inds].+im*bus["xg"][grounding_lossy_inds] - Y_sh = diagm(0=>inv.(zg)) # diagonal matrix, so matrix inverse is element-wise inverse - add_virtual!(data_model, "shunt", create_shunt(bus=bus["id"], connections=grounding_lossy, - g_sh=real.(Y_sh), b_sh=imag.(Y_sh) - )) - end - end - end - return mappings -end - - -"" -function _load_to_shunt!(data_model) - mappings = [] - if haskey(data_model, "load") - for (id, load) in data_model["load"] - if load["model"]=="constant_impedance" - b = load["qd_ref"]./load["vnom"].^2*1E3 - g = load["pd_ref"]./load["vnom"].^2*1E3 - y = b.+im*g - N = length(b) - - if load["configuration"]=="delta" - # create delta transformation matrix Md - Md = diagm(0=>ones(N), 1=>-ones(N-1)) - Md[N,1] = -1 - Y = Md'*diagm(0=>y)*Md - - else # load["configuration"]=="wye" - Y_fr = diagm(0=>y) - # B = [[b]; -1'*[b]]*[I -1] - Y = vcat(Y_fr, -ones(N)'*Y_fr)*hcat(diagm(0=>ones(N)), -ones(N)) - end - - shunt = add_virtual!(data_model, "shunt", create_shunt(bus=load["bus"], connections=load["connections"], b_sh=imag.(Y), g_sh=real.(Y))) - - delete_component!(data_model, "load", load) - - push!(mappings, Dict( - "load" => load, - "shunt_id" => shunt["id"], - )) - end - end - end - - return mappings -end - - -"" -function _capacitor_to_shunt!(data_model) - mappings = [] - - if haskey(data_model, "capacitor") - for (id, cap) in data_model["capacitor"] - b = cap["qd_ref"]./cap["vnom"]^2*1E3 - N = length(b) - - if cap["configuration"]=="delta" - # create delta transformation matrix Md - Md = diagm(0=>ones(N), 1=>-ones(N-1)) - Md[N,1] = -1 - B = Md'*diagm(0=>b)*Md - - elseif cap["configuration"]=="wye-grounded" - B = diagm(0=>b) - - elseif cap["configuration"]=="wye-floating" - # this is a floating wye-segment - # B = [b]*(I-1/(b'*1)*[b';...;b']) - B = diagm(0=>b)*(diagm(0=>ones(N)) - 1/sum(b)*repeat(b',N,1)) - - else # cap["configuration"]=="wye" - B_fr = diagm(0=>b) - # B = [[b]; -1'*[b]]*[I -1] - B = vcat(B_fr, -ones(N)'*B_fr)*hcat(diagm(0=>ones(N)), -ones(N)) - end - - shunt = create_shunt(NaN, cap["bus"], cap["connections"], b_sh=B) - add_virtual!(data_model, "shunt", shunt) - delete_component!(data_model, "capacitor", cap) - - push!(mappings, Dict( - "capacitor" => cap, - "shunt_id" => shunt["id"], - )) - end - end - - return mappings -end diff --git a/src/io/utils.jl b/src/io/utils.jl index c875a23de..563bcf83f 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -1,15 +1,15 @@ "all edge types that can help define buses" -const _dss_edge_components = ["line", "transformer", "reactor", "capacitor"] +const _dss_edge_components = Vector{String}(["line", "transformer", "reactor", "capacitor"]) "components currently supported for automatic data type parsing" -const _dss_supported_components = [ +const _dss_supported_components = Vector{String}([ "line", "linecode", "load", "generator", "capacitor", "reactor", "transformer", "pvsystem", "storage", "loadshape", "options", "xfmrcode", "vsource", -] +]) "two number operators for reverse polish notation" -_double_operators = Dict( +_double_operators = Dict{String,Any}( "+" => +, "-" => -, "*" => *, @@ -19,7 +19,7 @@ _double_operators = Dict( ) "single number operators in reverse polish notation" -_single_operators = Dict( +_single_operators = Dict{String,Any}( "sqr" => x -> x * x, "sqrt" => sqrt, "inv" => inv, @@ -35,10 +35,10 @@ _single_operators = Dict( ) "different acceptable delimiters for arrays" -const _array_delimiters = ['\"', '\'', '[', '{', '(', ']', '}', ')'] +const _array_delimiters = Vector{Char}(['\"', '\'', '[', '{', '(', ']', '}', ')']) "properties that should be excluded from being overwritten during the application of `like`" -const _like_exclusions = Dict{String,Array}( +const _like_exclusions = Dict{String,Vector{String}}( "all" => ["name", "bus1", "bus2", "phases", "nphases", "enabled"], "line" => ["switch"], "transformer" => ["bank", "bus", "bus_2", "bus_3", "buses", "windings", "wdg", "wdg_2", "wdg_3"], @@ -149,7 +149,7 @@ Parses a OpenDSS style triangular matrix string `data` into a two dimensional array of type `dtype`. Matrix strings are capped by either parenthesis or brackets, rows are separated by "|", and columns are separated by spaces. """ -function _parse_matrix(dtype::Type, data::AbstractString)::Array +function _parse_matrix(dtype::Type, data::AbstractString)::Matrix{dtype} rows = [] for line in split(strip(data, _array_delimiters), '|') cols = [] @@ -195,17 +195,12 @@ end function _isa_array(data::AbstractString)::Bool clean_data = strip(data) if !occursin("|", clean_data) - if occursin(",", clean_data) - return true - elseif startswith(clean_data, "[") && endswith(clean_data, "]") - return true - elseif startswith(clean_data, "\"") && endswith(clean_data, "\"") - return true - elseif startswith(clean_data, "\'") && endswith(clean_data, "\'") - return true - elseif startswith(clean_data, "(") && endswith(clean_data, ")") - return true - elseif startswith(clean_data, "{") && endswith(clean_data, "}") + if occursin(",", clean_data) || + (startswith(clean_data, "[") && endswith(clean_data, "]")) || + (startswith(clean_data, "\"") && endswith(clean_data, "\"")) || + (startswith(clean_data, "\'") && endswith(clean_data, "\'")) || + (startswith(clean_data, "(") && endswith(clean_data, ")")) || + (startswith(clean_data, "{") && endswith(clean_data, "}")) return true else return false @@ -221,7 +216,7 @@ Parses a OpenDSS style array string `data` into a one dimensional array of type `dtype`. Array strings are capped by either brackets, single quotes, or double quotes, and elements are separated by spaces. """ -function _parse_array(dtype::Type, data::AbstractString)::Vector +function _parse_array(dtype::Type, data::AbstractString)::Vector{dtype} if occursin(",", data) split_char = ',' else @@ -330,7 +325,7 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) end -"" +"discovers all terminals in the network" function _discover_terminals!(data_eng::Dict{String,<:Any}) terminals = Dict{String, Set{Int}}([(name, Set{Int}()) for (name,bus) in data_eng["bus"]]) @@ -379,6 +374,7 @@ function _discover_terminals!(data_eng::Dict{String,<:Any}) end +"discovers all phases and neutrals in the network" function _discover_phases_neutral!(data_eng::Dict{String,<:Any}) bus_neutral = _find_neutrals(data_eng) for (id, bus) in data_eng["bus"] @@ -394,7 +390,7 @@ function _discover_phases_neutral!(data_eng::Dict{String,<:Any}) end -"" +"Discovers all neutrals in the network" function _find_neutrals(data_eng::Dict{String,<:Any}) vertices = [(id, t) for (id, bus) in data_eng["bus"] for t in bus["terminals"]] neutrals = [] @@ -432,7 +428,7 @@ end "Returns an ordered list of defined conductors. If ground=false, will omit any `0`" -function _get_conductors_ordered(busname::AbstractString; default::Array=[], check_length::Bool=true, pad_ground::Bool=false)::Array +function _get_conductors_ordered(busname::AbstractString; default::Vector{Int}=Vector{Int}([]), check_length::Bool=true, pad_ground::Bool=false)::Vector{Int} parts = split(busname, '.'; limit=2) ret = [] if length(parts)==2 @@ -454,9 +450,9 @@ function _get_conductors_ordered(busname::AbstractString; default::Array=[], che end -"" -function _import_all!(component::Dict{String,<:Any}, defaults::Dict{String,<:Any}, prop_order::Vector{String}) - component["dss"] = Dict{String,Any}((key, defaults[key]) for key in prop_order) +"creates a `dss` dict inside `object` that imports all items in `prop_order` from `dss_obj`" +function _import_all!(object::Dict{String,<:Any}, dss_obj::Dict{String,<:Any}, prop_order::Vector{String}) + object["dss"] = Dict{String,Any}((key, dss_obj[key]) for key in prop_order) end @@ -477,6 +473,7 @@ function _get_idxs(vec::Vector{<:Any}, els::Vector{<:Any})::Vector{Int} end +"" function _get_ilocs(vec::Vector{<:Any}, loc::Any)::Vector{Int} return collect(1:length(vec))[vec.==loc] end @@ -504,8 +501,8 @@ function _discover_buses(data_dss::Dict{String,<:Any})::Set end -"" -function _barrel_roll(x::Vector, shift) +"shifts a vector by `shift` spots to the left" +function _barrel_roll(x::Vector{T}, shift::Int)::Vector{T} where T N = length(x) if shift < 0 shift = shift + ceil(Int, shift/N)*N @@ -518,7 +515,7 @@ end "Parses busnames as defined in OpenDSS, e.g. \"primary.1.2.3.0\"" -function _parse_busname(busname::AbstractString) +function _parse_busname(busname::AbstractString)::Tuple{String,Vector{Bool}} parts = split(busname, '.'; limit=2) name = parts[1] elements = "1.2.3" @@ -527,7 +524,7 @@ function _parse_busname(busname::AbstractString) name, elements = split(busname, '.'; limit=2) end - nodes = Array{Bool}([0 0 0 0]) + nodes = Vector{Bool}([0, 0, 0, 0]) for num in 1:3 if occursin("$num", elements) @@ -550,7 +547,7 @@ end "" -function _apply_ordered_properties(defaults::Dict{String,<:Any}, raw_dss::Dict{String,<:Any}; code_dict::Dict{String,<:Any}=Dict{String,Any}()) +function _apply_ordered_properties(defaults::Dict{String,<:Any}, raw_dss::Dict{String,<:Any}; code_dict::Dict{String,<:Any}=Dict{String,Any}())::Dict{String,Any} _defaults = deepcopy(defaults) for prop in filter(p->p!="like", raw_dss["prop_order"]) @@ -566,7 +563,7 @@ end "applies `like` to component" -function _apply_like!(raw_dss, data_dss, comp_type) +function _apply_like!(raw_dss::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, comp_type::String) links = ["like"] if any(link in raw_dss["prop_order"] for link in links) new_prop_order = Vector{String}([]) @@ -639,7 +636,7 @@ end Parses the data in keys defined by `to_parse` in `data_dss` using types given by the default properties from the `get_prop_default` function. """ -function parse_dss_with_dtypes!(data_dss::Dict{String,<:Any}, to_parse::Array{String}=_dss_supported_components) +function parse_dss_with_dtypes!(data_dss::Dict{String,<:Any}, to_parse::Vector{String}=_dss_supported_components) for obj_type in to_parse if haskey(data_dss, obj_type) dtypes = _dss_parameter_data_types[obj_type] @@ -656,7 +653,7 @@ end "parses the raw dss values into their expected data types" -function _parse_element_with_dtype(dtype, element) +function _parse_element_with_dtype(dtype::Type, element::AbstractString) if _isa_rpn(element) out = _parse_rpn(element, dtype) elseif _isa_matrix(element) @@ -692,7 +689,7 @@ end "" -function _parse_obj_dtypes!(obj_type, object, dtypes) +function _parse_obj_dtypes!(obj_type::String, object::Dict{String,Any}, dtypes::Dict{String,Type}) for (k, v) in object if haskey(dtypes, k) if isa(v, Array) @@ -732,7 +729,7 @@ Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this method will calculate the reduced addmittance matrix if terminal 'ground' is grounded. """ -function _calc_ground_shunt_admittance_matrix(cnds, Y, ground) +function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{T}, ground::Int)::Tuple{Vector{Int}, Matrix{T}} where T # TODO add types if ground in cnds cndsr = setdiff(cnds, ground) @@ -746,37 +743,46 @@ end "" -function _rm_floating_cnd(cnds, Y, f) - # TODO add types +function _rm_floating_cnd(cnds::Vector{Int}, Y::Matrix{T}, f::Int) where T P = setdiff(cnds, f) + f_inds = _get_idxs(cnds, [f]) P_inds = _get_idxs(cnds, P) + Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] + return (P,Yrm) end "" -function _register_awaiting_ground!(bus, connections) +function _register_awaiting_ground!(bus::Dict{String,<:Any}, connections::Vector{Int}) if !haskey(bus, "awaiting_ground") bus["awaiting_ground"] = [] end + push!(bus["awaiting_ground"], connections) end -"" +"checks to see if a property is after linecode" function _is_after_linecode(prop_order::Vector{String}, property::String)::Bool - linecode_idx = 0 - property_idx = 0 + return _is_after(prop_order, property, "linecode") +end + + +"checks to see if property1 is after property2 in the prop_order" +function _is_after(prop_order::Vector{String}, property1::String, property2::String)::Bool + property1_idx = 0 + property2_idx = 0 for (i, prop) in enumerate(prop_order) - if prop == "linecode" - linecode_idx = i - elseif prop == property - property_idx = i + if prop == property1 + property1_idx = i + elseif prop == property2 + property2_idx = i end end - return linecode_idx < property_idx + return property1_idx > property2_idx end From c4d530c430c43aec725f51482141980102f0e5b1 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 13 Mar 2020 13:18:52 +1100 Subject: [PATCH 092/224] pad vmax Inf --- src/core/data.jl | 116 +++++++++++++++++++++++-------------- src/data_model/eng2math.jl | 3 +- 2 files changed, 76 insertions(+), 43 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index 8bcdd01ba..0d6551c87 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -1,6 +1,4 @@ -const _excluded_count_busname_patterns = Vector{Regex}([ - r"^_virtual.*", -]) +import LinearAlgebra: Adjoint "wraps angles in degrees to 180" function _wrap_to_180(degrees) @@ -48,13 +46,13 @@ _replace_nan(v) = map(x -> isnan(x) ? zero(x) : x, v) function count_nodes(data::Dict{String,<:Any})::Int n_nodes = 0 - if get(data, "data_model", missing) == "dss" - sourcebus = get(data["vsource"]["source"], "bus1", "sourcebus") + if get(data, "source_type", "none") == "dss" && !haskey(data, "data_model") + sourcebus = get(data["circuit"], "bus1", "sourcebus") all_nodes = Dict() - for obj_type in values(data) - for object in values(obj_type) - if isa(object, Dict) && haskey(object, "buses") - for busname in values(object["buses"]) + for comp_type in values(data) + for comp in values(comp_type) + if isa(comp, Dict) && haskey(comp, "buses") + for busname in values(comp["buses"]) name, nodes = _parse_busname(busname) if !haskey(all_nodes, name) @@ -67,8 +65,8 @@ function count_nodes(data::Dict{String,<:Any})::Int end end end - elseif isa(object, Dict) - for (prop, val) in object + elseif isa(comp, Dict) + for (prop, val) in comp if startswith(prop, "bus") && prop != "buses" name, nodes = _parse_busname(val) @@ -92,21 +90,19 @@ function count_nodes(data::Dict{String,<:Any})::Int n_nodes += length(phases) end end - elseif get(data, "data_model", missing) in ["mathematical", "engineering"] || (haskey(data, "source_type") && data["source_type"] == "matlab") - if get(data, "data_model", missing) == "mathematical" + + elseif get(data, "source_type", "none") == "dss" && haskey(data, "data_model") + + if get(data, "data_model", "mathematical") == "mathematical" Memento.info(_LOGGER, "counting nodes from PowerModelsDistribution structure may not be as accurate as directly from `parse_dss` data due to virtual buses, etc.") end n_nodes = 0 - for (name, bus) in data["bus"] - if get(data, "data_model", missing) == "matpower" || (haskey(data, "source_type") && data["source_type"] == "matlab") + for bus in values(data["bus"]) + if get(data, "source_type", "none") == "matlab" n_nodes += sum(bus["vm"] .> 0.0) - else - if data["data_model"] == "mathematical" - name = bus["name"] - end - - if all(!occursin(pattern, name) for pattern in [_excluded_count_busname_patterns..., data["sourcebus"]]) + elseif get(data, "source_type", "none") == "dss" + if !(data["source_type"] == "dss" && bus["name"] in ["_virtual_sourcebus", data["sourcebus"]]) && !(data["source_type"] == "dss" && startswith(bus["name"], "tr") && endswith(bus["name"], r"_b\d")) n_nodes += sum(bus["vmax"] .> 0.0) end end @@ -151,19 +147,18 @@ function _calc_bus_vm_ll_bounds(bus::Dict; vdmin_eps=0.1) vmax = bus["vmax"] vmin = bus["vmin"] if haskey(bus, "vm_ll_max") - vdmax = bus["vm_ll_max"]*sqrt(3) + vdmax = bus["vm_ll_max"] else # implied valid upper bound - vdmax = [1 1 0; 0 1 1; 1 0 1]*vmax + vdmax = _mat_mult_rm_nan([1 1 0; 0 1 1; 1 0 1], vmax) id = bus["index"] end if haskey(bus, "vm_ll_min") - vdmin = bus["vm_ll_min"]*sqrt(3) + vdmin = bus["vm_ll_min"] else - vdmin = ones(3)*vdmin_eps*sqrt(3) - id = bus["index"] - Memento.info(_LOGGER, "Bus $id has no phase-to-phase vm upper bound; instead, $vdmin_eps was used as a valid upper bound.") + vdmin = fill(0.0, length(vmin)) end + return (vdmin, vdmax) end @@ -176,10 +171,10 @@ function _calc_load_pq_bounds(load::Dict, bus::Dict) a, alpha, b, beta = _load_expmodel_params(load, bus) vmin, vmax = _calc_load_vbounds(load, bus) # get bounds - pmin = min.(a.*vmin.^alpha, a.*vmax.^alpha) - pmax = max.(a.*vmin.^alpha, a.*vmax.^alpha) - qmin = min.(b.*vmin.^beta, b.*vmax.^beta) - qmax = max.(b.*vmin.^beta, b.*vmax.^beta) + pmin = _nan2zero(min.(a.*vmin.^alpha, a.*vmax.^alpha), a) + pmax = _nan2zero(max.(a.*vmin.^alpha, a.*vmax.^alpha), a) + qmin = _nan2zero(min.(b.*vmin.^beta, b.*vmax.^beta), b) + qmax = _nan2zero(max.(b.*vmin.^beta, b.*vmax.^beta), b) return (pmin, pmax, qmin, qmax) end @@ -203,8 +198,8 @@ Returns magnitude bounds for the current going through the load. function _calc_load_current_magnitude_bounds(load::Dict, bus::Dict) a, alpha, b, beta = _load_expmodel_params(load, bus) vmin, vmax = _calc_load_vbounds(load, bus) - cb1 = sqrt.(a.^(2).*vmin.^(2*alpha.-2) + b.^(2).*vmin.^(2*beta.-2)) - cb2 = sqrt.(a.^(2).*vmax.^(2*alpha.-2) + b.^(2).*vmax.^(2*beta.-2)) + cb1 = sqrt.(_nan2zero(a.^(2).*vmin.^(2*alpha.-2), a) + _nan2zero(b.^(2).*vmin.^(2*beta.-2), b)) + cb2 = sqrt.(_nan2zero(a.^(2).*vmax.^(2*alpha.-2), a) + _nan2zero(b.^(2).*vmax.^(2*beta.-2), b)) cmin = min.(cb1, cb2) cmax = max.(cb1, cb2) return cmin, cmax @@ -253,10 +248,10 @@ multiphase load. These are inferred from vmin/vmax for wye loads and from _calc_bus_vm_ll_bounds for delta loads. """ function _calc_load_vbounds(load::Dict, bus::Dict) - if load["conn"]=="wye" + if load["configuration"]=="wye" vmin = bus["vmin"] vmax = bus["vmax"] - elseif load["conn"]=="delta" + elseif load["configuration"]=="delta" vmin, vmax = _calc_bus_vm_ll_bounds(bus) end return vmin, vmax @@ -407,6 +402,16 @@ function _calc_branch_power_max(branch::Dict, bus::Dict) end +""" +Returns a total (shunt+series) power magnitude bound for the from and to side +of a branch. The total current rating also implies a current bound through the +upper bound on the voltage magnitude of the connected buses. +""" +function _calc_branch_power_max_frto(branch::Dict, bus_fr::Dict, bus_to::Dict) + return _calc_branch_power_max(branch, bus_fr), _calc_branch_power_max(branch, bus_to) +end + + """ Returns a valid series current magnitude bound for a branch. """ @@ -424,15 +429,12 @@ function _calc_branch_series_current_max(branch::Dict, bus_fr::Dict, bus_to::Dic # get valid bounds on total current c_max_fr_tot = _calc_branch_current_max(branch, bus_fr) c_max_to_tot = _calc_branch_current_max(branch, bus_to) - if ismissing(c_max_fr_tot) || ismissing(c_max_to_tot) - return missing - end # get valid bounds on shunt current y_fr = branch["g_fr"] + im* branch["b_fr"] y_to = branch["g_to"] + im* branch["b_to"] - c_max_fr_sh = abs.(y_fr)*vmax_fr - c_max_to_sh = abs.(y_to)*vmax_to + c_max_fr_sh = _mat_mult_rm_nan(abs.(y_fr), vmax_fr) + c_max_to_sh = _mat_mult_rm_nan(abs.(y_to), vmax_to) # now select element-wise lowest valid bound between fr and to N = 3 #TODO update for 4-wire @@ -656,17 +658,25 @@ end "Local wrapper method for JuMP.set_lower_bound, which skips NaN and infinite (-Inf only)" -function set_lower_bound(x::JuMP.VariableRef, bound) +function set_lower_bound(x::JuMP.VariableRef, bound; loose_bounds::Bool=false, pm=missing, category=:default) if !(isnan(bound) || bound==-Inf) JuMP.set_lower_bound(x, bound) + elseif loose_bounds + lbs = pm.ext[:loose_bounds] + JuMP.set_lower_bound(x, -lbs.bound_values[category]) + push!(lbs.loose_lb_vars, x) end end "Local wrapper method for JuMP.set_upper_bound, which skips NaN and infinite (+Inf only)" -function set_upper_bound(x::JuMP.VariableRef, bound) +function set_upper_bound(x::JuMP.VariableRef, bound; loose_bounds::Bool=false, pm=missing, category=:default) if !(isnan(bound) || bound==Inf) JuMP.set_upper_bound(x, bound) + elseif loose_bounds + lbs = pm.ext[:loose_bounds] + JuMP.set_upper_bound(x, lbs.bound_values[category]) + push!(lbs.loose_ub_vars, x) end end @@ -693,3 +703,25 @@ function sol_polar_voltage!(pm::_PMs.AbstractPowerModel, solution::Dict) end end end + +# BOUND manipulation methods (0*Inf->0 is often desired) +_sum_rm_nan(X::Vector) = sum([X[(!).(isnan.(X))]..., 0.0]) + + +function _mat_mult_rm_nan(A::Matrix, B::Union{Matrix, Adjoint}) where T + N, A_ncols = size(A) + B_nrows, M = size(B) + @assert(A_ncols==B_nrows) + return [_sum_rm_nan(A[n,:].*B[:,m]) for n in 1:N, m in 1:M] +end + + +_mat_mult_rm_nan(A::Union{Matrix, Adjoint}, b::Vector) = dropdims(_mat_mult_rm_nan(A, reshape(b, length(b), 1)), dims=2) +_mat_mult_rm_nan(a::Vector, B::Union{Matrix, Adjoint}) = _mat_mult_rm_nan(reshape(a, length(a), 1), B) + + +function _nan2zero(b, a; val=0) + and(x, y) = x && y + b[and.(isnan.(b), a.==val)] .= 0.0 + return b +end diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 08b7fb0d3..b0e99b531 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -162,7 +162,8 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, @assert(all(t in kr_phases for t in terminals_kr)) _apply_filter!(math_obj, ["vm", "va", "vmin", "vmax"], filter) - _pad_properties!(math_obj, ["vm", "va", "vmin", "vmax"], terminals_kr, kr_phases) + _pad_properties!(math_obj, ["vmax"], terminals_kr, kr_phases, pad_value=Inf) + _pad_properties!(math_obj, ["vm", "va", "vmin"], terminals_kr, kr_phases) end data_math["bus"]["$(math_obj["index"])"] = math_obj From 9883a4d7ae428011560da82b435e1a37c3977123 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Fri, 13 Mar 2020 13:24:45 +1100 Subject: [PATCH 093/224] fix data.jl (changes were lost) --- src/core/data.jl | 54 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index 0d6551c87..c1f658bf6 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -1,5 +1,9 @@ import LinearAlgebra: Adjoint +const _excluded_count_busname_patterns = Vector{Regex}([ + r"^_virtual.*", +]) + "wraps angles in degrees to 180" function _wrap_to_180(degrees) return degrees - 360*floor.((degrees .+ 180)/360) @@ -46,13 +50,13 @@ _replace_nan(v) = map(x -> isnan(x) ? zero(x) : x, v) function count_nodes(data::Dict{String,<:Any})::Int n_nodes = 0 - if get(data, "source_type", "none") == "dss" && !haskey(data, "data_model") - sourcebus = get(data["circuit"], "bus1", "sourcebus") + if get(data, "data_model", missing) == "dss" + sourcebus = get(data["vsource"]["source"], "bus1", "sourcebus") all_nodes = Dict() - for comp_type in values(data) - for comp in values(comp_type) - if isa(comp, Dict) && haskey(comp, "buses") - for busname in values(comp["buses"]) + for obj_type in values(data) + for object in values(obj_type) + if isa(object, Dict) && haskey(object, "buses") + for busname in values(object["buses"]) name, nodes = _parse_busname(busname) if !haskey(all_nodes, name) @@ -65,8 +69,8 @@ function count_nodes(data::Dict{String,<:Any})::Int end end end - elseif isa(comp, Dict) - for (prop, val) in comp + elseif isa(object, Dict) + for (prop, val) in object if startswith(prop, "bus") && prop != "buses" name, nodes = _parse_busname(val) @@ -90,19 +94,21 @@ function count_nodes(data::Dict{String,<:Any})::Int n_nodes += length(phases) end end - - elseif get(data, "source_type", "none") == "dss" && haskey(data, "data_model") - - if get(data, "data_model", "mathematical") == "mathematical" + elseif get(data, "data_model", missing) in ["mathematical", "engineering"] || (haskey(data, "source_type") && data["source_type"] == "matlab") + if get(data, "data_model", missing) == "mathematical" Memento.info(_LOGGER, "counting nodes from PowerModelsDistribution structure may not be as accurate as directly from `parse_dss` data due to virtual buses, etc.") end n_nodes = 0 - for bus in values(data["bus"]) - if get(data, "source_type", "none") == "matlab" + for (name, bus) in data["bus"] + if get(data, "data_model", missing) == "matpower" || (haskey(data, "source_type") && data["source_type"] == "matlab") n_nodes += sum(bus["vm"] .> 0.0) - elseif get(data, "source_type", "none") == "dss" - if !(data["source_type"] == "dss" && bus["name"] in ["_virtual_sourcebus", data["sourcebus"]]) && !(data["source_type"] == "dss" && startswith(bus["name"], "tr") && endswith(bus["name"], r"_b\d")) + else + if data["data_model"] == "mathematical" + name = bus["name"] + end + + if all(!occursin(pattern, name) for pattern in [_excluded_count_busname_patterns..., data["sourcebus"]]) n_nodes += sum(bus["vmax"] .> 0.0) end end @@ -163,6 +169,22 @@ function _calc_bus_vm_ll_bounds(bus::Dict; vdmin_eps=0.1) end +""" +Calculates lower and upper bounds for the loads themselves (not the power +withdrawn at the bus). +""" +function _calc_load_pq_bounds(load::Dict, bus::Dict) + a, alpha, b, beta = _load_expmodel_params(load, bus) + vmin, vmax = _calc_load_vbounds(load, bus) + # get bounds + pmin = min.(a.*vmin.^alpha, a.*vmax.^alpha) + pmax = max.(a.*vmin.^alpha, a.*vmax.^alpha) + qmin = min.(b.*vmin.^beta, b.*vmax.^beta) + qmax = max.(b.*vmin.^beta, b.*vmax.^beta) + return (pmin, pmax, qmin, qmax) +end + + """ Calculates lower and upper bounds for the loads themselves (not the power withdrawn at the bus). From 540a26f68d67efc67193d934635021252c1e6bc8 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 16 Mar 2020 12:03:03 -0600 Subject: [PATCH 094/224] REF: component creation, checking, adding Updates functions for creating a data model using create_ and add_ functions, and the check functions for checking the data of components --- src/data_model/checks.jl | 470 +++++++++++++++++++++++ src/data_model/components.jl | 713 +++++++---------------------------- 2 files changed, 603 insertions(+), 580 deletions(-) create mode 100644 src/data_model/checks.jl diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl new file mode 100644 index 000000000..7a65c5244 --- /dev/null +++ b/src/data_model/checks.jl @@ -0,0 +1,470 @@ +"data check functions for the engineering data model" +const _eng_model_checks = Dict{Symbol,Symbol}( + :bus => :_check_bus, + :linecode => :_check_linecode, + # :xfmrcode => :_check_xfmrcode, + :line => :_check_line, + :transformer => :_check_transformer, + # :switch => :_check_switch, + # :line_reactor => :_check_line_reactor, + # :series_capacitor => :_check_series_capacitor, + :load => :_check_load, + :shunt_capacitor => :_check_shunt_capacitor, + # :shunt_reactor => :_check_shunt_reactor, + :shunt => :_check_shunt, + :generator => :_check_generator, + :voltage_source => :_check_voltage_source, + # :pvsystem => :_check_pvsystem, + # :storage => :_check_storage, + # :grounding => :_check_grounding, +) + +"Data types of accepted fields in the engineering data model" +const _eng_model_dtypes = Dict{Symbol,Dict{Symbol,Type}}( + :linecode => Dict{Symbol,Type}( + :rs => Array{<:Real, 2}, + :xs => Array{<:Real, 2}, + :g_fr => Array{<:Real, 2}, + :g_to => Array{<:Real, 2}, + :b_fr => Array{<:Real, 2}, + :b_to => Array{<:Real, 2} + ), + :line => Dict{Symbol,Type}( + :status => Int, + :f_bus => AbstractString, + :t_bus => AbstractString, + :f_connections => Vector{<:Int}, + :t_connections => Vector{<:Int}, + :linecode => AbstractString, + :length => Real, + :c_rating =>Vector{<:Real}, + :s_rating =>Vector{<:Real}, + :angmin=>Vector{<:Real}, + :angmax=>Vector{<:Real}, + :rs => Array{<:Real, 2}, + :xs => Array{<:Real, 2}, + :g_fr => Array{<:Real, 2}, + :g_to => Array{<:Real, 2}, + :b_fr => Array{<:Real, 2}, + :b_to => Array{<:Real, 2}, + ), + :bus => Dict{Symbol,Type}( + :status => Int, + :bus_type => Int, + :terminals => Array{<:Any}, + :phases => Array{<:Int}, + :neutral => Union{Int, Missing}, + :grounded => Array{<:Any}, + :rg => Array{<:Real}, + :xg => Array{<:Real}, + :vm_pn_min => Real, + :vm_pn_max => Real, + :vm_pp_min => Real, + :vm_pp_max => Real, + :vm_min => Array{<:Real, 1}, + :vm_max => Array{<:Real, 1}, + :vm_fix => Array{<:Real, 1}, + :va_fix => Array{<:Real, 1}, + ), + :load => Dict{Symbol,Type}( + :status => Int, + :bus => Any, + :connections => Vector, + :configuration => String, + :model => String, + :pd => Array{<:Real, 1}, + :qd => Array{<:Real, 1}, + :pd_ref => Array{<:Real, 1}, + :qd_ref => Array{<:Real, 1}, + :vnom => Array{<:Real, 1}, + :alpha => Array{<:Real, 1}, + :beta => Array{<:Real, 1}, + ), + :generator => Dict{Symbol,Type}( + :status => Int, + :bus => Any, + :connections => Vector, + :configuration => String, + :cost => Vector{<:Real}, + :pg => Array{<:Real, 1}, + :qg => Array{<:Real, 1}, + :pg_min => Array{<:Real, 1}, + :pg_max => Array{<:Real, 1}, + :qg_min => Array{<:Real, 1}, + :qg_max => Array{<:Real, 1}, + ), + :transformer => Dict{Symbol,Type}( + :status => Int, + :bus => Array{<:AbstractString, 1}, + :connections => Vector, + :vnom => Array{<:Real, 1}, + :snom => Array{<:Real, 1}, + :configuration => Array{String, 1}, + :polarity => Array{Bool, 1}, + :xsc => Array{<:Real, 1}, + :rs => Array{<:Real, 1}, + :noloadloss => Real, + :imag => Real, + :tm_fix => Array{Array{Bool, 1}, 1}, + :tm => Array{<:Array{<:Real, 1}, 1}, + :tm_min => Array{<:Array{<:Real, 1}, 1}, + :tm_max => Array{<:Array{<:Real, 1}, 1}, + :tm_step => Array{<:Array{<:Real, 1}, 1}, + ), + :shunt_capacitor => Dict{Symbol,Type}( + :status => Int, + :bus => Any, + :connections => Vector, + :configuration => String, + :qd_ref => Array{<:Real, 1}, + :vnom => Real, + ), + :shunt => Dict{Symbol,Type}( + :status => Int, + :bus => Any, + :connections => Vector, + :g_sh => Array{<:Real, 2}, + :b_sh => Array{<:Real, 2}, + ), + :voltage_source => Dict{Symbol,Type}( + :status => Int, + :bus => Any, + :connections => Vector, + :vm =>Array{<:Real}, + :va =>Array{<:Real}, + :pg_max =>Array{<:Real}, + :pg_min =>Array{<:Real}, + :qg_max =>Array{<:Real}, + :qg_min =>Array{<:Real}, + ), + :pvsystem => Dict{Symbol,Type}(), + :storage => Dict{Symbol,Type}(), + :switch => Dict{Symbol,Type}(), + :grounding => Dict{Symbol,Type}( + :bus => Any, + :rg => Real, + :xg => Real, + ), + # :ev => Dict{Symbol,Type}(), + # :wind => Dict{Symbol,Type}(), + # :autotransformer => Dict{Symbol,Type}(), + # :synchronous_generator => Dict{Symbol,Type}(), + # :zip_load => Dict{Symbol,Type}(), + # :boundary => Dict{Symbol,Type}(), + # :meter => Dict{Symbol,Type}() +) + +"required fields in the engineering data model" +const _eng_model_req_fields= Dict{Symbol,Vector{Symbol}}( + :bus => Vector{Symbol}([ + :status, :terminals, :grounded, :rg, :xg + ]), + :linecode => Vector{Symbol}([ + :rs, :xs, :g_fr, :g_to, :b_fr, :b_to + ]), + :xfmrcode => Vector{Symbol}([ + :vnom, :snom, :configuration, :polarity, :xsc, :rs, :noloadloss, :imag, + :tm_fix, :tm, :tm_min, :tm_max, :tm_step + ]), + :line => Vector{Symbol}([ + :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, + :length + ]), + :transformer => Vector{Symbol}([ + :status, :bus, :connections, :vnom, :snom, :configuration, :polarity, + :xsc, :rs, :noloadloss, :imag, :tm_fix, :tm, :tm_min, :tm_max, + :tm_step + ]), + :switch => Vector{Symbol}([]), + :line_reactor => Vector{Symbol}([]), + :series_capacitor => Vector{Symbol}([]), + :load => Vector{Symbol}([ + :status, :bus, :connections, :configuration + ]), + :shunt_capacitor => Vector{Symbol}([ + :status, :bus, :connections, :configuration, :qd_ref, :vnom + ]), + :shunt_reactor => Vector{Symbol}([]), + :shunt => Vector{Symbol}([ + :status, :bus, :connections, :g_sh, :b_sh + ]), + :generator => Vector{Symbol}([ + :status, :bus, :connections + ]), + :voltage_source => Vector{Symbol}([ + :status, :bus, :connections, :vm, :va + ]), + :pvsystem => Vector{Symbol}([]), + :storage => Vector{Symbol}([]), + :grounding => Vector{Symbol}([]), + # Future Components + # :ev => Vector{Symbol}([]), + # :wind => Vector{Symbol}([]), + # :autotransformer => Vector{Symbol}([]), + # :zip_load => Vector{Symbol}([]), + # :synchronous_generator => Vector{Symbol}([]), + # :boundary => Vector{Symbol}([]), + # :meter => Vector{Symbol}([]) +) + + +"checks that an engineering model component has the correct data types" +function _check_eng_component_dtypes(data_eng::Dict{String,<:Any}, component_type::String, component_name::String; additional_dtypes=Dict{Symbol,Type}()) + if haskey(_eng_model_dtypes, Symbol(component_type)) + dtypes = merge(_eng_model_dtypes[Symbol(component_type)], additional_dtypes) + else + dtypes = additional_dtypes + end + + if haskey(data_eng, component_type) && haskey(data_eng[component_type], component_name) + component = data_eng[component_type][component_name] + + for (field, dtype) in dtypes + if haskey(component, string(field)) + @assert isa(component[field], dtype) "$component_type $component_name: the property $field should be a $dtype, not a $(typeof(component[field]))" + end + end + else + Memento.warn(_LOGGER, "$component_type $component_name does not exist") + end +end + + + +"check that all data in `fields` have the same size" +function _check_same_size(component::Dict{String,<:Any}, fields::Vector{String}; context::Union{String,Missing}=missing) + @assert length(unique([size(component[string(field)]) for field in fields])) == 1 "$context: not all properties are the same size" +end + + +"check that `fields` has size `data_size`" +function _check_has_size(component::Dict{String,<:Any}, fields::Vector{String}, data_size::Tuple; context::Union{String,Missing}=missing, allow_missing::Bool=true) + for key in keys + if haskey(component, key) || !allow_missing + @assert all(size(component[string(key)]).==data_size) "$context: the property $key should have as size $data_size" + end + end +end + + +"checks connectivity of object" +function _check_connectivity(data_eng::Dict{String,<:Any}, object::Dict{String,<:Any}; context::Union{String,Missing}=missing) + if haskey(object, "f_bus") + # two-port element + _check_bus_and_terminals(data_eng, object["f_bus"], object["f_connections"], context) + _check_bus_and_terminals(data_eng, object["t_bus"], object["t_connections"], context) + elseif haskey(object, "bus") + if isa(object["bus"], Vector) + for i in 1:length(object["bus"]) + _check_bus_and_terminals(data_eng, object["bus"][i], object["connections"][i], context) + end + else + _check_bus_and_terminals(data_eng, object["bus"], object["connections"], context) + end + end +end + + +"checks `bus_name` exists and has `terminals`" +function _check_bus_and_terminals(data_eng::Dict{String,<:Any}, bus_name::Any, terminals::Vector{Int}, context::Union{String,Missing}=missing) + @assert haskey(data_eng, "bus") && haskey(data_eng["bus"], bus_name) "$context: the bus $bus_name is not defined." + + bus = data_eng["bus"][bus_name] + for t in terminals + @assert t in bus["terminals"] "$context: bus $(bus["obj_name"]) does not have terminal \'$t\'." + end +end + + +"checks that a component has `fields`" +function _check_has_keys(object::Dict{String,<:Any}, fields::Vector{String}; context::Union{String,Missing}=missing) + for key in fields + @assert haskey(object, key) "$context: the property $key is missing." + end +end + + +"checks the connection configuration and infers the dimensions of the connection (number of connected terminals)" +function _check_configuration_infer_dim(object::Dict{String,<:Any}; context::Union{String,Missing}=missing)::Int + conf = object["configuration"] + @assert conf in ["delta", "wye"] "$context: the configuration should be \'delta\' or \'wye\', not \'$conf\'." + + return conf=="wye" ? length(object["connections"])-1 : length(object["connections"]) +end + + +"checks the engineering data model for correct data types, required fields and applies default checks" +function check_eng_data_model(data_eng::Dict{String,<:Any}) + for (component_type, components) in data_eng + if isa(components, Dict) + for (name, component) in keys(components) + _check_eng_component_dtypes(data_eng, component_type, name) + + for field in get(_eng_model_req_fields, Symbol(component_type), Vector{Symbol}([])) + @assert haskey(component, string(field)) "The property \'$field\' is missing on $component_type $name" + end + + for check in get(_eng_model_checks, Symbol(component_type), missing) + if !ismissing(check) + @eval $(check)(data_eng, name) + end + end + end + end + end +end + + +"bus data checks" +function _check_bus(data_eng::Dict{String,<:Any}, name::Any) + bus = data_eng["bus"][name] + + _check_same_size(bus, ["grounded", "rg", "xg"], context="bus $name") + + N = length(bus["terminals"]) + _check_has_size(bus, ["vm_max", "vm_min", "vm", "va"], N, context="bus $name") + + if haskey(bus, "neutral") + @assert haskey(bus, "phases") "bus $name: has a neutral, but no phases." + end +end + + +"load data checks" +function _check_load(data_eng::Dict{String,<:Any}, name::Any) + load = data_eng["load"][name] + + N = _check_configuration_infer_dim(load; context="load $name") + + model = load["model"] + @assert model in ["constant_power", "constant_impedance", "constant_current", "exponential"] + + if model=="constant_power" + _check_has_keys(load, ["pd", "qd"], context="load $name, $model:") + _check_has_size(load, ["pd", "qd"], N, context="load $name, $model:") + elseif model=="exponential" + _check_has_keys(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], context="load $name, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], N, context="load $name, $model:") + else + _check_has_keys(load, ["pd_ref", "qd_ref", "vnom"], context="load $name, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vnom"], N, context="load $name, $model:") + end + + _check_connectivity(data_eng, load; context="load $name") +end + + +"linecode data checks" +function _check_linecode(data_eng::Dict{String,<:Any}, name::Any) + _check_same_size(data_eng["linecode"][name], [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to]) +end + + +"line data checks" +function _check_line(data_eng::Dict{String,<:Any}, name::Any) + line = data_eng["line"][name] + + # for now, always require a line code + if haskey(line, "linecode") + # line is defined with a linecode + @assert haskey(line, "length") "line $name: a line defined through a linecode, should have a length property." + + linecode_obj_name = line["linecode"] + @assert haskey(data_eng, "linecode") && haskey(data_eng["linecode"], "$linecode_obj_name") "line $name: the linecode $linecode_obj_name is not defined." + linecode = data_eng["linecode"]["$linecode_obj_name"] + + for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + @assert !haskey(line, key) "line $name: a line with a linecode, should not specify $key; this is already done by the linecode." + end + + N = size(linecode["rs"])[1] + @assert length(line["f_connections"])==N "line $name: the number of terminals should match the number of conductors in the linecode." + @assert length(line["t_connections"])==N "line $name: the number of terminals should match the number of conductors in the linecode." + else + # normal line + @assert !haskey(line, "length") "line $name: length only makes sense for linees defined through linecodes." + for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + @assert haskey(line, key) "line $name: a line without linecode, should specify $key." + end + end + + _check_connectivity(data_eng, line, context="line $(name)") +end + + +"generator data checks" +function _check_generator(data_eng::Dict{String,<:Any}, name::Any) + generator = data_eng["generator"][name] + + N = _check_configuration_infer_dim(generator; context="generator $name") + _check_has_size(generator, ["pd", "qd", "pd_min", "pd_max", "qd_min", "qd_max"], N, context="generator $name") + + _check_connectivity(data_eng, generator; context="generator $name") +end + + +"Transformer, n-windings three-phase lossy data checks" +function _check_transformer(data_eng::Dict{String,<:Any}, name::Any) + transformer = data_eng["transformer"][name] + + nrw = length(transformer["bus"]) + _check_has_size(transformer, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_min", "tm_max", "tm_step"], nrw, context="trans $name") + + @assert length(transformer["xsc"])==(nrw^2-nrw)/2 + + nphs = [] + for w in 1:nrw + @assert transformer["configuration"][w] in ["wye", "delta"] + + conf = transformer["configuration"][w] + conns = transformer["connections"][w] + nph = conf=="wye" ? length(conns)-1 : length(conns) + @assert all(nph.==nphs) "transformer $name: winding $w has a different number of phases than the previous ones." + + push!(nphs, nph) + #TODO check length other properties + end + + _check_connectivity(data_eng, transformer; context="transformer_nw $name") +end + + +"shunt capacitor data checks" +function _check_shunt_capacitor(data_eng::Dict{String,<:Any}, name::Any) + shunt_capacitor = data_eng["shunt_capacitor"][name] + + N = length(shunt_capacitor["connections"]) + config = shunt_capacitor["configuration"] + if config=="wye" + @assert length(shunt_capacitor["qd_ref"])==N-1 "capacitor $name: qd_ref should have $(N-1) elements." + else + @assert length(shunt_capacitor["qd_ref"])==N "capacitor $name: qd_ref should have $N elements." + end + + @assert config in ["delta", "wye", "wye-grounded" "wye-floating"] + + if config=="delta" + @assert N>=3 "Capacitor $name: delta-connected capacitors should have at least 3 elements." + end + + _check_connectivity(data_eng, shunt_capacitor; context="capacitor $name") +end + + +"shunt data checks" +function _check_shunt(data_eng::Dict{String,<:Any}, name::Any) + shunt = data_eng["shunt"][name] + + _check_connectivity(data_eng, shunt; context="shunt $name") +end + + +"voltage source data checks" +function _check_voltage_source(data_eng::Dict{String,<:Any}, name::Any) + voltage_source = data_eng["voltage_source"][name] + + _check_connectivity(data_eng, voltage_source; context="voltage source $name") + + N = length(voltage_source["connections"]) + _check_has_size(voltage_source, ["vm", "va", "pg_max", "pg_min", "qg_max", "qg_min"], N, context="voltage source $name") +end diff --git a/src/data_model/components.jl b/src/data_model/components.jl index f14bd1f53..454c08f88 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -3,339 +3,37 @@ # Add current/power bounds to data model -function copy_kwargs_to_dict_if_present!(dict, kwargs, args) - for arg in args - if haskey(kwargs, arg) - dict[string(arg)] = kwargs[arg] +"adds kwargs that were specified but unused by the required defaults to the component" +function _add_unused_kwargs!(object::Dict{String,<:Any}, kwargs::Dict{Symbol,<:Any}) + for (property, value) in kwargs + if !haskey(object, string(property)) + object[string(property)] = value end end end -function add_kwarg!(dict, kwargs, name, default) - if haskey(kwargs, name) - dict[string(name)] = kwargs[name] - else - dict[string(name)] = default - end -end - -function component_dict_from_list!(list) - dict = Dict{String, Any}() - for object in list - dict[object["obj_name"]] = object - end - return dict -end - -const _eng_model_dtypes = Dict{Symbol,Dict{Symbol,Type}}( - :linecode => Dict{Symbol,Type}( - :obj_name => Any, - :rs => Array{<:Real, 2}, - :xs => Array{<:Real, 2}, - :g_fr => Array{<:Real, 2}, - :g_to => Array{<:Real, 2}, - :b_fr => Array{<:Real, 2}, - :b_to => Array{<:Real, 2} - ), - :line => Dict{Symbol,Type}( - :obj_name => Any, - :status => Int, - :f_bus => AbstractString, - :t_bus => AbstractString, - :f_connections => Vector{<:Int}, - :t_connections => Vector{<:Int}, - :linecode => AbstractString, - :length => Real, - :c_rating =>Vector{<:Real}, - :s_rating =>Vector{<:Real}, - :angmin=>Vector{<:Real}, - :angmax=>Vector{<:Real}, - :rs => Array{<:Real, 2}, - :xs => Array{<:Real, 2}, - :g_fr => Array{<:Real, 2}, - :g_to => Array{<:Real, 2}, - :b_fr => Array{<:Real, 2}, - :b_to => Array{<:Real, 2}, - ), - :bus => Dict{Symbol,Type}( - :obj_name => Any, - :status => Int, - :bus_type => Int, - :terminals => Array{<:Any}, - :phases => Array{<:Int}, - :neutral => Union{Int, Missing}, - :grounded => Array{<:Any}, - :rg => Array{<:Real}, - :xg => Array{<:Real}, - :vm_pn_min => Real, - :vm_pn_max => Real, - :vm_pp_min => Real, - :vm_pp_max => Real, - :vm_min => Array{<:Real, 1}, - :vm_max => Array{<:Real, 1}, - :vm_fix => Array{<:Real, 1}, - :va_fix => Array{<:Real, 1}, - ), - :load => Dict{Symbol,Type}( - :obj_name => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :configuration => String, - :model => String, - :pd => Array{<:Real, 1}, - :qd => Array{<:Real, 1}, - :pd_ref => Array{<:Real, 1}, - :qd_ref => Array{<:Real, 1}, - :vnom => Array{<:Real, 1}, - :alpha => Array{<:Real, 1}, - :beta => Array{<:Real, 1}, - ), - :generator => Dict{Symbol,Type}( - :obj_name => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :configuration => String, - :cost => Vector{<:Real}, - :pg => Array{<:Real, 1}, - :qg => Array{<:Real, 1}, - :pg_min => Array{<:Real, 1}, - :pg_max => Array{<:Real, 1}, - :qg_min => Array{<:Real, 1}, - :qg_max => Array{<:Real, 1}, - ), - :transformer_nw => Dict{Symbol,Type}( - :obj_name => Any, - :status => Int, - :bus => Array{<:AbstractString, 1}, - :connections => Vector, - :vnom => Array{<:Real, 1}, - :snom => Array{<:Real, 1}, - :configuration => Array{String, 1}, - :polarity => Array{Bool, 1}, - :xsc => Array{<:Real, 1}, - :rs => Array{<:Real, 1}, - :noloadloss => Real, - :imag => Real, - :tm_fix => Array{Array{Bool, 1}, 1}, - :tm => Array{<:Array{<:Real, 1}, 1}, - :tm_min => Array{<:Array{<:Real, 1}, 1}, - :tm_max => Array{<:Array{<:Real, 1}, 1}, - :tm_step => Array{<:Array{<:Real, 1}, 1}, - ), - :capacitor => Dict{Symbol,Type}( - :obj_name => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :configuration => String, - :qd_ref => Array{<:Real, 1}, - :vnom => Real, - ), - :shunt => Dict{Symbol,Type}( - :obj_name => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :g_sh => Array{<:Real, 2}, - :b_sh => Array{<:Real, 2}, - ), - :voltage_source => Dict{Symbol,Type}( - :obj_name => Any, - :status => Int, - :bus => Any, - :connections => Vector, - :vm =>Array{<:Real}, - :va =>Array{<:Real}, - :pg_max =>Array{<:Real}, - :pg_min =>Array{<:Real}, - :qg_max =>Array{<:Real}, - :qg_min =>Array{<:Real}, - ), - :ev => Dict{Symbol,Type}(), - :storage => Dict{Symbol,Type}(), - :pv => Dict{Symbol,Type}(), - :wind => Dict{Symbol,Type}(), - :switch => Dict{Symbol,Type}(), - :autotransformer => Dict{Symbol,Type}(), - :synchronous_generator => Dict{Symbol,Type}(), - :zip_load => Dict{Symbol,Type}(), - :grounding => Dict{Symbol,Type}( - :bus => Any, - :rg => Real, - :xg => Real, - ), - :boundary => Dict{Symbol,Type}(), - :meter => Dict{Symbol,Type}() -) - -const _eng_model_req_fields= Dict{Symbol,Array{Symbol,1}}( - :linecode => Array{Symbol,1}([:obj_name, :rs, :xs, :g_fr, :g_to, :b_fr, :b_to]), - :line => Array{Symbol,1}([:obj_name, :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, :length]), - :bus => Array{Symbol,1}([:obj_name, :status, :terminals, :grounded, :rg, :xg]), - :load => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :configuration]), - :generator => Array{Symbol,1}([:obj_name, :status, :bus, :connections]), - :transformer_nw => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :vnom, :snom, :configuration, :polarity, :xsc, :rs, :noloadloss, :imag, :tm_fix, :tm, :tm_min, :tm_max, :tm_step]), - :capacitor => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :configuration, :qd_ref, :vnom]), - :shunt => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :g_sh, :b_sh]), - :voltage_source => Array{Symbol,1}([:obj_name, :status, :bus, :connections, :vm, :va]), - :ev => Array{Symbol,1}([]), - :storage => Array{Symbol,1}([]), - :pv => Array{Symbol,1}([]), - :wind => Array{Symbol,1}([]), - :switch => Array{Symbol,1}([]), - :autotransformer => Array{Symbol,1}([]), - :synchronous_generator => Array{Symbol,1}([]), - :zip_load => Array{Symbol,1}([]), - :grounding => Array{Symbol,1}([]), - :boundary => Array{Symbol,1}([]), - :meter => Array{Symbol,1}([]) -) - -_eng_model_checks = Dict() - - -"" -function check__eng_model_dtypes(dict, _eng_model_dtypes, comp_type, obj_name) - for key in keys(dict) - symb = Symbol(key) - if haskey(_eng_model_dtypes, symb) - @assert(isa(dict[key], _eng_model_dtypes[symb]), "$comp_type $obj_name: the property $key should be a $(_eng_model_dtypes[symb]), not a $(typeof(dict[key])).") - else - #@assert(false, "$comp_type $obj_name: the property $key is unknown.") - end - end -end -"" -function add!(data_eng::Dict{String,<:Any}, obj_type::AbstractString, obj_name::AbstractString, object::Dict{String,<:Any}) - # @assert(haskey(object, "obj_name"), "The component does not have an obj_name defined.") +"Generic add function to add components to an engineering data model" +function add_object!(data_eng::Dict{String,<:Any}, obj_type::String, obj_id::Any, object::Dict{String,<:Any}) if !haskey(data_eng, obj_type) - data_eng[obj_type] = Dict{String,Any}() - else - @assert(!haskey(data_eng[obj_type], obj_name), "There is already a $obj_type with name $obj_name.") - end - - data_eng[obj_type][obj_name] = object -end - - -"" -function _add_unused_kwargs!(object, kwargs) - for (prop, val) in kwargs - if !haskey(object, "$prop") - object["$prop"] = val - end + data_eng[obj_type] = Dict{Any,Any}() end -end - -"" -function check_data_model(data) - for component in keys(_eng_model_dtypes) - if haskey(data, string(component)) - for (obj_name, object) in data[string(component)] - if haskey(_eng_model_req_fields, component) - for field in _eng_model_req_fields[component] - @assert(haskey(object, string(field)), "The property \'$field\' is missing for $component $obj_name.") - end - end - if haskey(_eng_model_dtypes, component) - check__eng_model_dtypes(object, _eng_model_dtypes[component], component, obj_name) - end - if haskey(_eng_model_checks, component) - _eng_model_checks[component](data, object) - end - end - end - end + data_eng[obj_type][obj_id] = object end "" -function create_data_model(; kwargs...) - data_model = Dict{String, Any}("settings"=>Dict{String, Any}()) - - add_kwarg!(data_model["settings"], kwargs, :v_var_scalar, 1e3) - - _add_unused_kwargs!(data_model["settings"], kwargs) - - return data_model -end - - -"" -function _check_same_size(data, keys; context=missing) - size_comp = size(data[string(keys[1])]) - for key in keys[2:end] - @assert(all(size(data[string(key)]).==size_comp), "$context: the property $key should have the same size as $(keys[1]).") - end -end - - -"" -function _check_has_size(data, keys, size_comp; context=missing, allow_missing=true) - for key in keys - if haskey(data, key) || !allow_missing - @assert(all(size(data[string(key)]).==size_comp), "$context: the property $key should have as size $size_comp.") - end - end -end - - -"" -function _check_connectivity(data, object; context=missing) - if haskey(object, "f_bus") - # two-port element - _check_bus_and_terminals(data, object["f_bus"], object["f_connections"], context) - _check_bus_and_terminals(data, object["t_bus"], object["t_connections"], context) - elseif haskey(object, "bus") - if isa(object["bus"], Vector) - for i in 1:length(object["bus"]) - _check_bus_and_terminals(data, object["bus"][i], object["connections"][i], context) - end - else - _check_bus_and_terminals(data, object["bus"], object["connections"], context) - end - end -end - - -"" -function _check_bus_and_terminals(data, bus_obj_name, terminals, context=missing) - @assert(haskey(data, "bus") && haskey(data["bus"], bus_obj_name), "$context: the bus $bus_obj_name is not defined.") - bus = data["bus"][bus_obj_name] - for t in terminals - @assert(t in bus["terminals"], "$context: bus $(bus["obj_name"]) does not have terminal \'$t\'.") - end -end - - -"" -function _check_has_keys(object, keys; context=missing) - for key in keys - @assert(haskey(object, key), "$context: the property $key is missing.") - end -end - - -"" -function _check_configuration_infer_dim(object; context=missing) - conf = object["configuration"] - @assert(conf in ["delta", "wye"], "$context: the configuration should be \'delta\' or \'wye\', not \'$conf\'.") - return conf=="wye" ? length(object["connections"])-1 : length(object["connections"]) -end - - -# linecode -_eng_model_checks[:linecode] = function check_linecode(data, linecode) - _check_same_size(linecode, [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to]) -end - function create_linecode(; kwargs...) - linecode = Dict{String,Any}() + linecode = Dict{String,Any}( + "rs" => get(kwargs, :rs, fill(0.0, n_conductors, n_conductors)), + "xs" => get(kwargs, :xs, fill(0.0, n_conductors, n_conductors)), + "g_fr" => get(kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)), + "b_fr" => get(kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)), + "g_to" => get(kwargs, :g_to, fill(0.0, n_conductors, n_conductors)), + "b_to" => get(kwargs, :b_to, fill(0.0, n_conductors, n_conductors)), +) n_conductors = 0 for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] @@ -343,60 +41,36 @@ function create_linecode(; kwargs...) n_conductors = size(kwargs[key])[1] end end - add_kwarg!(linecode, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(linecode, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) _add_unused_kwargs!(linecode, kwargs) return linecode end -# line -_eng_model_checks[:line] = function check_line(data, line) - i = line["obj_name"] - # for now, always require a line code - if haskey(line, "linecode") - # line is defined with a linecode - @assert(haskey(line, "length"), "line $i: a line defined through a linecode, should have a length property.") +"creates an engineering model" +function create_eng_model(name; kwargs...)::Dict{String,Any} + kwargs = Dict{Symbol,Any}(kwargs) - linecode_obj_name = line["linecode"] - @assert(haskey(data, "linecode") && haskey(data["linecode"], "$linecode_obj_name"), "line $i: the linecode $linecode_obj_name is not defined.") - linecode = data["linecode"]["$linecode_obj_name"] - - for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - @assert(!haskey(line, key), "line $i: a line with a linecode, should not specify $key; this is already done by the linecode.") - end + data_model = Dict{String,Any}( + "name" => name, + "data_model" => "engineering", + "settings"=>Dict{String,Any}( + "v_var_scalar" = get(kwargs, :v_var_scalar, 1e3) + ) + ) - N = size(linecode["rs"])[1] - @assert(length(line["f_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") - @assert(length(line["t_connections"])==N, "line $i: the number of terminals should match the number of conductors in the linecode.") - else - # normal line - @assert(!haskey(line, "length"), "line $i: length only makes sense for linees defined through linecodes.") - for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - @assert(haskey(line, key), "line $i: a line without linecode, should specify $key.") - end - end + _add_unused_kwargs!(data_model["settings"], kwargs) - _check_connectivity(data, line, context="line $(line["obj_name"])") + return data_model end +"" function create_line(; kwargs...) - line = Dict{String,Any}() + kwargs = Dict{Symbol,Any}(kwargs) - add_kwarg!(line, kwargs, :status, 1) - add_kwarg!(line, kwargs, :f_connections, collect(1:4)) - add_kwarg!(line, kwargs, :t_connections, collect(1:4)) - - N = length(line["f_connections"]) - add_kwarg!(line, kwargs, :angmin, fill(-60/180*pi, N)) - add_kwarg!(line, kwargs, :angmax, fill( 60/180*pi, N)) + N = length(get(kwargs, :f_connections, collect(1:4))) # if no linecode, then populate loss parameters with zero if !haskey(kwargs, :linecode) @@ -406,84 +80,64 @@ function create_line(; kwargs...) n_conductors = size(kwargs[key])[1] end end - add_kwarg!(line, kwargs, :rs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :xs, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :g_to, fill(0.0, n_conductors, n_conductors)) - add_kwarg!(line, kwargs, :b_to, fill(0.0, n_conductors, n_conductors)) end + line = Dict{String,Any}( + "status" => get(kwargs, :status, 1), + "f_connections" => get(kwargs, :f_connections, collect(1:4)), + "t_connections" => get(kwargs, :t_connections, collect(1:4)), + "angmin" => get(kwargs, :angmin, fill(-60/180*pi, N)), + "angmax" => get(kwargs, :angmax, fill( 60/180*pi, N)), + "rs" => get(kwargs, :rs, fill(0.0, n_conductors, n_conductors)), + "xs" => get(kwargs, :xs, fill(0.0, n_conductors, n_conductors)), + "g_fr" => get(kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)), + "b_fr" => get(kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)), + "g_to" => get(kwargs, :g_to, fill(0.0, n_conductors, n_conductors)), + "b_to" => get(kwargs, :b_to, fill(0.0, n_conductors, n_conductors)), + ) + _add_unused_kwargs!(line, kwargs) return line end -# Bus -_eng_model_checks[:bus] = function check_bus(data, bus) - obj_name = bus["obj_name"] - - _check_same_size(bus, ["grounded", "rg", "xg"], context="bus $obj_name") - - N = length(bus["terminals"]) - _check_has_size(bus, ["vm_max", "vm_min", "vm", "va"], N, context="bus $obj_name") - - if haskey(bus, "neutral") - assert(haskey(bus, "phases"), "bus $obj_name: has a neutral, but no phases.") - end -end +"creates a bus object with some defaults" function create_bus(; kwargs...) - bus = Dict{String,Any}() + kwargs = Dict{Symbol,Any}(kwargs) - add_kwarg!(bus, kwargs, :status, 1) - add_kwarg!(bus, kwargs, :terminals, collect(1:4)) - add_kwarg!(bus, kwargs, :grounded, []) - add_kwarg!(bus, kwargs, :bus_type, 1) - add_kwarg!(bus, kwargs, :rg, Array{Float64, 1}()) - add_kwarg!(bus, kwargs, :xg, Array{Float64, 1}()) + bus = Dict{String,Any}( + "status" => get(kwargs, :status, 1), + "terminals" => get(kwargs, :terminals, collect(1:4)), + "grounded" => get(kwargs, :grounded, []), + "bus_type" => get(kwargs, :bus_type, 1), + "rg" => get(kwargs, :rg, Array{Float64, 1}()), + "xg" => get(kwargs, :xg, Array{Float64, 1}()), + ) _add_unused_kwargs!(bus, kwargs) return bus end -# Load -_eng_model_checks[:load] = function check_load(data, load) - obj_name = load["obj_name"] - - N = _check_configuration_infer_dim(load; context="load $obj_name") - - model = load["model"] - @assert(model in ["constant_power", "constant_impedance", "constant_current", "exponential"]) - if model=="constant_power" - _check_has_keys(load, ["pd", "qd"], context="load $obj_name, $model:") - _check_has_size(load, ["pd", "qd"], N, context="load $obj_name, $model:") - elseif model=="exponential" - _check_has_keys(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], context="load $obj_name, $model") - _check_has_size(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], N, context="load $obj_name, $model:") - else - _check_has_keys(load, ["pd_ref", "qd_ref", "vnom"], context="load $obj_name, $model") - _check_has_size(load, ["pd_ref", "qd_ref", "vnom"], N, context="load $obj_name, $model:") - end - - _check_connectivity(data, load; context="load $obj_name") -end - +"creates a load object with some defaults" function create_load(; kwargs...) - load = Dict{String,Any}() + kwargs = Dict{Symbol,Any}(kwargs) - add_kwarg!(load, kwargs, :status, 1) - add_kwarg!(load, kwargs, :configuration, "wye") - add_kwarg!(load, kwargs, :connections, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) - add_kwarg!(load, kwargs, :model, "constant_power") + load = Dict{String,Any}( + "status" => get(kwargs, :status, 1), + "configuration" => get(kwargs, :configuration, "wye"), + "model" => get(kwargs, :model, "constant_power"), + ) + + load["connections"] = get(kwargs, :connections, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]), if load["model"]=="constant_power" - add_kwarg!(load, kwargs, :pd, fill(0.0, 3)) - add_kwarg!(load, kwargs, :qd, fill(0.0, 3)) + load["pd"] get(kwargs, :pd, fill(0.0, 3)) + load["qd"] get(kwargs, :qd, fill(0.0, 3)) else - add_kwarg!(load, kwargs, :pd_ref, fill(0.0, 3)) - add_kwarg!(load, kwargs, :qd_ref, fill(0.0, 3)) + load["pd_ref"] => get(kwargs, :pd_ref, fill(0.0, 3)) + load["qd_ref"] => get(kwargs, :qd_ref, fill(0.0, 3)) end _add_unused_kwargs!(load, kwargs) @@ -491,23 +145,18 @@ function create_load(; kwargs...) return load end -# generator -_eng_model_checks[:generator] = function check_generator(data, generator) - obj_name = generator["obj_name"] - - N = _check_configuration_infer_dim(generator; context="generator $obj_name") - _check_has_size(generator, ["pd", "qd", "pd_min", "pd_max", "qd_min", "qd_max"], N, context="generator $obj_name") - - _check_connectivity(data, generator; context="generator $obj_name") -end +"creates a generator object with some defaults" function create_generator(; kwargs...) - generator = Dict{String,Any}() + kwargs = Dict{Symbol,Any}(kwargs) - add_kwarg!(generator, kwargs, :status, 1) - add_kwarg!(generator, kwargs, :configuration, "wye") - add_kwarg!(generator, kwargs, :cost, [1.0, 0.0]*1E-3) - add_kwarg!(generator, kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) + generator = Dict{String,Any}( + "status" => get(kwargs, :status, 1), + "configuration" => get(kwargs, :configuration, "wye"), + "cost" => get(kwargs, :cost, [1.0, 0.0]*1E-3), + ) + + generator["connections"] = get(kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) _add_unused_kwargs!(generator, kwargs) @@ -515,140 +164,60 @@ function create_generator(; kwargs...) end -# Transformer, n-windings three-phase lossy -_eng_model_checks[:transformer_nw] = function check_transformer_nw(data, trans) - obj_name = trans["obj_name"] - nrw = length(trans["bus"]) - _check_has_size(trans, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_min", "tm_max", "tm_step"], nrw, context="trans $obj_name") - @assert(length(trans["xsc"])==(nrw^2-nrw)/2) - - nphs = [] - for w in 1:nrw - @assert(trans["configuration"][w] in ["wye", "delta"]) - conf = trans["configuration"][w] - conns = trans["connections"][w] - nph = conf=="wye" ? length(conns)-1 : length(conns) - @assert(all(nph.==nphs), "transformer $obj_name: winding $w has a different number of phases than the previous ones.") - push!(nphs, nph) - #TODO check length other properties - end - - _check_connectivity(data, trans; context="transformer_nw $obj_name") -end - - -function create_transformer_nw(; kwargs...) - trans = Dict{String,Any}() +"creates a n-winding transformer object with some defaults" +function create_transformer(; kwargs...) + kwargs = Dict{Symbol,Any}(kwargs) - @assert(haskey(kwargs, :bus), "You have to specify at least the buses.") + @assert haskey(kwargs, :bus) "bus must be defined at the very least" n_windings = length(kwargs[:bus]) - add_kwarg!(trans, kwargs, :status, 1) - add_kwarg!(trans, kwargs, :configuration, fill("wye", n_windings)) - add_kwarg!(trans, kwargs, :polarity, fill(true, n_windings)) - add_kwarg!(trans, kwargs, :rs, fill(0.0, n_windings)) - add_kwarg!(trans, kwargs, :xsc, fill(0.0, n_windings^2-n_windings)) - add_kwarg!(trans, kwargs, :noloadloss, 0.0) - add_kwarg!(trans, kwargs, :imag, 0.0) - add_kwarg!(trans, kwargs, :tm, fill(fill(1.0, 3), n_windings)) - add_kwarg!(trans, kwargs, :tm_min, fill(fill(0.9, 3), n_windings)) - add_kwarg!(trans, kwargs, :tm_max, fill(fill(1.1, 3), n_windings)) - add_kwarg!(trans, kwargs, :tm_step, fill(fill(1/32, 3), n_windings)) - add_kwarg!(trans, kwargs, :tm_fix, fill(fill(true, 3), n_windings)) - - _add_unused_kwargs!(trans, kwargs) - - return trans -end - -# -# # Transformer, two-winding three-phase -# -# _eng_model_dtypes[:transformer_2w_obj_nameeal] = Dict( -# :obj_name => Any, -# :f_bus => String, -# :t_bus => String, -# :configuration => String, -# :f_terminals => Array{Int, 1}, -# :t_terminals => Array{Int, 1}, -# :tm_nom => Real, -# :tm_set => Real, -# :tm_min => Real, -# :tm_max => Real, -# :tm_step => Real, -# :tm_fix => Real, -# ) -# -# -# _eng_model_checks[:transformer_2w_obj_nameeal] = function check_transformer_2w_obj_nameeal(data, trans) -# end -# -# -# function create_transformer_2w_obj_nameeal(obj_name, f_bus, t_bus, tm_nom; kwargs...) -# trans = Dict{String,Any}() -# trans["obj_name"] = obj_name -# trans["f_bus"] = f_bus -# trans["t_bus"] = t_bus -# trans["tm_nom"] = tm_nom -# add_kwarg!(trans, kwargs, :configuration, "wye") -# add_kwarg!(trans, kwargs, :f_terminals, trans["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) -# add_kwarg!(trans, kwargs, :t_terminals, [1, 2, 3, 4]) -# add_kwarg!(trans, kwargs, :tm_set, 1.0) -# add_kwarg!(trans, kwargs, :tm_min, 0.9) -# add_kwarg!(trans, kwargs, :tm_max, 1.1) -# add_kwarg!(trans, kwargs, :tm_step, 1/32) -# add_kwarg!(trans, kwargs, :tm_fix, true) -# return trans -# end - - -# Capacitor -_eng_model_checks[:capacitor] = function check_capacitor(data, cap) - obj_name = cap["obj_name"] - N = length(cap["connections"]) - config = cap["configuration"] - if config=="wye" - @assert(length(cap["qd_ref"])==N-1, "capacitor $obj_name: qd_ref should have $(N-1) elements.") - else - @assert(length(cap["qd_ref"])==N, "capacitor $obj_name: qd_ref should have $N elements.") - end - @assert(config in ["delta", "wye", "wye-grounded", "wye-floating"]) - if config=="delta" - @assert(N>=3, "Capacitor $obj_name: delta-connected capacitors should have at least 3 elements.") - end - - _check_connectivity(data, cap; context="capacitor $obj_name") -end - -function create_capacitor(; kwargs...) - cap = Dict{String,Any}() + transformer = Dict{String,Any}( + "status" => get(kwargs, :status, 1), + "configuration" => get(kwargs, :configuration, fill("wye", n_windings)), + "polarity" => get(kwargs, :polarity, fill(true, "n_windings")), + "rs" => get(kwargs, :rs, zeros(n_windings)), + "xsc" => get(kwargs, :xsc, zeros(n_windings^2-n_windings)), + "noloadloss" => get(kwargs, :noloadloss, 0.0), + "imag" => get(kwargs, :imag, 0.0), + "tm" => get(kwargs, :tm, fill(fill(1.0, 3), n_windings)), + "tm_min" => get(kwargs, :tm_min, fill(fill(0.9, 3), n_windings)), + "tm_max" => get(kwargs, :tm_max, fill(fill(1.1, 3), n_windings)), + "tm_step" => get(kwargs, :tm_step, fill(fill(1/32, 3), n_windings)), + "tm_fix" => get(kwargs, :tm_fix, fill(fill(true, 3), n_windings)), + ) - add_kwarg!(cap, kwargs, :status, 1) - add_kwarg!(cap, kwargs, :configuration, "wye") - add_kwarg!(cap, kwargs, :connections, collect(1:4)) - add_kwarg!(cap, kwargs, :qd_ref, fill(0.0, 3)) + _add_unused_kwargs!(transformer, kwargs) - _add_unused_kwargs!(cap, kwargs) - - return cap + return transformer end -# Shunt -_eng_model_checks[:shunt] = function check_shunt(data, shunt) - _check_connectivity(data, shunt; context="shunt $obj_name") +"creates a shunt capacitor object with some defaults" +function create_shunt_capacitor(; kwargs...) + shunt_capacitor = Dict{String,Any}( + "status" => get(kwargs, :status, 1), + "configuration" => get(kwargs, :configuration, "wye"), + "connections" => get(kwargs, :connections, collect(1:4)), + "qd_ref" => get(kwargs, :qd_ref, fill(0.0, 3)), + ) + + _add_unused_kwargs!(shunt_capacitor, kwargs) + return shunt_capacitor end +"creates a generic shunt with some defaults" function create_shunt(; kwargs...) - shunt = Dict{String,Any}() + kwargs = Dict{Symbol,Any}(kwargs) - N = length(kwargs[:connections]) + N = length(get(kwargs, :connections, collect(1:4))) - add_kwarg!(shunt, kwargs, :status, 1) - add_kwarg!(shunt, kwargs, :g_sh, fill(0.0, N, N)) - add_kwarg!(shunt, kwargs, :b_sh, fill(0.0, N, N)) + shunt = Dict{String,Any}( + "status" => get(kwargs, :status, 1), + "g_sh" => get(kwargs, :g_sh, fill(0.0, N, N)), + "b_sh" => get(kwargs, :b_sh, fill(0.0, N, N)), + ) _add_unused_kwargs!(shunt, kwargs) @@ -656,47 +225,31 @@ function create_shunt(; kwargs...) end -# voltage source -_eng_model_checks[:voltage_source] = function check_voltage_source(data, vs) - obj_name = vs["obj_name"] - _check_connectivity(data, vs; context="voltage source $obj_name") - N = length(vs["connections"]) - _check_has_size(vs, ["vm", "va", "pg_max", "pg_min", "qg_max", "qg_min"], N, context="voltage source $obj_name") - -end - - +"creates a voltage source with some defaults" function create_voltage_source(; kwargs...) - vs = Dict{String,Any}() + kwargs = Dict{Symbol,Any}(kwargs) - add_kwarg!(vs, kwargs, :status, 1) - add_kwarg!(vs, kwargs, :connections, collect(1:3)) + voltage_source = Dict{String,Any}( + "status" => get(kwargs, :status, 1), + "connections" => get(kwargs, :connections, collect(1:3)), + ) - _add_unused_kwargs!(vs, kwargs) + _add_unused_kwargs!(voltage_source, kwargs) - return vs + return voltage_source end -# create add_comp! methods -for comp in keys(_eng_model_dtypes) - eval(Meta.parse("add_$(comp)!(data_model, name; kwargs...) = add!(data_model, \"$comp\", name, create_$comp(; kwargs...))")) -end - - -"" -function delete_component!(data_eng, comp_type, comp::Dict) - delete!(data_eng[comp_type], comp["id"]) - if isempty(data_eng[comp_type]) - delete!(data_eng, comp_type) +"deletes a component from the engineering data model" +function delete_component!(data_eng::Dict{String,<:Any}, component_type::String, component_id::Any) + delete!(data_eng[component_type], component_id) + if isempty(data_eng[component_type]) + delete!(data_eng, component_type) end end -"" -function delete_component!(data_eng, comp_type, id::Any) - delete!(data_eng[comp_type], id) - if isempty(data_eng[comp_type]) - delete!(data_eng, comp_type) - end +"create add_{component_type}! methods that will create a component and add it to the engineering data model" +for comp in keys(_eng_model_dtypes) + @eval Meta.parse("add_$(comp)!(data_model, name; kwargs...) = add_object!(data_model, \"$comp\", name, create_$comp(; kwargs...))") end From 1c0a59200915c17c4224751a874be6f7b9f2be7b Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 17 Mar 2020 09:34:40 -0600 Subject: [PATCH 095/224] ADD: run_mc_model Alias to PowerModels run_model with multiconductor=true and transformer arcs refs added by default FIX: components.jl syntax error --- src/data_model/components.jl | 4 ++-- src/prob/common.jl | 4 ++++ src/prob/debug.jl | 4 ++-- src/prob/mld.jl | 6 +++--- src/prob/opf.jl | 2 +- src/prob/opf_bf.jl | 2 +- src/prob/opf_bf_lm.jl | 2 +- src/prob/opf_iv.jl | 2 +- src/prob/opf_oltc.jl | 2 +- src/prob/pf.jl | 2 +- src/prob/pf_bf.jl | 2 +- src/prob/pf_iv.jl | 2 +- src/prob/test.jl | 8 ++++---- 13 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 src/prob/common.jl diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 454c08f88..604c5f915 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -55,8 +55,8 @@ function create_eng_model(name; kwargs...)::Dict{String,Any} data_model = Dict{String,Any}( "name" => name, "data_model" => "engineering", - "settings"=>Dict{String,Any}( - "v_var_scalar" = get(kwargs, :v_var_scalar, 1e3) + "settings" => Dict{String,Any}( + "v_var_scalar" => get(kwargs, :v_var_scalar, 1e3) ) ) diff --git a/src/prob/common.jl b/src/prob/common.jl new file mode 100644 index 000000000..a67a34753 --- /dev/null +++ b/src/prob/common.jl @@ -0,0 +1,4 @@ +"alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" +function run_mc_model(data::Dict{String,<:Any}, model_type, solver, build_mc; ref_extensions=[], kwargs...) + _PMs.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) +end diff --git a/src/prob/debug.jl b/src/prob/debug.jl index 79b8b87b6..803b89666 100644 --- a/src/prob/debug.jl +++ b/src/prob/debug.jl @@ -2,7 +2,7 @@ # that do not converge using the standard formulations "OPF problem with slack power at every bus" function run_mc_opf_pbs(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_opf_pbs; multiconductor=true, ref_extensions=[ref_add_arcs_trans!], kwargs...) + return run_mc_model(data, model_type, solver, build_mc_opf_pbs; kwargs...) end @@ -14,7 +14,7 @@ end "PF problem with slack power at every bus" function run_mc_pf_pbs(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_pf_pbs; multiconductor=true, ref_extensions=[ref_add_arcs_trans!], kwargs...) + return run_mc_model(data, model_type, solver, build_mc_pf_pbs; kwargs...) end diff --git a/src/prob/mld.jl b/src/prob/mld.jl index 9609dbfda..9783291b2 100644 --- a/src/prob/mld.jl +++ b/src/prob/mld.jl @@ -1,6 +1,6 @@ "Run load shedding problem with storage" function run_mc_mld(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_mld; multiconductor=true, ref_extensions=[ref_add_arcs_trans!], kwargs...) + return run_mc_model(data, model_type, solver, build_mc_mld; kwargs...) end @@ -12,7 +12,7 @@ end "Run Branch Flow Model Load Shedding Problem" function run_mc_mld_bf(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_mld_bf; multiconductor=true, ref_extensions=[ref_add_arcs_trans!], kwargs...) + return run_mc_model(data, model_type, solver, build_mc_mld_bf; kwargs...) end @@ -24,7 +24,7 @@ end "Run unit commitment load shedding problem (!relaxed)" function run_mc_mld_uc(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_mld_uc; multiconductor=true, ref_extensions=[ref_add_arcs_trans!], kwargs...) + return run_mc_model(data, model_type, solver, build_mc_mld_uc; kwargs...) end diff --git a/src/prob/opf.jl b/src/prob/opf.jl index 4749b0f50..fc027ab55 100644 --- a/src/prob/opf.jl +++ b/src/prob/opf.jl @@ -6,7 +6,7 @@ end "" function run_mc_opf(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_opf; multiconductor=true, ref_extensions=[ref_add_arcs_trans!], kwargs...) + return run_mc_model(data, model_type, solver, build_mc_opf; kwargs...) end diff --git a/src/prob/opf_bf.jl b/src/prob/opf_bf.jl index 5b2ab7728..a2f547f62 100644 --- a/src/prob/opf_bf.jl +++ b/src/prob/opf_bf.jl @@ -1,6 +1,6 @@ "" function run_mc_opf_bf(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_opf_bf; ref_extensions=[ref_add_arcs_trans!], multiconductor=true, kwargs...) + return run_mc_model(data, model_type, solver, build_mc_opf_bf; kwargs...) end diff --git a/src/prob/opf_bf_lm.jl b/src/prob/opf_bf_lm.jl index 716e5f8cd..ea5588084 100644 --- a/src/prob/opf_bf_lm.jl +++ b/src/prob/opf_bf_lm.jl @@ -4,7 +4,7 @@ "" function run_mc_opf_bf_lm(data::Dict{String,Any}, model_constructor, solver; kwargs...) - return _PMs.run_model(data, model_constructor, solver, build_mc_opf_bf_lm; multiconductor=true, ref_extensions=[ref_add_arcs_trans!], kwargs...) + return run_mc_model(data, model_constructor, solver, build_mc_opf_bf_lm; kwargs...) end diff --git a/src/prob/opf_iv.jl b/src/prob/opf_iv.jl index 67fd3a4e8..0f3b72fe5 100644 --- a/src/prob/opf_iv.jl +++ b/src/prob/opf_iv.jl @@ -1,6 +1,6 @@ "" function run_mc_opf_iv(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_opf_iv; ref_extensions=[ref_add_arcs_trans!], multiconductor=true, kwargs...) + return run_mc_model(data, model_type, solver, build_mc_opf_iv; kwargs...) end diff --git a/src/prob/opf_oltc.jl b/src/prob/opf_oltc.jl index 388ef88db..83c165f6c 100644 --- a/src/prob/opf_oltc.jl +++ b/src/prob/opf_oltc.jl @@ -6,7 +6,7 @@ end "" function run_mc_opf_oltc(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_opf_oltc; multiconductor=true, ref_extensions=[ref_add_arcs_trans!], kwargs...) + return run_mc_model(data, model_type, solver, build_mc_opf_oltc; kwargs...) end diff --git a/src/prob/pf.jl b/src/prob/pf.jl index 07176655b..0e99fda5e 100644 --- a/src/prob/pf.jl +++ b/src/prob/pf.jl @@ -12,7 +12,7 @@ end "" function run_mc_pf(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_pf; multiconductor=true, ref_extensions=[ref_add_arcs_trans!], kwargs...) + return run_mc_model(data, model_type, solver, build_mc_pf; kwargs...) end diff --git a/src/prob/pf_bf.jl b/src/prob/pf_bf.jl index c2a663b9a..e2a937d53 100644 --- a/src/prob/pf_bf.jl +++ b/src/prob/pf_bf.jl @@ -1,6 +1,6 @@ "" function run_mc_pf_bf(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_pf_bf; ref_extensions=[ref_add_arcs_trans!], multiconductor=true, kwargs...) + return run_mc_model(data, model_type, solver, build_mc_pf_bf; kwargs...) end diff --git a/src/prob/pf_iv.jl b/src/prob/pf_iv.jl index 150738901..7f315c20a 100644 --- a/src/prob/pf_iv.jl +++ b/src/prob/pf_iv.jl @@ -1,6 +1,6 @@ "" function run_mc_pf_iv(data::Dict{String,Any}, model_type, solver; kwargs...) - return _PMs.run_model(data, model_type, solver, build_mc_pf_iv; ref_extensions=[ref_add_arcs_trans!], multiconductor=true, kwargs...) + return run_mc_model(data, model_type, solver, build_mc_pf_iv; kwargs...) end diff --git a/src/prob/test.jl b/src/prob/test.jl index 749051fdf..c5b461f15 100644 --- a/src/prob/test.jl +++ b/src/prob/test.jl @@ -6,7 +6,7 @@ ###### # "multi-network opf with storage" # function _run_mn_mc_opf(data::Dict{String,Any}, model_type, solver; kwargs...) -# return _PMs.run_model(data, model_type, solver, _build_mn_mc_strg_opf; ref_extensions=[ref_add_arcs_trans!], multiconductor=true, multinetwork=true, kwargs...) +# return run_mc_model(data, model_type, solver, _build_mn_mc_strg_opf; multinetwork=true, kwargs...) # end # # @@ -81,7 +81,7 @@ "" function _run_mc_ucopf(file, model_type::Type, solver; kwargs...) - return _PMs.run_model(file, model_type, solver, _build_mc_ucopf; ref_extensions=[ref_add_arcs_trans!], multiconductor=true, kwargs...) + return run_mc_model(file, model_type, solver, _build_mc_ucopf; kwargs...) end "" @@ -165,7 +165,7 @@ end "" function _run_mn_mc_opf(file, model_type::Type, optimizer; kwargs...) - return _PMs.run_model(file, model_type, optimizer, _build_mn_mc_opf; ref_extensions=[ref_add_arcs_trans!], multinetwork=true, multiconductor=true, kwargs...) + return run_mc_model(file, model_type, optimizer, _build_mn_mc_opf; multinetwork=true, kwargs...) end "" @@ -212,7 +212,7 @@ end "" function _run_mn_mc_opf_strg(file, model_type::Type, optimizer; kwargs...) - return _PMs.run_model(file, model_type, optimizer, _build_mn_mc_opf_strg; ref_extensions=[ref_add_arcs_trans!], multinetwork=true, multiconductor=true, kwargs...) + return run_mc_model(file, model_type, optimizer, _build_mn_mc_opf_strg; multinetwork=true, kwargs...) end "warning: this model is not realistic or physically reasonable, it is only for test coverage" From 241701698219512e63491b820b29fbcf98051548 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 17 Mar 2020 10:21:56 -0600 Subject: [PATCH 096/224] REVERT: pad vmax with INF Padding with INF breaks phase drop case5 --- src/PowerModelsDistribution.jl | 2 ++ src/core/data.jl | 16 ---------------- src/data_model/checks.jl | 2 +- src/data_model/components.jl | 14 +++++++------- src/data_model/data_model_test.jl | 2 +- src/data_model/eng2math.jl | 3 +-- src/io/opendss.jl | 2 +- 7 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 49c001acc..205e9d34e 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -47,11 +47,13 @@ module PowerModelsDistribution include("io/common.jl") include("data_model/utils.jl") + include("data_model/checks.jl") include("data_model/components.jl") include("data_model/eng2math.jl") include("data_model/math2eng.jl") include("data_model/units.jl") + include("prob/common.jl") include("prob/mld.jl") include("prob/opf.jl") include("prob/opf_iv.jl") diff --git a/src/core/data.jl b/src/core/data.jl index c1f658bf6..c7ab75c52 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -169,22 +169,6 @@ function _calc_bus_vm_ll_bounds(bus::Dict; vdmin_eps=0.1) end -""" -Calculates lower and upper bounds for the loads themselves (not the power -withdrawn at the bus). -""" -function _calc_load_pq_bounds(load::Dict, bus::Dict) - a, alpha, b, beta = _load_expmodel_params(load, bus) - vmin, vmax = _calc_load_vbounds(load, bus) - # get bounds - pmin = min.(a.*vmin.^alpha, a.*vmax.^alpha) - pmax = max.(a.*vmin.^alpha, a.*vmax.^alpha) - qmin = min.(b.*vmin.^beta, b.*vmax.^beta) - qmax = max.(b.*vmin.^beta, b.*vmax.^beta) - return (pmin, pmax, qmin, qmax) -end - - """ Calculates lower and upper bounds for the loads themselves (not the power withdrawn at the bus). diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index 7a65c5244..b310be5f7 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -441,7 +441,7 @@ function _check_shunt_capacitor(data_eng::Dict{String,<:Any}, name::Any) @assert length(shunt_capacitor["qd_ref"])==N "capacitor $name: qd_ref should have $N elements." end - @assert config in ["delta", "wye", "wye-grounded" "wye-floating"] + @assert config in ["delta", "wye", "wye-grounded", "wye-floating"] if config=="delta" @assert N>=3 "Capacitor $name: delta-connected capacitors should have at least 3 elements." diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 604c5f915..3628e0aad 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -133,11 +133,11 @@ function create_load(; kwargs...) load["connections"] = get(kwargs, :connections, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]), if load["model"]=="constant_power" - load["pd"] get(kwargs, :pd, fill(0.0, 3)) - load["qd"] get(kwargs, :qd, fill(0.0, 3)) + load["pd"] = get(kwargs, :pd, fill(0.0, 3)) + load["qd"] = get(kwargs, :qd, fill(0.0, 3)) else - load["pd_ref"] => get(kwargs, :pd_ref, fill(0.0, 3)) - load["qd_ref"] => get(kwargs, :qd_ref, fill(0.0, 3)) + load["pd_ref"] = get(kwargs, :pd_ref, fill(0.0, 3)) + load["qd_ref"] = get(kwargs, :qd_ref, fill(0.0, 3)) end _add_unused_kwargs!(load, kwargs) @@ -174,7 +174,7 @@ function create_transformer(; kwargs...) transformer = Dict{String,Any}( "status" => get(kwargs, :status, 1), "configuration" => get(kwargs, :configuration, fill("wye", n_windings)), - "polarity" => get(kwargs, :polarity, fill(true, "n_windings")), + "polarity" => get(kwargs, :polarity, fill(true, n_windings)), "rs" => get(kwargs, :rs, zeros(n_windings)), "xsc" => get(kwargs, :xsc, zeros(n_windings^2-n_windings)), "noloadloss" => get(kwargs, :noloadloss, 0.0), @@ -249,7 +249,7 @@ function delete_component!(data_eng::Dict{String,<:Any}, component_type::String, end -"create add_{component_type}! methods that will create a component and add it to the engineering data model" +# create add_{component_type}! methods that will create a component and add it to the engineering data model for comp in keys(_eng_model_dtypes) - @eval Meta.parse("add_$(comp)!(data_model, name; kwargs...) = add_object!(data_model, \"$comp\", name, create_$comp(; kwargs...))") + eval(Meta.parse("add_$(comp)!(data_model, name; kwargs...) = add_object!(data_model, \"$comp\", name, create_$comp(; kwargs...))")) end diff --git a/src/data_model/data_model_test.jl b/src/data_model/data_model_test.jl index 21353716d..d7a89153e 100644 --- a/src/data_model/data_model_test.jl +++ b/src/data_model/data_model_test.jl @@ -90,7 +90,7 @@ function make_3wire_data_model() add_bus!(data_model, "tr_sec", terminals=collect(1:4)) #add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) - # add!(data_model, "transformer_nw", create_transformer_nw("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], + # add!(data_model, "transformer_nw", create_transformer("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], # [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], # configuration=["delta", "wye", "delta"], # xsc=[0.0, 0.0, 0.0], diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index b0e99b531..08b7fb0d3 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -162,8 +162,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, @assert(all(t in kr_phases for t in terminals_kr)) _apply_filter!(math_obj, ["vm", "va", "vmin", "vmax"], filter) - _pad_properties!(math_obj, ["vmax"], terminals_kr, kr_phases, pad_value=Inf) - _pad_properties!(math_obj, ["vm", "va", "vmin"], terminals_kr, kr_phases) + _pad_properties!(math_obj, ["vm", "va", "vmin", "vmax"], terminals_kr, kr_phases) end data_math["bus"]["$(math_obj["index"])"] = math_obj diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 6355b1c14..9cc867bd1 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -615,7 +615,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 end - eng_obj = create_transformer_nw(; Dict(Symbol.(keys(eng_obj)).=>values(eng_obj))...) + eng_obj = create_transformer(; Dict(Symbol.(keys(eng_obj)).=>values(eng_obj))...) if !haskey(data_eng, "transformer") data_eng["transformer"] = Dict{String,Any}() From c2947b09c93397a9b70e4749c7dd322b4f250d6c Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 17 Mar 2020 13:56:36 -0600 Subject: [PATCH 097/224] REF: rename pvsystem to solar --- src/data_model/checks.jl | 6 ++-- src/data_model/eng2math.jl | 68 ++++++++++++++++++++------------------ src/io/opendss.jl | 8 ++--- src/prob/common.jl | 12 ++++++- 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index b310be5f7..56e1adb3e 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -14,7 +14,7 @@ const _eng_model_checks = Dict{Symbol,Symbol}( :shunt => :_check_shunt, :generator => :_check_generator, :voltage_source => :_check_voltage_source, - # :pvsystem => :_check_pvsystem, + # :solar => :_check_solar, # :storage => :_check_storage, # :grounding => :_check_grounding, ) @@ -137,7 +137,7 @@ const _eng_model_dtypes = Dict{Symbol,Dict{Symbol,Type}}( :qg_max =>Array{<:Real}, :qg_min =>Array{<:Real}, ), - :pvsystem => Dict{Symbol,Type}(), + :solar => Dict{Symbol,Type}(), :storage => Dict{Symbol,Type}(), :switch => Dict{Symbol,Type}(), :grounding => Dict{Symbol,Type}( @@ -194,7 +194,7 @@ const _eng_model_req_fields= Dict{Symbol,Vector{Symbol}}( :voltage_source => Vector{Symbol}([ :status, :bus, :connections, :vm, :va ]), - :pvsystem => Vector{Symbol}([]), + :solar => Vector{Symbol}([]), :storage => Vector{Symbol}([]), :grounding => Vector{Symbol}([]), # Future Components diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 08b7fb0d3..22531487b 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -4,11 +4,12 @@ import LinearAlgebra: diagm const _1to1_maps = Dict{String,Vector{String}}( "bus" => ["vm", "va", "vmin", "vmax"], "load" => ["model", "configuration", "status", "source_id"], - "capacitor" => ["status", "source_id"], + "shunt_capacitor" => ["status", "source_id"], + "series_capacitor" => [], "shunt" => ["status", "source_id"], "shunt_reactor" => ["status", "source_id"], "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id", "configuration"], - "pvsystem" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], + "solar" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], "storage" => ["status", "source_id"], "line" => ["source_id"], "line_reactor" => ["source_id"], @@ -22,11 +23,12 @@ const _extra_eng_data = Dict{String,Vector{String}}( "root" => ["sourcebus", "files", "dss_options", "settings"], "bus" => ["grounded", "neutral", "awaiting_ground", "xg", "phases", "rg", "terminals"], "load" => [], - "capacitor" => [], + "shunt_capacitor" => [], + "series_capacitor" => [], "shunt" => [], "shunt_reactor" => [], "generator" => ["control_model"], - "pvsystem" => [], + "solar" => [], "storage" => [], "line" => ["f_connections", "t_connections", "linecode"], "switch" => [], @@ -35,9 +37,9 @@ const _extra_eng_data = Dict{String,Vector{String}}( "voltage_source" => [], ) -const _node_elements = ["load", "capacitor", "shunt_reactor", "generator", "pvsystem", "storage", "vsource"] +const _node_elements = ["load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource"] -const _edge_elements = ["line", "switch", "transformer"] +const _edge_elements = ["line", "switch", "transformer", "line_reactor", "series_capacitor"] "" @@ -69,14 +71,14 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_load!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_shunt_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_pvsystem!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_vsource!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) @@ -174,7 +176,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, data_math["bus_lookup"][name] = math_obj["index"] data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "bus.$name", + :from => name, :to => "bus.$(math_obj["index"])", :unmap_function => :_map_math2eng_bus!, :kron_reduced => kron_reduced, @@ -196,6 +198,8 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["status"] = data_math["status"] + math_obj["pd"] = eng_obj["pd"] math_obj["qd"] = eng_obj["qd"] @@ -217,7 +221,7 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any data_math["load"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "load.$name", + :from => name, :to => "load.$(math_obj["index"])", :unmap_function => :_map_math2eng_load!, :kron_reduced => kron_reduced, @@ -228,7 +232,7 @@ end "" -function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) +function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "capacitor", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("capacitor", eng_obj, length(data_math["shunt"])+1) @@ -285,9 +289,9 @@ function _map_eng2math_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{ data_math["shunt"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "capacitor.$name", + :from => name, :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_capacitor!, + :unmap_function => :_map_math2eng_shunt_capacitor!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["capacitor"]) ) @@ -321,7 +325,7 @@ function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An data_math["shunt"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "shunt.$name", + :from => name, :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_capacitor!, :kron_reduced => kron_reduced, @@ -359,7 +363,7 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D data_math["shunt"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "shunt_reactor.$name", + :from => name, :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_shunt_reactor!, :kron_reduced => kron_reduced, @@ -413,7 +417,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ data_math["gen"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "generator.$name", + :from => name, :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_generator!, :kron_reduced => kron_reduced, @@ -424,9 +428,9 @@ end "" -function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "pvsystem", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("pvsystem", eng_obj, length(data_math["gen"])+1) +function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("solar", eng_obj, length(data_math["gen"])+1) phases = eng_obj["phases"] connections = eng_obj["connections"] @@ -462,11 +466,11 @@ function _map_eng2math_pvsystem!(data_math::Dict{String,<:Any}, data_eng::Dict{< data_math["gen"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "pvsystem.$name", + :from => "solar.$name", :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_pvsystem!, + :unmap_function => :_map_math2eng_solar!, :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["pvsystem"]) + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["solar"]) ) end end @@ -508,7 +512,7 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: data_math["storage"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "storage.$name", + :from => name, :to => "storage.$(math_obj["index"])", :unmap_function => :_map_math2eng_storage!, :kron_reduced => kron_reduced, @@ -580,7 +584,7 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any data_math["branch"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "line.$name", + :from => name, :to => "branch.$(math_obj["index"])", :unmap_function => :_map_math2eng_line!, :kron_reduced => kron_reduced, @@ -643,7 +647,7 @@ function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Di data_math["branch"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "line_reactor.$name", + :from => name, :to => "branch.$(math_obj["index"])", :unmap_function => :_map_math2eng_line_reactor!, :kron_reduced => kron_reduced, @@ -744,7 +748,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A # data_math["switch"]["$(switch_obj["index"])"] = switch_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from_id => "switch.$name", + :from_id => name, # :to_id => ["switch.$(switch_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], # TODO enable real switches :to_id => ["branch.$(branch_obj["index"])"], :unmap_function => :_map_math2eng_switch!, @@ -761,7 +765,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic # Build map first, so we can update it as we decompose the transformer map_idx = length(data_math["map"])+1 data_math["map"][map_idx] = Dict{Symbol,Any}( - :from_id => "transformer.$name", + :from_id => name, :to_id => Vector{String}([]), :unmap_function => :_map_math2eng_transformer!, :kron_reduced => kron_reduced, @@ -844,7 +848,7 @@ end "" -function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) +function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) # TODO create option for lossy vs lossless sourcebus connection for (name, eng_obj) in data_eng["voltage_source"] nconductors = data_math["conductors"] @@ -856,7 +860,7 @@ function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<: "name" => "_virtual_bus.voltage_source.$name", "bus_type" => 3, "vm" => eng_obj["vm"], - "va" => deg2rad.(eng_obj["va"]), + "va" => eng_obj["va"], "vmin" => eng_obj["vm"], "vmax" => eng_obj["vm"], "basekv" => data_math["basekv"] @@ -906,9 +910,9 @@ function _map_eng2math_vsource!(data_math::Dict{String,<:Any}, data_eng::Dict{<: data_math["branch"]["$(branch_obj["index"])"] = branch_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from_id => "voltage_source.$name", + :from_id => name, :to_id => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], - :unmap_function => :_map_math2eng_sourcebus!, + :unmap_function => :_map_math2eng_voltage_source!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) ) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 9cc867bd1..b259643ad 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -686,8 +686,6 @@ end "Adds pvsystems to `data_eng` from `data_dss`" function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "pvsystem", Dict{String,Any}()) - Memento.warn(_LOGGER, "Converting PVSystem \"$(dss_obj["name"])\" into generator with limits determined by OpenDSS property 'kVA'") - _apply_like!(dss_obj, data_dss, "pvsystem") defaults = _apply_ordered_properties(_create_pvsystem(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) @@ -724,11 +722,11 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end - if !haskey(data_eng, "pvsystem") - data_eng["pvsystem"] = Dict{String,Any}() + if !haskey(data_eng, "solar") + data_eng["solar"] = Dict{String,Any}() end - data_eng["pvsystem"][name] = eng_obj + data_eng["solar"][name] = eng_obj end end diff --git a/src/prob/common.jl b/src/prob/common.jl index a67a34753..7c35214dd 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -1,4 +1,14 @@ "alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" function run_mc_model(data::Dict{String,<:Any}, model_type, solver, build_mc; ref_extensions=[], kwargs...) - _PMs.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) + if get(data, "data_model", "mathematical") == "engineering" + data = transform_data_model(data) + end + + result = _PMs.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) + + if get(data, "data_model", "mathematical") == "engineering" + result["solution"] = solution_math2eng(result["solution"], data; make_si=get(data, "per_unit", false), make_deg=get(data, "per_unit", false)) + end + + return result end From c1ed2d3251813b8ddb61f8912ac9ab7f8506c2a5 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 17 Mar 2020 15:19:01 -0600 Subject: [PATCH 098/224] WIP: ADD math2eng conversions --- src/data_model/math2eng.jl | 154 ++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 4 deletions(-) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index dc9a022ee..81c789501 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -3,13 +3,15 @@ function _map_math2eng!(data_math) @assert get(data_math, "data_model", "mathematical") == "mathematical" "Cannot map data to engineering model: provided data is not a mathematical model" @assert haskey(data_math, "map") "Cannot map data to engineering model: no mapping from mathematical to engineering data model is provided" - data_eng = Dict{<:Any,<:Any}() + data_eng = Dict{String,Any}() - map_keys = sort(keys(data_math["map"]); rev=true) - for map in map_keys - # TODO + unmap_keys = sort(keys(data_math["map"]), rev=true) + reverse_bus_lookup = Dict{Int,Any}((bus_math, bus_eng) for (bus_eng, bus_math) in data_math["bus_lookup"]) + for key in unmap_keys + @eval $(map[:unmap_function])(data_eng, data_math, data_math["map"][key], reverse_bus_lookup) end + return data_eng end @@ -64,3 +66,147 @@ function solution_math2eng(solution_math::Dict, data_math::Dict; make_si::Bool=t return solution_eng end + + +function _map_math2eng_voltage_source!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + if !haskey(data_eng, "voltage_source") + data_eng["voltage_source"] = Dict{Any,Any}() + end + + eng_obj = Dict{String,Any}() + merge!(eng_obj, map[:extras]) + + for to_id in map[:to] + math_obj = _get_math_obj(data_math, to_id) + if startswith(to_id, "bus") + eng_obj["vm"] = math_obj["vm"] + eng_obj["va"] = math_obj["va"] + + elseif startswith(to_id, "gen") + eng_obj["source_id"] = strip(math_obj["source_id"], "_virtual_gen.") + + elseif startswith(to_id, "branch") + eng_obj["bus"] = bus_lookup[math_obj["t_bus"]] + eng_obj["rs"] = math_obj["br_r"] + eng_obj["xs"] = math_obj["br_x"] + else + Memento.warn(_LOGGER, "transforming from $to_id to $(map[:from]) is not supported") + end + end + + data_eng[map[:from]] = eng_obj +end + + +function _map_math2eng_bus!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "bus", map) + math_obj = _get_math_obj(data_math, map[:to]) + + eng_obj["status"] = math_obj["bus_type"] == 4 ? 0 : 1 + + for key in ["vm", "va", "vmin", "vmax"] + if haskey(math_obj, key) + eng_obj[key] = math_obj[key] + end + end + + data_eng[map[:from]] = eng_obj +end + + +function _map_math2eng_load!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "load", map) + math_obj = _get_math_obj(data_math, map[:to]) + + eng_obj["vnom"] = math_obj["vnom_kv"] + eng_obj["bus"] = bus_lookup[math_obj["load_bus"]] + + eng_obj["pd"] = math_obj["pd"] + eng_obj["qd"] = math_obj["qd"] + + eng_obj["status"] = math_obj["status"] + + eng_obj["configuration"] = math_obj["configuration"] + + data_eng[map[:from]] = eng_obj +end + + +function _map_math2eng_shunt_capacitor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "shunt_capacitor", map) + math_obj = _get_math_obj(data_math, map[:to]) + + eng_obj["bus"] = bus_lookup["shunt_bus"] + eng_obj["status"] = math_obj["status"] + + eng_obj["configuration"] = eng_obj["configuration"] + + B = math_obj["bs"] + + if math_obj["configuration"] == "wye" + b_cap_pu = diag(B) + else + # TODO + end + + # TODO how to pull kv, kvar back out, this is a destructive transformation + + data_eng[map[:from]] = eng_obj +end + + +function _map_math2eng_shunt!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +end + + +function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "generator", map) + math_obj = _get_math_obj(data_math, map[:to]) + + eng_obj["bus"] = bus_lookup[math_obj["gen_bus"]] + eng_obj["configuration"] = math_obj["configuration"] + eng_obj["status"] = math_obj["gen_status"] + + eng_obj["kw"] = math_obj["pg"] + eng_obj["kvar"] = math_obj["qg"] + eng_obj["kv"] = math_obj["vg"] + + eng_obj["kvar_min"] = math_obj["qmin"] + eng_obj["kvar_max"] = math_obj["qmax"] + + eng_obj["kw_min"] = math_obj["pmin"] + eng_obj["kw_max"] = math_obj["pmax"] + + gen_bus = data_math["bus"]["$(math_obj["gen_bus"])"] + if gen_bus["bus_type"] == 2 + eng_obj["control_mode"] = 3 + else + eng_obj["control_mode"] = 1 + end + + data_eng[map[:from]] = eng_obj +end + + +function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +end + + +function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +end + + +function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +end + + +function _map_math2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +end + + +function _map_math2eng_switch!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +end + + +function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +end From b1242fe96bfd8035d09334d10a4d6ff247f1931e Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 17 Mar 2020 17:07:54 -0600 Subject: [PATCH 099/224] REF: opendss parser combines capacitor and reactor parsing under single for-loop (should not be separate at opendss level, but only at the engineering data model level) makes properties per phase at the engineering level for generators, solar, storage, capacitors, etc. --- src/data_model/eng2math.jl | 107 ++++----- src/data_model/math2eng.jl | 27 ++- src/data_model/utils.jl | 28 +++ src/io/opendss.jl | 452 +++++++++++++------------------------ src/io/utils.jl | 112 +++++---- test/opendss.jl | 7 - 6 files changed, 306 insertions(+), 427 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 22531487b..92268b1b6 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -8,8 +8,8 @@ const _1to1_maps = Dict{String,Vector{String}}( "series_capacitor" => [], "shunt" => ["status", "source_id"], "shunt_reactor" => ["status", "source_id"], - "generator" => ["model", "startup", "shutdown", "ncost", "cost", "source_id", "configuration"], - "solar" => ["model", "startup", "shutdown", "ncost", "cost", "source_id"], + "generator" => ["source_id", "configuration"], + "solar" => ["source_id", "configuration"], "storage" => ["status", "source_id"], "line" => ["source_id"], "line_reactor" => ["source_id"], @@ -72,8 +72,8 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_load!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced) @@ -81,6 +81,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) + # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) @@ -188,7 +189,6 @@ end "" function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - # TODO add delta loads for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("load", eng_obj, length(data_math["load"])+1) math_obj["name"] = name @@ -198,7 +198,7 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["status"] = data_math["status"] + math_obj["status"] = eng_obj["status"] math_obj["pd"] = eng_obj["pd"] math_obj["qd"] = eng_obj["qd"] @@ -233,52 +233,33 @@ end "" function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "capacitor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("capacitor", eng_obj, length(data_math["shunt"])+1) + for (name, eng_obj) in get(data_eng, "shunt_capacitor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt_capacitor", eng_obj, length(data_math["shunt"])+1) # TODO change to new capacitor shunt calc logic math_obj["name"] = name math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - nphases = eng_obj["phases"] + f_terminals = eng_obj["f_connections"] + t_terminals = eng_obj["t_connections"] vnom_ln = eng_obj["kv"] - # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) - if eng_obj["phases"] > 1 - vnom_ln = vnom_ln/sqrt(3) - end - # 'kvar' is specified for all phases at once; we want the per-phase one, in MVar - qnom = (eng_obj["kvar"]/1E3)/nphases - # indexing qnom[1] is a dirty fix to support both kvar=[x] and kvar=x - # TODO fix this in a clear way, in dss_structs.jl - b_cap = qnom[1]/vnom_ln^2 - # get the base addmittance, with a LN voltage base - Sbase = 1 # not yet pmd_data["baseMVA"] because this is done in _PMs.make_per_unit - Ybase_ln = Sbase/(data_math["basekv"]/sqrt(3))^2 - # now convent b_cap to per unit - b_cap_pu = b_cap/Ybase_ln - - b = fill(b_cap_pu, nphases) - N = length(b) - - if eng_obj["configuration"] == "wye" - B = diagm(0=>b) - else - # create delta transformation matrix Md - Md = diagm(0=>ones(N), 1=>-ones(N-1)) - Md[N,1] = -1 - B = Md'*diagm(0=>b)*Md - end + qnom = eng_obj["kvar"] ./ 1e3 + b = qnom ./ vnom_ln.^2 - math_obj["gs"] = fill(0.0, nphases, nphases) - math_obj["bs"] = B + # convert to a shunt matrix + terminals, B = _calc_shunt(f_terminals, t_terminals, b) - # neutral = get(data_eng["bus"][eng_obj["bus"]], "neutral", 4) + # if one terminal is ground (0), reduce shunt addmittance matrix + terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) + + math_obj["bs"] = B + math_obj["gs"] = zeros(size(B)) if kron_reduced filter = _kron_reduce_branch!(math_obj, - [], ["gs", "bs"], - eng_obj["connections"], kr_neutral + Vector{String}([]), ["gs", "bs"], + eng_obj["f_connections"], kr_neutral ) connections = eng_obj["f_connections"][filter] _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) @@ -293,7 +274,7 @@ function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng: :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_shunt_capacitor!, :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["capacitor"]) + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["shunt_capacitor"]) ) end end @@ -386,15 +367,17 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["name"] = name math_obj["gen_status"] = eng_obj["status"] - math_obj["pg"] = fill(eng_obj["kw"] / phases, phases) - math_obj["qg"] = fill(eng_obj["kvar"] / phases, phases) - math_obj["vg"] = fill(eng_obj["kv"] / data_math["basekv"]) + math_obj["pg"] = eng_obj["kw"] + math_obj["qg"] = eng_obj["kvar"] + math_obj["vg"] = eng_obj["kv"] ./ data_math["basekv"] - math_obj["qmin"] = fill(eng_obj["kvar_min"] / phases, phases) - math_obj["qmax"] = fill(eng_obj["kvar_max"] / phases, phases) + math_obj["qmin"] = eng_obj["kvar_min"] + math_obj["qmax"] = eng_obj["kvar_max"] - math_obj["pmax"] = fill(eng_obj["kw"] / phases, phases) - math_obj["pmin"] = fill(0.0, phases) + math_obj["pmax"] = eng_obj["kw"] + math_obj["pmin"] = zeros(phases) + + _add_gen_cost_model!(math_obj, eng_obj) math_obj["configuration"] = eng_obj["configuration"] @@ -440,17 +423,17 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["gen_status"] = eng_obj["status"] - math_obj["pg"] = fill(eng_obj["kva"] / phases, phases) - math_obj["qg"] = fill(eng_obj["kva"] / phases, phases) - math_obj["vg"] = fill(eng_obj["kv"] / data_math["basekv"], phases) + math_obj["pg"] = eng_obj["kva"] + math_obj["qg"] = eng_obj["kva"] + math_obj["vg"] = eng_obj["kv"] math_obj["pmin"] = fill(0.0, phases) - math_obj["pmax"] = fill(eng_obj["kva"] / phases, phases) + math_obj["pmax"] = eng_obj["kva"] - math_obj["qmin"] = fill(-eng_obj["kva"] / phases, phases) - math_obj["qmax"] = fill( eng_obj["kva"] / phases, phases) + math_obj["qmin"] = -eng_obj["kva"] + math_obj["qmax"] = eng_obj["kva"] - math_obj["configuration"] = eng_obj["configuration"] + _add_gen_cost_model!(math_obj, eng_obj) if kron_reduced if math_obj["configuration"]=="wye" @@ -748,9 +731,9 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A # data_math["switch"]["$(switch_obj["index"])"] = switch_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from_id => name, + :from => name, # :to_id => ["switch.$(switch_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], # TODO enable real switches - :to_id => ["branch.$(branch_obj["index"])"], + :to => ["branch.$(branch_obj["index"])"], :unmap_function => :_map_math2eng_switch!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["switch"]) @@ -765,14 +748,14 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic # Build map first, so we can update it as we decompose the transformer map_idx = length(data_math["map"])+1 data_math["map"][map_idx] = Dict{Symbol,Any}( - :from_id => name, - :to_id => Vector{String}([]), + :from => name, + :to => Vector{String}([]), :unmap_function => :_map_math2eng_transformer!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["transformer"]) ) - to_map = data_math["map"][map_idx][:to_id] + to_map = data_math["map"][map_idx][:to] vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] @@ -860,7 +843,7 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "name" => "_virtual_bus.voltage_source.$name", "bus_type" => 3, "vm" => eng_obj["vm"], - "va" => eng_obj["va"], + "va" => deg2rad.(eng_obj["va"]), "vmin" => eng_obj["vm"], "vmax" => eng_obj["vm"], "basekv" => data_math["basekv"] @@ -910,8 +893,8 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: data_math["branch"]["$(branch_obj["index"])"] = branch_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from_id => name, - :to_id => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], + :from => name, + :to => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], :unmap_function => :_map_math2eng_voltage_source!, :kron_reduced => kron_reduced, :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index 81c789501..c452d35f7 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -34,8 +34,8 @@ function solution_math2eng(solution_math::Dict, data_math::Dict; make_si::Bool=t elseif umap_type==:_map_math2eng_root! # nothing to do for the solution elseif umap_type==:_map_math2eng_transformer! - _, name = split(map[:from_id], ".") - trans_2wa_ids = [index for (comp_type, index) in split.(map[:to_id], ".", limit=2) if comp_type=="transformer"] + name = map[:from] + trans_2wa_ids = [index for (comp_type, index) in split.(map[:to], ".", limit=2) if comp_type=="transformer"] if !haskey(solution_eng, "transformer") solution_eng["transformer"] = Dict{String, Any}() @@ -50,16 +50,19 @@ function solution_math2eng(solution_math::Dict, data_math::Dict; make_si::Bool=t solution_eng["transformer"][name] = trans else - comp_type_eng, name = split(map[:from], ".") - comp_type_math, index = split(map[:to], ".") - if !haskey(solution_eng, comp_type_eng) - solution_eng[comp_type_eng] = Dict{String, Any}() - end - if haskey(solution_math, comp_type_math) - solution_eng[comp_type_eng][name] = solution_math[comp_type_math][index] - else - #TODO add empty dicts if math object has no solution object? - solution_eng[comp_type_eng][name] = Dict{String, Any}() + comp_type_eng = match(r"_map_math2eng_(\w+)!", string(umap_type)).captures[1] + name = map[:from] + if !isa(map[:to], Vector) + comp_type_math, index = split(map[:to], ".") + if !haskey(solution_eng, comp_type_eng) + solution_eng[comp_type_eng] = Dict{String, Any}() + end + if haskey(solution_math, comp_type_math) + solution_eng[comp_type_eng][name] = solution_math[comp_type_math][index] + else + #TODO add empty dicts if math object has no solution object? + solution_eng[comp_type_eng][name] = Dict{String, Any}() + end end end end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index d16eb12bc..6a08eb49a 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -439,3 +439,31 @@ function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{T}, g return cnds, Y end end + + +"initialization actions for unmapping" +function _init_unmap_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, map::Dict{Symbol,Any})::Dict{String,Any} + if !haskey(data_eng, eng_obj_type) + data_eng[eng_obj_type] = Dict{Any,Any}() + end + + eng_obj = Dict{String,Any}() + return merge(eng_obj, map[:extras]) +end + + +"returns component from the mathematical data model" +function _get_math_obj(data_math::Dict{String,<:Any}, to_id::String)::Dict{String,Any} + math_type, math_id = split(map[:to], '.') + return data_math[math_type][math_id] +end + + +"convert cost model names" +function _add_gen_cost_model!(math_obj::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}) + math_obj["model"] = get(eng_obj, "cost_model", 2) + math_obj["startup"] = get(eng_obj, "startup_cost", 0.0) + math_obj["shutdown"] = get(eng_obj, "shutdown_cost", 0.0) + math_obj["ncost"] = get(eng_obj, "ncost_terms", 3) + math_obj["cost"] = get(eng_obj, "cost", [0.0, 1.0, 0.0]) +end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index b259643ad..222bb47a9 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -38,22 +38,18 @@ function _dss2eng_loadshape!(data_eng::Dict{String,<:Any}, data_dss::Dict{String _apply_like!(dss_obj, data_dss, "loadshape") defaults = _apply_ordered_properties(_create_loadshape(name; _to_kwargs(dss_obj)...), dss_obj) - eng_obj = Dict{String,Any}() - - eng_obj["hour"] = defaults["hour"] - eng_obj["pmult"] = defaults["pmult"] - eng_obj["qmult"] = defaults["qmult"] - eng_obj["use_actual"] = defaults["useactual"] - - if !haskey(data_eng, "loadshape") - data_eng["loadshape"] = Dict{String,Any}() - end + eng_obj = Dict{String,Any}( + "hour" => defaults["hour"], + "pmult" => defaults["pmult"], + "qmult" => defaults["qmult"], + "use_actual" => defaults["useactual"] + ) if import_all _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end - data_eng["loadshape"][name] = eng_obj + _add_eng_obj!(data_eng, "loadshape", name, eng_obj) end end @@ -156,21 +152,13 @@ end "Adds capacitors to `data_eng` from `data_dss`" -function _dss2eng_capacitor_to_shunt!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) +function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "capacitor", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "capacitor") defaults = _apply_ordered_properties(_create_capacitor(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] - - dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") - conn = dyz_map[defaults["conn"]] - - bus_name = _parse_busname(defaults["bus1"])[1] - bus2_name = _parse_busname(defaults["bus2"])[1] - if bus_name!=bus2_name - Memento.error("Capacitor $(name): bus1 and bus2 should connect to the same bus.") - end + conn = defaults["conn"] f_terminals = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) if conn=="wye" @@ -181,116 +169,69 @@ function _dss2eng_capacitor_to_shunt!(data_eng::Dict{String,<:Any}, data_dss::Di t_terminals = [f_terminals[2:end]..., f_terminals[1]] end - # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) - #TODO figure out for more than 3 phases - vnom_ln = defaults["kv"] - if defaults["phases"] in [2,3] - vnom_ln = vnom_ln/sqrt(3) - end - # 'kvar' is specified for all phases at once; we want the per-phase one, in MVar - qnom = (defaults["kvar"]/1E3)/nphases - b = fill(qnom/vnom_ln^2, nphases) - - # convert to a shunt matrix - terminals, B = _calc_shunt(f_terminals, t_terminals, b) - - # if one terminal is ground (0), reduce shunt addmittance matrix - terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) - - eng_obj = create_shunt(status=convert(Int, defaults["enabled"]), bus=bus_name, connections=terminals, g_sh=fill(0.0, size(B)...), b_sh=B) - - eng_obj["source_id"] = "capacitor.$name" + eng_obj = Dict{String,Any}( + "configuration" => conn, + "status" => convert(Int, defaults["enabled"]), + "source_id" => "capacitor.$name", + "phases" => nphases, + "f_connections" => f_terminals, + "t_connections" => t_terminals, + ) if import_all _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end - if !haskey(data_eng, "shunt") - data_eng["shunt"] = Dict{String,Any}() - end - - data_eng["shunt"][name] = eng_obj - end -end - - -"Adds capacitors to `data_eng` from `data_dss`" -function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "capacitor", Dict{String,Any}()) - _apply_like!(dss_obj, data_dss, "capacitor") - defaults = _apply_ordered_properties(_create_capacitor(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) - - nphases = defaults["phases"] - - conn = defaults["conn"] - bus_name = _parse_busname(defaults["bus1"])[1] bus2_name = _parse_busname(defaults["bus2"])[1] + if bus_name == bus2_name + eng_obj["bus"] = bus_name + + # 'kv' is specified as phase-to-phase for phases=2/3 (unsure for 4 and more) + #TODO figure out for more than 3 phases + vnom_ln = defaults["kv"] + if defaults["phases"] in [2,3] + vnom_ln = vnom_ln/sqrt(3) + end + eng_obj["kv"] = fill(vnom_ln, nphases) - if bus_name!=bus2_name - Memento.error(_LOGGER, "Capacitor $(name): bus1 and bus2 should connect to the same bus.") - end + # 'kvar' is specified for all phases at once; we want the per-phase one + eng_obj["kvar"] = fill(defaults["kvar"] / nphases, nphases) - f_terminals = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) - if conn=="wye" - t_terminals = _get_conductors_ordered(defaults["bus2"], default=fill(0,nphases)) + _add_eng_obj!(data_eng, "shunt_capacitor", name, eng_obj) else - # if delta connected, ignore bus2 and generate t_terminals such that - # it corresponds to a delta winding - t_terminals = [f_terminals[2:end]..., f_terminals[1]] - end - - eng_obj = Dict{String,Any}() - - eng_obj["bus"] = bus_name - - eng_obj["configuration"] = conn - - eng_obj["f_terminals"] = f_terminals - eng_obj["t_terminals"] = t_terminals - - eng_obj["kv"] = defaults["kv"] - eng_obj["kvar"] = defaults["kvar"] - - eng_obj["phases"] = defaults["phases"] + eng_obj["f_bus"] = bus_name + eng_obj["t_bus"] = bus2_name - eng_obj["status"] = convert(Int, defaults["enabled"]) - - eng_obj["source_id"] = "capacitor.$name" - - if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) - end + eng_obj["kv"] = fill(defaults["kv"] / nphases, phases) + eng_obj["kvar"] = fill(defaults["kvar"] / nphases, phases) - if !haskey(data_eng, "capacitor") - data_eng["capacitor"] = Dict{String,Any}() + _add_eng_obj!(data_eng, "series_capacitor", name, eng_obj) end - - data_eng["capacitor"][name] = eng_obj end end + "Adds shunt reactors to `data_eng` from `data_dss`" -function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) +function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) if !haskey(dss_obj, "bus2") _apply_like!(dss_obj, data_dss, "reactor") defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) - eng_obj = Dict{String,Any}() - - eng_obj["phases"] = defaults["phases"] + eng_obj = Dict{String,Any}( + "phases" => defaults["phases"], + "configuration" => defaults["conn"], + "bus" => _parse_busname(defaults["bus1"])[1], + "kvar" => defaults["kvar"], + "status" => convert(Int, defaults["enabled"]), + "source_id" => "reactor.$name", + ) - eng_obj["configuration"] = defaults["conn"] connections_default = eng_obj["configuration"] == "wye" ? [collect(1:eng_obj["phases"])..., 0] : collect(1:eng_obj["phases"]) eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], default=connections_default, check_length=false) - eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] - eng_obj["kvar"] = defaults["kvar"] - eng_obj["status"] = convert(Int, defaults["enabled"]) - eng_obj["source_id"] = "reactor.$name" - # if the ground is used directly, register if 0 in eng_obj["connections"] _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) @@ -300,11 +241,48 @@ function _dss2eng_shunt_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{St _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end - if !haskey(data_eng, "shunt_reactor") - data_eng["shunt_reactor"] = Dict{String,Any}() + _add_eng_obj!(data_eng, "shunt_reactor", name, eng_obj) + else + Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating reactor.$name like line") + _apply_like!(dss_obj, data_dss, "reactor") + defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], name, dss_obj["bus2"]; _to_kwargs(dss_obj)...), dss_obj) + + eng_obj = Dict{String,Any}() + + nphases = defaults["phases"] + eng_obj["phases"] = nphases + + eng_obj["f_bus"] = _parse_busname(defaults["bus1"])[1] + eng_obj["t_bus"] = _parse_busname(defaults["bus2"])[1] + + eng_obj["length"] = 1.0 + + eng_obj["rs"] = diagm(0 => fill(0.2, nphases)) + + for key in ["xs", "g_fr", "g_to", "b_fr", "b_to"] + eng_obj[key] = zeros(nphases, nphases) end - data_eng["shunt_reactor"][name] = eng_obj + eng_obj["f_connections"] = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) + eng_obj["t_connections"] = _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)) + + # if the ground is used directly, register + if 0 in eng_obj["f_connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["f_bus"]], eng_obj["f_connections"]) + end + if 0 in eng_obj["t_connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["t_bus"]], eng_obj["t_connections"]) + end + + eng_obj["status"] = convert(Int, defaults["enabled"]) + + eng_obj["source_id"] = "reactor.$(name)" + + if import_all + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + end + + _add_eng_obj!(data_eng, "line_reactor", name, eng_obj) end end end @@ -316,48 +294,33 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String _apply_like!(dss_obj, data_dss, "generator") defaults = _apply_ordered_properties(_create_generator(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) - eng_obj = Dict{String,Any}() - - eng_obj["phases"] = defaults["phases"] - eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)) + nphases = defaults["phases"] - eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] + eng_obj = Dict{String,Any}( + "phases" => nphases, + "connections" => _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)), + "bus" => _parse_busname(defaults["bus1"])[1], + "kw" => fill(defaults["kw"] / nphases, nphases), + "kvar" => fill(defaults["kvar"] / nphases, nphases), + "kv" => fill(defaults["kv"], nphases), + "kvar_min" => fill(defaults["minkvar"] / nphases, nphases), + "kvar_max" => fill(defaults["maxkvar"] / nphases, nphases), + "control_model" => defaults["model"], + "configuration" => "wye", + "status" => convert(Int, defaults["enabled"]), + "source_id" => "generator.$(name)" + ) # if the ground is used directly, register load if 0 in eng_obj["connections"] _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end - eng_obj["kw"] = defaults["kw"] - eng_obj["kvar"] = defaults["kvar"] - eng_obj["kv"] = defaults["kv"] - - eng_obj["kvar_min"] = defaults["minkvar"] - eng_obj["kvar_max"] = defaults["maxkvar"] - - eng_obj["control_model"] = defaults["model"] - - eng_obj["model"] = 2 - eng_obj["startup"] = 0.0 - eng_obj["shutdown"] = 0.0 - eng_obj["ncost"] = 3 - eng_obj["cost"] = [0.0, 1.0, 0.0] - - eng_obj["configuration"] = "wye" - - eng_obj["status"] = convert(Int, defaults["enabled"]) - - eng_obj["source_id"] = "generator.$(name)" - if import_all _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end - if !haskey(data_eng, "generator") - data_eng["generator"] = Dict{String,Any}() - end - - data_eng["generator"][name] = eng_obj + _add_eng_obj!(data_eng, "generator", name, eng_obj) end end @@ -365,50 +328,40 @@ end "Adds vsources to `data_eng` from `data_dss`" function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "vsource", Dict{<:Any,<:Any}()) - eng_obj = Dict{String,Any}() - - if !haskey(data_eng, "vsource") - data_eng["vsource"] = Dict{String,Dict{String,Any}}() - - defaults = _create_vsource(get(dss_obj, "bus1", "sourcebus"), name; _to_kwargs(dss_obj)...) - - ph1_ang = defaults["angle"] - vm_pu = defaults["pu"] + _apply_like!(dss_obj, data_dss, "vsource") + defaults = _create_vsource(get(dss_obj, "bus1", "sourcebus"), name; _to_kwargs(dss_obj)...) - phases = defaults["phases"] - vnom = data_eng["settings"]["set_vbase_val"] - vm = fill(vm_pu, phases)*vnom - va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) + ph1_ang = defaults["angle"] + vm_pu = defaults["pu"] - eng_obj = Dict{String,Any}( - "bus" => _parse_busname(defaults["bus1"])[1], - "connections" => collect(1:phases), - "vm" => vm, - "va" => va, - "rs" => defaults["rmatrix"], - "xs" => defaults["xmatrix"], - "source_id" => "vsource.$name", - "status" => 1 - ) + phases = defaults["phases"] + vnom = data_eng["settings"]["set_vbase_val"] - # if the ground is used directly, register load - if 0 in eng_obj["connections"] - _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) - end + vm = fill(vm_pu, phases)*vnom + va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) - if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) - end + eng_obj = Dict{String,Any}( + "bus" => _parse_busname(defaults["bus1"])[1], + "connections" => collect(1:phases), + "vm" => vm, + "va" => va, + "rs" => defaults["rmatrix"], + "xs" => defaults["xmatrix"], + "source_id" => "vsource.$name", + "status" => convert(Int, defaults["enabled"]) + ) - if !haskey(data_eng, "voltage_source") - data_eng["voltage_source"] = Dict{String,Any}() - end + # if the ground is used directly, register load + if 0 in eng_obj["connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) + end - data_eng["voltage_source"][name] = eng_obj + if import_all + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end - data_eng["vsource"][name] = eng_obj + _add_eng_obj!(data_eng, "voltage_source", name, eng_obj) end end @@ -435,11 +388,7 @@ function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["g_fr"] = fill(0.0, nphases, nphases) eng_obj["g_to"] = fill(0.0, nphases, nphases) - if !haskey(data_eng, "linecode") - data_eng["linecode"] = Dict{String,Any}() - end - - data_eng["linecode"][name] = eng_obj + _add_eng_obj!(data_eng, "linecode", name, eng_obj) end end @@ -455,15 +404,17 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An defaults = _apply_ordered_properties(_create_line(dss_obj["bus1"], dss_obj["bus2"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) - eng_obj = Dict{String,Any}() - - eng_obj["f_bus"] = _parse_busname(defaults["bus1"])[1] - eng_obj["t_bus"] = _parse_busname(defaults["bus2"])[1] - - eng_obj["length"] = defaults["length"] - nphases = defaults["phases"] - eng_obj["n_conductors"] = nphases + + eng_obj = Dict{String,Any}( + "f_bus" => _parse_busname(defaults["bus1"])[1], + "t_bus" => _parse_busname(defaults["bus2"])[1], + "length" => defaults["length"], + "f_connections" => _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)), + "t_connections" => _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)), + "status" => convert(Int, defaults["enabled"]), + "source_id" => "line.$name" + ) if haskey(dss_obj, "linecode") eng_obj["linecode"] = dss_obj["linecode"] @@ -484,9 +435,6 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["g_to"] = fill(0.0, nphases, nphases) end - eng_obj["f_connections"] = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) - eng_obj["t_connections"] = _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)) - # if the ground is used directly, register if 0 in eng_obj["f_connections"] _register_awaiting_ground!(data_eng["bus"][eng_obj["f_bus"]], eng_obj["f_connections"]) @@ -495,26 +443,14 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An _register_awaiting_ground!(data_eng["bus"][eng_obj["t_bus"]], eng_obj["t_connections"]) end - eng_obj["status"] = convert(Int, defaults["enabled"]) - - eng_obj["source_id"] = "line.$(name)" - if import_all _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end if defaults["switch"] - if !haskey(data_eng, "switch") - data_eng["switch"] = Dict{String,Any}() - end - - data_eng["switch"][name] = eng_obj + _add_eng_obj!(data_eng, "switch", name, eng_obj) else - if !haskey(data_eng, "line") - data_eng["line"] = Dict{String, Any}() - end - - data_eng["line"][name] = eng_obj + _add_eng_obj!(data_eng, "line", name, eng_obj) end end end @@ -522,10 +458,6 @@ end "Adds transformers to `data_eng` from `data_dss`" function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - if !haskey(data_eng, "transformer") - data_eng["transformer"] = Dict{String,Any}() - end - for (name, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "transformer") defaults = _apply_ordered_properties(_create_transformer(dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) @@ -625,60 +557,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end - data_eng["transformer"][name] = eng_obj - end -end - - -"Adds line reactors to `data_eng` from `data_dss`" -function _dss2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) - if haskey(dss_obj, "bus2") - Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating reactor.$name like line") - _apply_like!(dss_obj, data_dss, "reactor") - defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], name, dss_obj["bus2"]; _to_kwargs(dss_obj)...), dss_obj) - - eng_obj = Dict{String,Any}() - - nphases = defaults["phases"] - eng_obj["phases"] = nphases - - eng_obj["f_bus"] = _parse_busname(defaults["bus1"])[1] - eng_obj["t_bus"] = _parse_busname(defaults["bus2"])[1] - - eng_obj["length"] = 1.0 - - eng_obj["rs"] = diagm(0 => fill(0.2, nphases)) - - for key in ["xs", "g_fr", "g_to", "b_fr", "b_to"] - eng_obj[key] = zeros(nphases, nphases) - end - - eng_obj["f_connections"] = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) - eng_obj["t_connections"] = _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)) - - # if the ground is used directly, register - if 0 in eng_obj["f_connections"] - _register_awaiting_ground!(data_eng["bus"][eng_obj["f_bus"]], eng_obj["f_connections"]) - end - if 0 in eng_obj["t_connections"] - _register_awaiting_ground!(data_eng["bus"][eng_obj["t_bus"]], eng_obj["t_connections"]) - end - - eng_obj["status"] = convert(Int, defaults["enabled"]) - - eng_obj["source_id"] = "reactor.$(name)" - - if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) - end - - if !haskey(data_eng, "line_reactor") - data_eng["line_reactor"] = Dict{String,Any}() - end - - data_eng["line_reactor"][name] = eng_obj - end + _add_eng_obj!(data_eng, "transformer", name, eng_obj) end end @@ -689,44 +568,31 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, _apply_like!(dss_obj, data_dss, "pvsystem") defaults = _apply_ordered_properties(_create_pvsystem(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) - eng_obj = Dict{String,Any}() - - eng_obj["name"] = name - eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] - - eng_obj["phases"] = defaults["phases"] + # TODO pick parameters for solar objects - eng_obj["configuration"] = "wye" + nphases = defaults["phases"] - eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)) + eng_obj = Dict{String,Any}( + "bus" => _parse_busname(defaults["bus1"])[1], + "phases" => nphases, + "configuration" => "wye", + "connections" => _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)), + "kva" => fill(defaults["kva"] / nphases, nphases), + "kv" => fill(defaults["kv"] / nphases, nphases), + "status" => convert(Int, defaults["enabled"]), + "source_id" => "pvsystem.$name", + ) # if the ground is used directly, register load if 0 in eng_obj["connections"] _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end - eng_obj["kva"] = defaults["kva"] - eng_obj["kv"] = defaults["kv"] - - eng_obj["model"] = 2 - eng_obj["startup"] = 0.0 - eng_obj["shutdown"] = 0.0 - eng_obj["ncost"] = 3 - eng_obj["cost"] = [0.0, 1.0, 0.0] - - eng_obj["status"] = convert(Int, defaults["enabled"]) - - eng_obj["source_id"] = "pvsystem.$(name)" - if import_all _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end - if !haskey(data_eng, "solar") - data_eng["solar"] = Dict{String,Any}() - end - - data_eng["solar"][name] = eng_obj + _add_eng_obj!(data_eng, "solar", name, eng_obj) end end @@ -774,11 +640,7 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< _import_all(eng_obj, defaults, dss_obj["prop_order"]) end - if !haskey(data_eng, "storage") - data_eng["storage"] = Dict{String,Any}() - end - - data_eng["storage"][name] = eng_obj + _add_eng_obj!(data_eng, "storage", name, eng_obj) end end @@ -833,14 +695,12 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban # _dss2eng_xfrmcode!(data_eng, data_dss, import_all) # TODO _dss2eng_transformer!(data_eng, data_dss, import_all) - _dss2eng_line_reactor!(data_eng, data_dss, import_all) + _dss2eng_capacitor!(data_eng, data_dss, import_all) + _dss2eng_reactor!(data_eng, data_dss, import_all) _dss2eng_loadshape!(data_eng, data_dss, import_all) _dss2eng_load!(data_eng, data_dss, import_all) - _dss2eng_capacitor_to_shunt!(data_eng, data_dss, import_all) - _dss2eng_shunt_reactor!(data_eng, data_dss, import_all) - # _dss2eng_sourcebus!(data_eng, data_dss, import_all) _dss2eng_vsource!(data_eng, data_dss, import_all) _dss2eng_generator!(data_eng, data_dss, import_all) diff --git a/src/io/utils.jl b/src/io/utils.jl index 563bcf83f..3820a06bc 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -262,65 +262,67 @@ end "Combines transformers with 'bank' keyword into a single transformer" function _bank_transformers!(data_eng::Dict{String,<:Any}) - bankable_transformers = Dict() - for (id, tr) in data_eng["transformer"] - if haskey(tr, "bank") - bank = tr["bank"] - if !haskey(bankable_transformers, bank) - bankable_transformers[bank] = ([], []) + if haskey(data_eng, "transformer") + bankable_transformers = Dict() + for (id, tr) in data_eng["transformer"] + if haskey(tr, "bank") + bank = tr["bank"] + if !haskey(bankable_transformers, bank) + bankable_transformers[bank] = ([], []) + end + push!(bankable_transformers[bank][1], id) + push!(bankable_transformers[bank][2], tr) end - push!(bankable_transformers[bank][1], id) - push!(bankable_transformers[bank][2], tr) end - end - for (bank, (ids, trs)) in bankable_transformers - # across-phase properties should be the same to be eligible for banking - props = ["bus", "noloadloss", "xsc", "rs", "imag", "vnom", "snom", "polarity", "configuration"] - btrans = Dict{String, Any}(prop=>trs[1][prop] for prop in props) - if !all(tr[prop]==btrans[prop] for tr in trs, prop in props) - continue - end - nrw = length(btrans["bus"]) + for (bank, (ids, trs)) in bankable_transformers + # across-phase properties should be the same to be eligible for banking + props = ["bus", "noloadloss", "xsc", "rs", "imag", "vnom", "snom", "polarity", "configuration"] + btrans = Dict{String, Any}(prop=>trs[1][prop] for prop in props) + if !all(tr[prop]==btrans[prop] for tr in trs, prop in props) + continue + end + nrw = length(btrans["bus"]) - # only attempt to bank wye-connected transformers - if !all(all(tr["configuration"].=="wye") for tr in trs) - continue - end - neutrals = [conns[end] for conns in trs[1]["connections"]] - # ensure all windings have the same neutral - if !all(all(conns[end]==neutrals[w] for (w, conns) in enumerate(tr["connections"])) for tr in trs) - continue - end + # only attempt to bank wye-connected transformers + if !all(all(tr["configuration"].=="wye") for tr in trs) + continue + end + neutrals = [conns[end] for conns in trs[1]["connections"]] + # ensure all windings have the same neutral + if !all(all(conns[end]==neutrals[w] for (w, conns) in enumerate(tr["connections"])) for tr in trs) + continue + end - # this will merge the per-phase properties in such a way that the - # f_connections will be sorted from small to large - f_phases_loc = Dict(hcat([[(c,(i,p)) for (p, c) in enumerate(tr["connections"][1][1:end-1])] for (i, tr) in enumerate(trs)]...)) - locs = [f_phases_loc[x] for x in sort(collect(keys(f_phases_loc)))] - props_merge = ["connections", "tm", "tm_max", "tm_min", "fixed"] - for prop in props_merge - btrans[prop] = [[trs[i][prop][w][p] for (i,p) in locs] for w in 1:nrw] - - # for the connections, also prefix the neutral per winding - if prop=="connections" - for w in 1:nrw - push!(btrans[prop][w], neutrals[w]) + # this will merge the per-phase properties in such a way that the + # f_connections will be sorted from small to large + f_phases_loc = Dict(hcat([[(c,(i,p)) for (p, c) in enumerate(tr["connections"][1][1:end-1])] for (i, tr) in enumerate(trs)]...)) + locs = [f_phases_loc[x] for x in sort(collect(keys(f_phases_loc)))] + props_merge = ["connections", "tm", "tm_max", "tm_min", "fixed"] + for prop in props_merge + btrans[prop] = [[trs[i][prop][w][p] for (i,p) in locs] for w in 1:nrw] + + # for the connections, also prefix the neutral per winding + if prop=="connections" + for w in 1:nrw + push!(btrans[prop][w], neutrals[w]) + end end end - end - btrans["source_id"] = "transformer.$bank" + btrans["source_id"] = "transformer.$bank" - # edit the transformer dict - for id in ids - delete!(data_eng["transformer"], id) - end - btrans_name = bank - if haskey(data_eng["transformer"], bank) - Memento.warn("The bank name ($bank) is already used for another transformer; using the name of the first transformer $(ids[1]) in the bank instead.") - btrans_name = ids[1] + # edit the transformer dict + for id in ids + delete!(data_eng["transformer"], id) + end + btrans_name = bank + if haskey(data_eng["transformer"], bank) + Memento.warn("The bank name ($bank) is already used for another transformer; using the name of the first transformer $(ids[1]) in the bank instead.") + btrans_name = ids[1] + end + data_eng["transformer"][btrans_name] = btrans end - data_eng["transformer"][btrans_name] = btrans end end @@ -346,7 +348,7 @@ function _discover_terminals!(data_eng::Dict{String,<:Any}) end end - for comp_type in [x for x in ["voltage_source", "load", "shunt", "gen"] if haskey(data_eng, x)] + for comp_type in [x for x in ["voltage_source", "load", "gen"] if haskey(data_eng, x)] for comp in values(data_eng[comp_type]) push!(terminals[comp["bus"]], setdiff(comp["connections"], [0])...) end @@ -786,3 +788,13 @@ function _is_after(prop_order::Vector{String}, property1::String, property2::Str return property1_idx > property2_idx end + + +"add engineering data object to engineering data model" +function _add_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, eng_obj_id::Any, eng_obj::Dict{String,<:Any}) + if !haskey(data_eng, eng_obj_type) + data_eng[eng_obj_type] = Dict{Any,Any}() + end + + data_eng[eng_obj_type][eng_obj_id] = eng_obj +end diff --git a/test/opendss.jl b/test/opendss.jl index e24e64019..bcba9d3fe 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -231,13 +231,6 @@ @test pmd_storage["storage"]["1"]["source_id"] == "storage.s1" end - @testset "opendss parse pvsystem" begin - Memento.setlevel!(TESTLOG, "warn") - @test_warn(TESTLOG, "Converting PVSystem \"pv1\" into generator with limits determined by OpenDSS property 'kVA'", - PMD.parse_file("../test/data/opendss/case3_balanced_pv.dss")) - Memento.setlevel!(TESTLOG, "error") - end - @testset "opendss parse verify source_id" begin @test pmd["shunt"]["2"]["source_id"] == "capacitor.c1" @test pmd["shunt"]["4"]["source_id"] == "reactor.reactor3" From ffe3d1dedbc5e71707acaaad31c87e8b3cd0ef3e Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 19 Mar 2020 12:11:01 -0600 Subject: [PATCH 100/224] FIX: automatic solution converter If an eng model is provided to run_mc_model, a eng solution will be returned --- src/data_model/math2eng.jl | 10 +++------- src/prob/common.jl | 10 +++++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index c452d35f7..48ae84ec1 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -55,14 +55,10 @@ function solution_math2eng(solution_math::Dict, data_math::Dict; make_si::Bool=t if !isa(map[:to], Vector) comp_type_math, index = split(map[:to], ".") if !haskey(solution_eng, comp_type_eng) - solution_eng[comp_type_eng] = Dict{String, Any}() - end - if haskey(solution_math, comp_type_math) - solution_eng[comp_type_eng][name] = solution_math[comp_type_math][index] - else - #TODO add empty dicts if math object has no solution object? - solution_eng[comp_type_eng][name] = Dict{String, Any}() + solution_eng[comp_type_eng] = Dict{Any,Any}() end + + solution_eng[comp_type_eng][name] = solution_math[comp_type_math][index] end end end diff --git a/src/prob/common.jl b/src/prob/common.jl index 7c35214dd..4d82fc52f 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -1,13 +1,13 @@ "alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" function run_mc_model(data::Dict{String,<:Any}, model_type, solver, build_mc; ref_extensions=[], kwargs...) if get(data, "data_model", "mathematical") == "engineering" - data = transform_data_model(data) - end + data_math = transform_data_model(data) - result = _PMs.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) + result = _PMs.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) - if get(data, "data_model", "mathematical") == "engineering" - result["solution"] = solution_math2eng(result["solution"], data; make_si=get(data, "per_unit", false), make_deg=get(data, "per_unit", false)) + result["solution"] = solution_math2eng(result["solution"], data_math; make_si=get(data, "per_unit", false), make_deg=get(data, "per_unit", false)) + else + result = _PMs.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) end return result From e8ddd2547d78cd9f8c8e3d8990884e7664e58101 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 19 Mar 2020 15:36:40 -0600 Subject: [PATCH 101/224] REF: New solution unmap pattern --- src/data_model/math2eng.jl | 197 ++++++++++++++++++++----------------- src/data_model/utils.jl | 10 +- 2 files changed, 111 insertions(+), 96 deletions(-) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index 48ae84ec1..78b9062f0 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -8,7 +8,7 @@ function _map_math2eng!(data_math) unmap_keys = sort(keys(data_math["map"]), rev=true) reverse_bus_lookup = Dict{Int,Any}((bus_math, bus_eng) for (bus_eng, bus_math) in data_math["bus_lookup"]) for key in unmap_keys - @eval $(map[:unmap_function])(data_eng, data_math, data_math["map"][key], reverse_bus_lookup) + getfield(PowerModelsDistribution, map[:unmap_function])(data_eng, data_math, data_math["map"][key], reverse_bus_lookup) end return data_eng @@ -27,138 +27,129 @@ function solution_math2eng(solution_math::Dict, data_math::Dict; make_si::Bool=t map_keys = sort(collect(keys(data_math["map"])); rev=true) for map_key in map_keys map = data_math["map"][map_key] - umap_type = map[:unmap_function] - - if umap_type==:_map_math2eng_sourcebus! - # nothing to do for the solution - elseif umap_type==:_map_math2eng_root! - # nothing to do for the solution - elseif umap_type==:_map_math2eng_transformer! - name = map[:from] - trans_2wa_ids = [index for (comp_type, index) in split.(map[:to], ".", limit=2) if comp_type=="transformer"] - - if !haskey(solution_eng, "transformer") - solution_eng["transformer"] = Dict{String, Any}() - end - - trans = Dict() - prop_map = Dict("pf"=>"p", "qf"=>"q") - for (prop_from, prop_to) in prop_map - trans[prop_to] = [get(solution_math["transformer"][id], prop_from, NaN) for id in trans_2wa_ids] - end - - solution_eng["transformer"][name] = trans - - else - comp_type_eng = match(r"_map_math2eng_(\w+)!", string(umap_type)).captures[1] - name = map[:from] - if !isa(map[:to], Vector) - comp_type_math, index = split(map[:to], ".") - if !haskey(solution_eng, comp_type_eng) - solution_eng[comp_type_eng] = Dict{Any,Any}() - end - - solution_eng[comp_type_eng][name] = solution_math[comp_type_math][index] - end - end + reverse_bus_lookup = Dict{Int,Any}((bus_math, bus_eng) for (bus_eng, bus_math) in data_math["bus_lookup"]) + getfield(PowerModelsDistribution, map[:unmap_function])(solution_eng, solution_math, map, reverse_bus_lookup; map_solution=true) end return solution_eng end -function _map_math2eng_voltage_source!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_voltage_source!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) if !haskey(data_eng, "voltage_source") data_eng["voltage_source"] = Dict{Any,Any}() end - eng_obj = Dict{String,Any}() - merge!(eng_obj, map[:extras]) + eng_obj = _init_unmap_eng_obj!(data_eng, "voltage_source", map; map_solution=map_solution) - for to_id in map[:to] - math_obj = _get_math_obj(data_math, to_id) - if startswith(to_id, "bus") - eng_obj["vm"] = math_obj["vm"] - eng_obj["va"] = math_obj["va"] - - elseif startswith(to_id, "gen") - eng_obj["source_id"] = strip(math_obj["source_id"], "_virtual_gen.") - - elseif startswith(to_id, "branch") - eng_obj["bus"] = bus_lookup[math_obj["t_bus"]] - eng_obj["rs"] = math_obj["br_r"] - eng_obj["xs"] = math_obj["br_x"] - else - Memento.warn(_LOGGER, "transforming from $to_id to $(map[:from]) is not supported") + if map_solution + for to_id in map[:to] + math_obj = _get_math_obj(data_math, to_id) + if startswith(to_id, "gen") + for property in ["pg", "qg", "pg_bus", "qg_bus"] + if haskey(math_obj, property) + eng_obj[property] = math_obj[property] + end + end + end + end + else + for to_id in map[:to] + math_obj = _get_math_obj(data_math, to_id) + if startswith(to_id, "bus") + eng_obj["vm"] = math_obj["vm"] + eng_obj["va"] = math_obj["va"] + + elseif startswith(to_id, "gen") + eng_obj["source_id"] = strip(math_obj["source_id"], "_virtual_gen.") + + elseif startswith(to_id, "branch") + eng_obj["bus"] = bus_lookup[math_obj["t_bus"]] + eng_obj["rs"] = math_obj["br_r"] + eng_obj["xs"] = math_obj["br_x"] + else + Memento.warn(_LOGGER, "transforming from $to_id to $(map[:from]) is not supported") + end end end - data_eng[map[:from]] = eng_obj + data_eng["voltage_source"][map[:from]] = eng_obj end -function _map_math2eng_bus!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) - eng_obj = _init_unmap_eng_obj!(data_eng, "bus", map) +function _map_math2eng_bus!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) + eng_obj = _init_unmap_eng_obj!(data_eng, "bus", map; map_solution=map_solution) math_obj = _get_math_obj(data_math, map[:to]) - eng_obj["status"] = math_obj["bus_type"] == 4 ? 0 : 1 + if map_solution + eng_obj = math_obj + else + eng_obj["status"] = math_obj["bus_type"] == 4 ? 0 : 1 - for key in ["vm", "va", "vmin", "vmax"] - if haskey(math_obj, key) - eng_obj[key] = math_obj[key] + for key in ["vm", "va", "vmin", "vmax"] + if haskey(math_obj, key) + eng_obj[key] = math_obj[key] + end end end - - data_eng[map[:from]] = eng_obj + data_eng["bus"][map[:from]] = eng_obj end -function _map_math2eng_load!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) - eng_obj = _init_unmap_eng_obj!(data_eng, "load", map) +function _map_math2eng_load!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) + eng_obj = _init_unmap_eng_obj!(data_eng, "load", map; map_solution=map_solution) math_obj = _get_math_obj(data_math, map[:to]) - eng_obj["vnom"] = math_obj["vnom_kv"] - eng_obj["bus"] = bus_lookup[math_obj["load_bus"]] + if map_solution + eng_obj = math_obj + else + eng_obj["vnom"] = math_obj["vnom_kv"] + eng_obj["bus"] = bus_lookup[math_obj["load_bus"]] - eng_obj["pd"] = math_obj["pd"] - eng_obj["qd"] = math_obj["qd"] + eng_obj["pd"] = math_obj["pd"] + eng_obj["qd"] = math_obj["qd"] - eng_obj["status"] = math_obj["status"] + eng_obj["status"] = math_obj["status"] - eng_obj["configuration"] = math_obj["configuration"] + eng_obj["configuration"] = math_obj["configuration"] + end - data_eng[map[:from]] = eng_obj + data_eng["load"][map[:from]] = eng_obj end -function _map_math2eng_shunt_capacitor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) - eng_obj = _init_unmap_eng_obj!(data_eng, "shunt_capacitor", map) +function _map_math2eng_shunt_capacitor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) + eng_obj = _init_unmap_eng_obj!(data_eng, "shunt_capacitor", map; map_solution=map_solution) math_obj = _get_math_obj(data_math, map[:to]) - eng_obj["bus"] = bus_lookup["shunt_bus"] - eng_obj["status"] = math_obj["status"] + if map_solution + eng_obj = math_obj + else + eng_obj["bus"] = bus_lookup["shunt_bus"] + eng_obj["status"] = math_obj["status"] + + eng_obj["configuration"] = eng_obj["configuration"] - eng_obj["configuration"] = eng_obj["configuration"] + B = math_obj["bs"] - B = math_obj["bs"] + if math_obj["configuration"] == "wye" + b_cap_pu = diag(B) + else + # TODO + end - if math_obj["configuration"] == "wye" - b_cap_pu = diag(B) - else - # TODO + # TODO how to pull kv, kvar back out, this is a destructive transformation end - # TODO how to pull kv, kvar back out, this is a destructive transformation - - data_eng[map[:from]] = eng_obj + data_eng["shunt_capacitor"][map[:from]] = eng_obj end -function _map_math2eng_shunt!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_shunt!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) end -function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) eng_obj = _init_unmap_eng_obj!(data_eng, "generator", map) math_obj = _get_math_obj(data_math, map[:to]) @@ -183,29 +174,49 @@ function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{ eng_obj["control_mode"] = 1 end - data_eng[map[:from]] = eng_obj + data_eng["generator"][map[:from]] = eng_obj end -function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) end -function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) end -function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) end -function _map_math2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) end -function _map_math2eng_switch!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_switch!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) end -function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) + eng_obj = _init_unmap_eng_obj!(data_eng, "transformer", map; map_solution=map_solution) + + if map_solution + trans_2wa_ids = [index for (comp_type, index) in split.(map[:to], ".", limit=2) if comp_type=="transformer"] + + prop_map = Dict("pf"=>"p", "qf"=>"q") + for (prop_from, prop_to) in prop_map + eng_obj[prop_to] = [get(data_math["transformer"][id], prop_from, NaN) for id in trans_2wa_ids] + end + else + end + + data_eng["transformer"][map[:from]] = eng_obj end + + +function _map_math2eng_root!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) + if map_solution + else + end +end \ No newline at end of file diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 6a08eb49a..4e2886419 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -442,19 +442,23 @@ end "initialization actions for unmapping" -function _init_unmap_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, map::Dict{Symbol,Any})::Dict{String,Any} +function _init_unmap_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, map::Dict{Symbol,Any}; map_solution::Bool=false)::Dict{String,Any} if !haskey(data_eng, eng_obj_type) data_eng[eng_obj_type] = Dict{Any,Any}() end eng_obj = Dict{String,Any}() - return merge(eng_obj, map[:extras]) + if !map_solution + merge!(eng_obj, map[:extras]) + end + + return eng_obj end "returns component from the mathematical data model" function _get_math_obj(data_math::Dict{String,<:Any}, to_id::String)::Dict{String,Any} - math_type, math_id = split(map[:to], '.') + math_type, math_id = split(to_id, '.') return data_math[math_type][math_id] end From 6338a0b4e53dffdcaf1dc93e16efe6c12ab90e4c Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 23 Mar 2020 15:45:25 -0600 Subject: [PATCH 102/224] FIX: deg2rad conversion --- src/data_model/eng2math.jl | 4 ++-- src/data_model/units.jl | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 92268b1b6..0ba3f7f43 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -833,7 +833,7 @@ end "" function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) # TODO create option for lossy vs lossless sourcebus connection - for (name, eng_obj) in data_eng["voltage_source"] + for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Any}()) nconductors = data_math["conductors"] # TODO fix per unit problem @@ -843,7 +843,7 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "name" => "_virtual_bus.voltage_source.$name", "bus_type" => 3, "vm" => eng_obj["vm"], - "va" => deg2rad.(eng_obj["va"]), + "va" => eng_obj["va"], "vmin" => eng_obj["vm"], "vmax" => eng_obj["vm"], "basekv" => data_math["basekv"] diff --git a/src/data_model/units.jl b/src/data_model/units.jl index d55a31517..4aeaae49b 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -1,3 +1,11 @@ +const _dimensionalize_math = Dict( + "bus" => Dict("rad2deg"=>["va"], "vbase"=>["vm", "vr", "vi"]), + "gen" => Dict("sbase"=>["pg", "qg", "pg_bus", "qg_bus"]), + "load" => Dict("sbase"=>["pd", "qd", "pd_bus", "qd_bus"]), + "line" => Dict("sbase"=>["pf", "qf", "pt", "qt"]), +) + + "finds voltage zones" function _find_zones(data_model) unused_line_ids = Set(keys(data_model["branch"])) @@ -186,9 +194,9 @@ function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) _scale_props!(bus, ["rg", "xg"], z_scale) # TODO fix - # if haskey(bus ,"va") - # bus["va"] = deg2rad.(bus["va"]) - # end + if haskey(bus ,"va") + bus["va"] = deg2rad.(bus["va"]) + end # save new vbase bus["vbase"] = vbase @@ -324,14 +332,6 @@ function add_big_M!(data_model; kwargs...) end -const _dimensionalize_math = Dict( - "bus" => Dict("rad2deg"=>["va"], "vbase"=>["vm", "vr", "vi"]), - "gen" => Dict("sbase"=>["pg", "qg", "pg_bus", "qg_bus"]), - "load" => Dict("sbase"=>["pd", "qd", "pd_bus", "qd_bus"]), - "line" => Dict("sbase"=>["pf", "qf", "pt", "qt"]), -) - - "" function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true, convert_rad2deg=true) solution_si = deepcopy(solution) From 4d019c30492cafeda3a508ad97d53fc72445cdc0 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 23 Mar 2020 15:46:17 -0600 Subject: [PATCH 103/224] REF: model building tools tested with single line, two bus, single load model (3 phase) --- src/data_model/components.jl | 138 ++++++++++++++++++++++++++--------- 1 file changed, 103 insertions(+), 35 deletions(-) diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 3628e0aad..c83cda073 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -20,43 +20,56 @@ function add_object!(data_eng::Dict{String,<:Any}, obj_type::String, obj_id::Any data_eng[obj_type] = Dict{Any,Any}() end - data_eng[obj_type][obj_id] = object -end - + if !haskey(object, "source_id") + object["source_id"] = "$obj_type.$obj_id" + end -"" -function create_linecode(; kwargs...) - linecode = Dict{String,Any}( - "rs" => get(kwargs, :rs, fill(0.0, n_conductors, n_conductors)), - "xs" => get(kwargs, :xs, fill(0.0, n_conductors, n_conductors)), - "g_fr" => get(kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)), - "b_fr" => get(kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)), - "g_to" => get(kwargs, :g_to, fill(0.0, n_conductors, n_conductors)), - "b_to" => get(kwargs, :b_to, fill(0.0, n_conductors, n_conductors)), -) + if obj_type == "voltage_source" + if !haskey(data_eng["settings"], "set_vbase_bus") + data_eng["settings"]["set_vbase_bus"] = object["bus"] + end - n_conductors = 0 - for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] - if haskey(kwargs, key) - n_conductors = size(kwargs[key])[1] + if !haskey(data_eng, "sourcebus") + data_eng["sourcebus"] = object["bus"] end end - _add_unused_kwargs!(linecode, kwargs) + for bus_key in ["f_", "t_", ""] + if haskey(object, "$(bus_key)bus") + if !haskey(data_eng, "bus") + data_eng["bus"] = Dict{Any,Any}() + end - return linecode + if obj_type == "transformer" + for (wdg, bus_id) in enumerate(data_eng["bus"]) + if !haskey(data_eng["bus"], bus_id) + data_eng["bus"][bus_id] = create_bus(; terminals=object["connections"][wdg]) + end + end + else + if !haskey(data_eng["bus"], object["$(bus_key)bus"]) + data_eng["bus"][object["$(bus_key)bus"]] = create_bus(; terminals=object["$(bus_key)connections"]) + end + end + end + end + + data_eng[obj_type][obj_id] = object end "creates an engineering model" -function create_eng_model(name; kwargs...)::Dict{String,Any} +function Model(kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) data_model = Dict{String,Any}( - "name" => name, + "name" => get(kwargs, :name, ""), "data_model" => "engineering", "settings" => Dict{String,Any}( - "v_var_scalar" => get(kwargs, :v_var_scalar, 1e3) + "v_var_scalar" => get(kwargs, :v_var_scalar, 1e3), + "set_vbase_val" => get(kwargs, :basekv, 1.0), + "set_sbase_val" => get(kwargs, :baseMVA, 1.0), + "basefreq" => get(kwargs, :basefreq, 60.0), ) ) @@ -66,15 +79,41 @@ function create_eng_model(name; kwargs...)::Dict{String,Any} end +"" +function create_linecode(; kwargs...) + n_conductors = 0 + for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] + if haskey(kwargs, key) + n_conductors = size(kwargs[key])[1] + end + end + + linecode = Dict{String,Any}( + "rs" => get(kwargs, :rs, fill(0.0, n_conductors, n_conductors)), + "xs" => get(kwargs, :xs, fill(0.0, n_conductors, n_conductors)), + "g_fr" => get(kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)), + "b_fr" => get(kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)), + "g_to" => get(kwargs, :g_to, fill(0.0, n_conductors, n_conductors)), + "b_to" => get(kwargs, :b_to, fill(0.0, n_conductors, n_conductors)), + ) + + _add_unused_kwargs!(linecode, kwargs) + + return linecode +end + + "" function create_line(; kwargs...) kwargs = Dict{Symbol,Any}(kwargs) + @assert haskey(kwargs, :f_bus) && haskey(kwargs, :t_bus) "Line must at least have f_bus and t_bus specified" + N = length(get(kwargs, :f_connections, collect(1:4))) # if no linecode, then populate loss parameters with zero if !haskey(kwargs, :linecode) - n_conductors = 0 + n_conductors = N for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] if haskey(kwargs, key) n_conductors = size(kwargs[key])[1] @@ -83,17 +122,20 @@ function create_line(; kwargs...) end line = Dict{String,Any}( + "f_bus" => kwargs[:f_bus], + "t_bus" => kwargs[:t_bus], "status" => get(kwargs, :status, 1), "f_connections" => get(kwargs, :f_connections, collect(1:4)), "t_connections" => get(kwargs, :t_connections, collect(1:4)), "angmin" => get(kwargs, :angmin, fill(-60/180*pi, N)), "angmax" => get(kwargs, :angmax, fill( 60/180*pi, N)), - "rs" => get(kwargs, :rs, fill(0.0, n_conductors, n_conductors)), - "xs" => get(kwargs, :xs, fill(0.0, n_conductors, n_conductors)), - "g_fr" => get(kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)), - "b_fr" => get(kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)), - "g_to" => get(kwargs, :g_to, fill(0.0, n_conductors, n_conductors)), - "b_to" => get(kwargs, :b_to, fill(0.0, n_conductors, n_conductors)), + "length" => get(kwargs, :length, 1.0), + "rs" => get(kwargs, :rs, diagm(0 => fill(0.01, n_conductors))), + "xs" => get(kwargs, :xs, diagm(0 => fill(0.01, n_conductors))), + "g_fr" => get(kwargs, :g_fr, diagm(0 => fill(0.0, n_conductors))), + "b_fr" => get(kwargs, :b_fr, diagm(0 => fill(0.0, n_conductors))), + "g_to" => get(kwargs, :g_to, diagm(0 => fill(0.0, n_conductors))), + "b_to" => get(kwargs, :b_to, diagm(0 => fill(0.0, n_conductors))), ) _add_unused_kwargs!(line, kwargs) @@ -129,9 +171,10 @@ function create_load(; kwargs...) "status" => get(kwargs, :status, 1), "configuration" => get(kwargs, :configuration, "wye"), "model" => get(kwargs, :model, "constant_power"), + "connections" => get(kwargs, :connections, get(kwargs, :configuration, "wye")=="wye" ? [1, 2, 3, 4] : [1, 2, 3]), + "vnom" => get(kwargs, :vnom, 1.0) ) - load["connections"] = get(kwargs, :connections, load["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]), if load["model"]=="constant_power" load["pd"] = get(kwargs, :pd, fill(0.0, 3)) load["qd"] = get(kwargs, :qd, fill(0.0, 3)) @@ -184,6 +227,7 @@ function create_transformer(; kwargs...) "tm_max" => get(kwargs, :tm_max, fill(fill(1.1, 3), n_windings)), "tm_step" => get(kwargs, :tm_step, fill(fill(1/32, 3), n_windings)), "tm_fix" => get(kwargs, :tm_fix, fill(fill(true, 3), n_windings)), + "connections" => get(kwargs, :connections, fill(collect(1:4), n_windings)), ) _add_unused_kwargs!(transformer, kwargs) @@ -234,6 +278,12 @@ function create_voltage_source(; kwargs...) "connections" => get(kwargs, :connections, collect(1:3)), ) + nphases = length(voltage_source["connections"]) + voltage_source["vm"] = get(kwargs, :vm, fill(1.0, nphases)) + voltage_source["va"] = deg2rad.(get(kwargs, :va, rad2deg.(_wrap_to_pi.([-2*pi/nphases*(i-1) for i in 1:nphases])))) + voltage_source["rs"] = fill(0.1, nphases, nphases) + voltage_source["xs"] = fill(0.0, nphases, nphases) + _add_unused_kwargs!(voltage_source, kwargs) return voltage_source @@ -248,8 +298,26 @@ function delete_component!(data_eng::Dict{String,<:Any}, component_type::String, end end - -# create add_{component_type}! methods that will create a component and add it to the engineering data model -for comp in keys(_eng_model_dtypes) - eval(Meta.parse("add_$(comp)!(data_model, name; kwargs...) = add_object!(data_model, \"$comp\", name, create_$comp(; kwargs...))")) -end +# Data objects +add_bus!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "bus", id, create_bus(; kwargs...)) +add_linecode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "linecode", id, create_linecode(; kwargs...)) +# add_xfmrcode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "xfmrcode", id, create_xfmrcode(; kwargs...)) +# add_timeseries!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "timeseries", id, create_timeseries(; kwargs...)) + +# Edge objects +add_line!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "line", id, create_line(; f_bus=f_bus, t_bus=t_bus, kwargs...)) +add_transformer!(data_eng::Dict{String,<:Any}, id::Any, bus::Vector{Any}; kwargs...) = add_object!(data_eng, "transformer", id, create_transformer(; bus=bus, kwargs...)) +# add_switch!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "switch", id, create_switch(; f_bus=f_bus, t_bus=t_bus, kwargs...)) +# add_series_capacitor!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "series_capacitor", id, create_series_capacitor(; f_bus=f_bus, t_bus=t_bus, kwargs...)) +# add_line_reactor!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "line_reactor", id, create_line_reactor(; f_bus=f_bus, t_bus=t_bus, kwargs...)) + +# Node objects +add_load!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "load", id, create_load(; bus=bus, kwargs...)) +add_shunt_capacitor!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "shunt_capacitor", id, create_shunt_capacitor(; bus=bus, kwargs...)) +# add_shunt_reactor!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "shunt_reactor", id, create_shunt_reactor(; bus=bus, kwargs...)) +add_shunt!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "shunt", id, create_shunt(; bus=bus, kwargs...)) +add_voltage_source!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "voltage_source", id, create_voltage_source(; bus=bus, kwargs...)) +add_generator!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "generator", id, create_generator(; bus=bus, kwargs...)) +# add_storage!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "storage", id, create_storage(; bus=bus, kwargs...)) +# add_solar!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "solar", id, create_solar(; bus=bus, kwargs...)) +# add_wind!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "wind", id, create_wind(; bus=bus, kwargs...)) \ No newline at end of file From 2f9256ad7dd96fa1970ee1a4437b0a02519036b3 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 25 Mar 2020 15:09:08 -0600 Subject: [PATCH 104/224] RM: sourcebus Removes "sourcebus" field at root level, unneeded now that we have a voltage_source called source --- src/core/data.jl | 7 ++----- src/data_model/components.jl | 4 ---- src/data_model/eng2math.jl | 8 ++++---- src/io/dss_structs.jl | 2 +- src/io/opendss.jl | 5 ++--- test/data.jl | 4 ++-- 6 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index c7ab75c52..dc0ee0cd2 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -51,7 +51,6 @@ function count_nodes(data::Dict{String,<:Any})::Int n_nodes = 0 if get(data, "data_model", missing) == "dss" - sourcebus = get(data["vsource"]["source"], "bus1", "sourcebus") all_nodes = Dict() for obj_type in values(data) for object in values(obj_type) @@ -90,9 +89,7 @@ function count_nodes(data::Dict{String,<:Any})::Int end for (name, phases) in all_nodes - if name != sourcebus - n_nodes += length(phases) - end + n_nodes += length(phases) end elseif get(data, "data_model", missing) in ["mathematical", "engineering"] || (haskey(data, "source_type") && data["source_type"] == "matlab") if get(data, "data_model", missing) == "mathematical" @@ -108,7 +105,7 @@ function count_nodes(data::Dict{String,<:Any})::Int name = bus["name"] end - if all(!occursin(pattern, name) for pattern in [_excluded_count_busname_patterns..., data["sourcebus"]]) + if all(!occursin(pattern, name) for pattern in [_excluded_count_busname_patterns...]) n_nodes += sum(bus["vmax"] .> 0.0) end end diff --git a/src/data_model/components.jl b/src/data_model/components.jl index c83cda073..6576134be 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -28,10 +28,6 @@ function add_object!(data_eng::Dict{String,<:Any}, obj_type::String, obj_id::Any if !haskey(data_eng["settings"], "set_vbase_bus") data_eng["settings"]["set_vbase_bus"] = object["bus"] end - - if !haskey(data_eng, "sourcebus") - data_eng["sourcebus"] = object["bus"] - end end for bus_key in ["f_", "t_", ""] diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 0ba3f7f43..4bf9b12e0 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -20,7 +20,7 @@ const _1to1_maps = Dict{String,Vector{String}}( ) const _extra_eng_data = Dict{String,Vector{String}}( - "root" => ["sourcebus", "files", "dss_options", "settings"], + "root" => ["files", "dss_options", "settings"], "bus" => ["grounded", "neutral", "awaiting_ground", "xg", "phases", "rg", "terminals"], "load" => [], "shunt_capacitor" => [], @@ -50,7 +50,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) "name" => data_eng["name"], "per_unit" => get(data_eng, "per_unit", false), "data_model" => "mathematical", - "sourcebus" => get(data_eng, "sourcebus", "sourcebus"), + "settings" => data_eng["settings"], ) data_math["conductors"] = kron_reduced ? 3 : 4 @@ -832,7 +832,7 @@ end "" function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) - # TODO create option for lossy vs lossless sourcebus connection + # TODO create option for lossy vs lossless source connection for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Any}()) nconductors = data_math["conductors"] @@ -873,7 +873,7 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "name" => "_virtual_branch.voltage_source.$name", "source_id" => "_virtual_branch.$(eng_obj["source_id"])", "f_bus" => bus_obj["bus_i"], - "t_bus" => data_math["bus_lookup"][data_eng["sourcebus"]], + "t_bus" => data_math["bus_lookup"][eng_obj["bus"]], "angmin" => fill(-60.0, nconductors), "angmax" => fill( 60.0, nconductors), "shift" => fill(0.0, nconductors), diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index afac36de3..18cfe79f0 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -564,7 +564,7 @@ end """ Creates a Dict{String,Any} containing all of the expected properties for a Voltage Source. If `bus2` is not specified, VSource will be treated like a -generator. Mostly used as `sourcebus` which represents the circuit. See +generator. Mostly used as `source` which represents the circuit. See OpenDSS documentation for valid fields and ways to specify the different properties. """ diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 222bb47a9..c2ff0ef94 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -667,9 +667,9 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban if haskey(data_dss, "vsource") && haskey(data_dss["vsource"], "source") && haskey(data_dss, "circuit") source = data_dss["vsource"]["source"] defaults = _create_vsource(get(source, "bus1", "sourcebus"), source["name"]; _to_kwargs(source)...) + source_bus = defaults["bus1"] data_eng["name"] = data_dss["circuit"] - data_eng["sourcebus"] = _parse_busname(defaults["bus1"])[1] data_eng["data_model"] = "engineering" # TODO rename fields @@ -677,7 +677,7 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban data_eng["settings"]["v_var_scalar"] = 1e3 # data_eng["settings"]["set_vbase_val"] = defaults["basekv"] / sqrt(3) * 1e3 data_eng["settings"]["set_vbase_val"] = defaults["basekv"]/sqrt(3) - data_eng["settings"]["set_vbase_bus"] = data_eng["sourcebus"] + data_eng["settings"]["set_vbase_bus"] = source_bus data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1E3 data_eng["settings"]["basefreq"] = get(get(data_dss, "options", Dict{String,Any}()), "defaultbasefreq", 60.0) @@ -701,7 +701,6 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban _dss2eng_loadshape!(data_eng, data_dss, import_all) _dss2eng_load!(data_eng, data_dss, import_all) - # _dss2eng_sourcebus!(data_eng, data_dss, import_all) _dss2eng_vsource!(data_eng, data_dss, import_all) _dss2eng_generator!(data_eng, data_dss, import_all) _dss2eng_pvsystem!(data_eng, data_dss, import_all) diff --git a/test/data.jl b/test/data.jl index 5285428b7..56cbf2739 100644 --- a/test/data.jl +++ b/test/data.jl @@ -73,10 +73,10 @@ end dss = PMD.parse_dss("../test/data/opendss/case5_phase_drop.dss") pmd = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss") - @test count_nodes(dss) == 7 + @test count_nodes(dss) == 10 # stopped excluding source from node count @test count_nodes(dss) == count_nodes(pmd) dss = PMD.parse_dss("../test/data/opendss/ut_trans_2w_yy.dss") - @test count_nodes(dss) == 9 + @test count_nodes(dss) == 12 # stopped excluding source from node count end end From 42246fc5399b28040d93b63b5ba694423017a78a Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 25 Mar 2020 15:10:55 -0600 Subject: [PATCH 105/224] REF: parse_file logic Changes parse_file to include automatic checks, refactors make_per_unit to include moving back to si from pu (wip) Removes warning about supported opendss components, no longer relevant --- src/data_model/checks.jl | 44 +++++++++---------- src/data_model/units.jl | 30 ++++++++++++- src/io/common.jl | 91 ++++++++++++++++++++++------------------ test/opendss.jl | 3 -- 4 files changed, 101 insertions(+), 67 deletions(-) diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index 56e1adb3e..52875337a 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -208,6 +208,28 @@ const _eng_model_req_fields= Dict{Symbol,Vector{Symbol}}( ) +"checks the engineering data model for correct data types, required fields and applies default checks" +function check_eng_data_model(data_eng::Dict{String,<:Any}) + for (component_type, components) in data_eng + if isa(components, Dict) + for (name, component) in keys(components) + _check_eng_component_dtypes(data_eng, component_type, name) + + for field in get(_eng_model_req_fields, Symbol(component_type), Vector{Symbol}([])) + @assert haskey(component, string(field)) "The property \'$field\' is missing on $component_type $name" + end + + for check in get(_eng_model_checks, Symbol(component_type), missing) + if !ismissing(check) + @eval $(check)(data_eng, name) + end + end + end + end + end +end + + "checks that an engineering model component has the correct data types" function _check_eng_component_dtypes(data_eng::Dict{String,<:Any}, component_type::String, component_name::String; additional_dtypes=Dict{Symbol,Type}()) if haskey(_eng_model_dtypes, Symbol(component_type)) @@ -293,28 +315,6 @@ function _check_configuration_infer_dim(object::Dict{String,<:Any}; context::Uni end -"checks the engineering data model for correct data types, required fields and applies default checks" -function check_eng_data_model(data_eng::Dict{String,<:Any}) - for (component_type, components) in data_eng - if isa(components, Dict) - for (name, component) in keys(components) - _check_eng_component_dtypes(data_eng, component_type, name) - - for field in get(_eng_model_req_fields, Symbol(component_type), Vector{Symbol}([])) - @assert haskey(component, string(field)) "The property \'$field\' is missing on $component_type $name" - end - - for check in get(_eng_model_checks, Symbol(component_type), missing) - if !ismissing(check) - @eval $(check)(data_eng, name) - end - end - end - end - end -end - - "bus data checks" function _check_bus(data_eng::Dict{String,<:Any}, name::Any) bus = data_eng["bus"][name] diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 4aeaae49b..48fa9a60b 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -6,6 +6,32 @@ const _dimensionalize_math = Dict( ) +"converts data model between per-unit and SI units" +function make_per_unit!(data::Dict{String,<:Any}) + data_model_type = get(data, "data_model", "mathematical") + + if data_model_type == "mathematical" + if !get(data, "per_unit", false) + bus_indexed_id = string(data["bus_lookup"][data["settings"]["set_vbase_bus"]]) + vbases = Dict(bus_indexed_id=>data["settings"]["set_vbase_val"]) + sbase = data["settings"]["set_sbase_val"] + + _make_math_per_unit!(data, vbases=vbases, sbase=sbase, v_var_scalar=data["settings"]["v_var_scalar"]) + else + # make math model si units + end + elseif data_model_type == "engineering" + if !get(data, "per_unit", false) + # make eng model per unit + else + # make eng model si units + end + else + Memento.warn(_LOGGER, "Data model '$data_model_type' is not recognized, no per-unit transformation performed") + end +end + + "finds voltage zones" function _find_zones(data_model) unused_line_ids = Set(keys(data_model["branch"])) @@ -91,7 +117,9 @@ end "converts to per unit from SI" -function make_per_unit!(data_model; settings=missing, sbase=1.0, vbases=missing, v_var_scalar=missing) +function _make_math_per_unit!(data_model; settings=missing, sbase=1.0, vbases=missing, v_var_scalar=missing) + + if ismissing(sbase) if !ismissing(settings) && haskey(settings, "set_sbase") sbase = settings["set_sbase"] diff --git a/src/io/common.jl b/src/io/common.jl index 806613328..552636048 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -3,72 +3,81 @@ Parses the IOStream of a file into a Three-Phase PowerModels data structure. """ -function parse_file(io::IO; data_model::String="mathematical", import_all::Bool=false, filetype::AbstractString="json", bank_transformers::Bool=true) - if filetype == "m" - pmd_data = PowerModelsDistribution.parse_matlab(io) - elseif filetype == "dss" - Memento.warn(_LOGGER, "Not all OpenDSS features are supported, currently only minimal support for lines, loads, generators, and capacitors as shunts. Transformers and reactors as transformer branches are included, but value translation is not fully supported.") - pmd_data = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) - elseif filetype == "json" - pmd_data = PowerModels.parse_json(io; validate=false) - else - Memento.error(_LOGGER, "only .m and .dss files are supported") - end +function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="mathematical", import_all::Bool=false, bank_transformers::Bool=true, lossless::Bool=false)::Dict{String,Any} + if filetype == "dss" + data_eng = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) - if data_model == "mathematical" - pmd_data = transform_data_model(pmd_data) + if data_model == "mathematical" + return transform_data_model(data_eng; make_pu=true) + else + return data_eng + end + elseif filetype == "json" + pmd_data = parse_json(io; validate=false) - correct_network_data!(pmd_data) + if get(pmd_data, "data_model", "mathematical") != data_model + return transform_data_model(pmd_data) + else + return pmd_data + end + else + Memento.error(_LOGGER, "only .dss and .json files are supported") end - - return pmd_data end "" -function parse_file(file::String; kwargs...) - pmd_data = open(file) do io - parse_file(io; filetype=split(lowercase(file), '.')[end], kwargs...) +function parse_file(file::String; kwargs...)::Dict{String,Any} + data = open(file) do io + parse_file(io, split(lowercase(file), '.')[end]; kwargs...) end - return pmd_data + return data end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model(data::Dict{<:Any,<:Any}; kron_reduced::Bool=true) +function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=false)::Dict{String,Any} current_data_model = get(data, "data_model", "mathematical") if current_data_model == "engineering" - out = _map_eng2math(data, kron_reduced=kron_reduced) + data_math = _map_eng2math(data; kron_reduced=kron_reduced) - bus_indexed_id = string(out["bus_lookup"][data["settings"]["set_vbase_bus"]]) - vbases = Dict(bus_indexed_id=>data["settings"]["set_vbase_val"]) - sbase = data["settings"]["set_sbase_val"] + correct_network_data!(data_math; make_pu=make_pu) - make_per_unit!(out, vbases=vbases, sbase=sbase, v_var_scalar=data["settings"]["v_var_scalar"]) - return out + return data_math elseif current_data_model == "mathematical" - return _map_math2eng!(data) + data_eng = _map_math2eng(data) + + correct_network_data!(data_eng; make_pu=make_pu) else - @warn "Data model '$current_data_model' is not recognized, no transformation performed" + @warn "Data model '$current_data_model' is not recognized, no model type transformation performed" return data end end "" -function correct_network_data!(data::Dict{String,Any}) - #_PMs.make_per_unit!(data) +function correct_network_data!(data::Dict{String,Any}; make_pu::Bool=true) + if get(data, "data_model", "mathematical") == "engineering" + check_eng_data_model(data) + if make_pu + make_per_unit!(data) + end + else + if make_pu + make_per_unit!(data) - _PMs.check_connectivity(data) - _PMs.correct_transformer_parameters!(data) - _PMs.correct_voltage_angle_differences!(data) - _PMs.correct_thermal_limits!(data) - _PMs.correct_branch_directions!(data) - _PMs.check_branch_loops(data) - _PMs.correct_bus_types!(data) - _PMs.correct_dcline_limits!(data) - _PMs.correct_cost_functions!(data) - _PMs.standardize_cost_terms!(data) + _PMs.check_connectivity(data) + _PMs.correct_transformer_parameters!(data) + _PMs.correct_voltage_angle_differences!(data) + _PMs.correct_thermal_limits!(data) + _PMs.correct_branch_directions!(data) + _PMs.check_branch_loops(data) + _PMs.correct_bus_types!(data) + _PMs.correct_dcline_limits!(data) + _PMs.correct_cost_functions!(data) + _PMs.standardize_cost_terms!(data) + end + end end diff --git a/test/opendss.jl b/test/opendss.jl index bcba9d3fe..d2bf31c53 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -74,9 +74,6 @@ @testset "opendss parse generic warnings and errors" begin Memento.setlevel!(TESTLOG, "info") - @test_warn(TESTLOG, "Not all OpenDSS features are supported, currently only minimal support for lines, loads, generators, and capacitors as shunts. Transformers and reactors as transformer branches are included, but value translation is not fully supported.", - PMD.parse_file("../test/data/opendss/test_simple.dss")) - @test_throws(TESTLOG, ErrorException, PMD.parse_file("../test/data/opendss/test_simple2.dss")) From 772d88300f99fcd2d47ead1eaa6aeee94a94166a Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 25 Mar 2020 15:35:58 -0600 Subject: [PATCH 106/224] ADD: select model type in Model Add ability to instantiate engineering or mathematical data models with Model --- src/data_model/components.jl | 47 ++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 6576134be..535d3e49e 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -13,7 +13,6 @@ function _add_unused_kwargs!(object::Dict{String,<:Any}, kwargs::Dict{Symbol,<:A end - "Generic add function to add components to an engineering data model" function add_object!(data_eng::Dict{String,<:Any}, obj_type::String, obj_id::Any, object::Dict{String,<:Any}) if !haskey(data_eng, obj_type) @@ -54,22 +53,44 @@ function add_object!(data_eng::Dict{String,<:Any}, obj_type::String, obj_id::Any end -"creates an engineering model" -function Model(kwargs...)::Dict{String,Any} +"Instantiates a PowerModelsDistribution data model" +function Model(model_type::String="engineering"; kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) - data_model = Dict{String,Any}( - "name" => get(kwargs, :name, ""), - "data_model" => "engineering", - "settings" => Dict{String,Any}( - "v_var_scalar" => get(kwargs, :v_var_scalar, 1e3), - "set_vbase_val" => get(kwargs, :basekv, 1.0), - "set_sbase_val" => get(kwargs, :baseMVA, 1.0), - "basefreq" => get(kwargs, :basefreq, 60.0), + if model_type == "engineering" + data_model = Dict{String,Any}( + "data_model" => "engineering", + "per_unit" => false, + "settings" => Dict{String,Any}( + "v_var_scalar" => get(kwargs, :v_var_scalar, 1e3), + "set_vbase_val" => get(kwargs, :basekv, 1.0), + "set_sbase_val" => get(kwargs, :baseMVA, 1.0), + "basefreq" => get(kwargs, :basefreq, 60.0), + ) + ) + + _add_unused_kwargs!(data_model["settings"], kwargs) + elseif model_type == "mathematical" + Memento.warn(_LOGGER, "There are not currently any helper functions to help build a mathematical model, this will only instantiate required fields.") + data_model = Dict{String,Any}( + "bus" => Dict{String,Any}(), + "load" => Dict{String,Any}(), + "shunt" => Dict{String,Any}(), + "gen" => Dict{String,Any}(), + "storage" => Dict{String,Any}(), + "branch" => Dict{String,Any}(), + "switch" => Dict{String,Any}(), + "dcline" => Dict{String,Any}(), + "per_unit" => false, + "baseMVA" => 100.0, + "basekv" => 1.0, + "data_model" => "mathematical" ) - ) - _add_unused_kwargs!(data_model["settings"], kwargs) + _add_unused_kwargs!(data_model, kwargs) + else + Memento.error(_LOGGER, "Model type '$model_type' not recognized") + end return data_model end From 34e1703cf7c22b4503851e86dbcd9edaedb44fa7 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 25 Mar 2020 16:10:08 -0600 Subject: [PATCH 107/224] ADD: lossless option to parser If `lossless`, internal impedances will be ignored for voltage_sources, generators, solar, storage, and switches --- src/data_model/eng2math.jl | 348 +++++++++++++++++++++---------------- src/io/common.jl | 10 +- 2 files changed, 202 insertions(+), 156 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 4bf9b12e0..983a2a34c 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -13,7 +13,7 @@ const _1to1_maps = Dict{String,Vector{String}}( "storage" => ["status", "source_id"], "line" => ["source_id"], "line_reactor" => ["source_id"], - "switch" => ["source_id"], + "switch" => ["source_id", "state", "status"], "line_reactor" => ["source_id"], "transformer" => ["source_id"], "voltage_source" => ["source_id"], @@ -43,7 +43,7 @@ const _edge_elements = ["line", "switch", "transformer", "line_reactor", "series "" -function _map_eng2math(data_eng; kron_reduced::Bool=true) +function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, use_dss_bounds::Bool=true) @assert get(data_eng, "data_model", "mathematical") == "engineering" data_math = Dict{String,Any}( @@ -75,18 +75,22 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) + if !use_dss_bounds + # _find_new_bounds(data_math) # TODO + end + return data_math end @@ -355,7 +359,7 @@ end "" -function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) +function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) math_obj = _init_math_obj("generator", eng_obj, length(data_math["gen"])+1) @@ -411,7 +415,7 @@ end "" -function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) +function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("solar", eng_obj, length(data_math["gen"])+1) @@ -460,7 +464,7 @@ end "" -function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) +function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("storage", eng_obj, length(data_math["storage"])+1) @@ -641,103 +645,123 @@ end "" -function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) +function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) # TODO enable real switches (right now only using vitual lines) for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] - # build virtual bus - f_bus = data_math["bus"]["$(data_math["bus_lookup"][eng_obj["f_bus"]])"] - - bus_obj = Dict{String,Any}( - "name" => "_virtual_bus.switch.$name", - "bus_i" => length(data_math["bus"])+1, - "bus_type" => 1, - "vmin" => f_bus["vmin"], - "vmax" => f_bus["vmax"], - "base_kv" => f_bus["base_kv"], - "status" => 1, - "index" => length(data_math["bus"])+1, - ) + if lossless + math_obj = _init_math_obj("switch", eng_obj, length(data_math["switch"])+1) + math_obj["name"] = name - # data_math["bus"]["$(bus_obj["index"])"] = bus_obj + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - # build virtual branch - if haskey(eng_obj, "linecode") - linecode = data_eng["linecode"][eng_obj["linecode"]] + data_math["switch"]["$(math_obj["index"])"] = math_obj - for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - if !haskey(eng_obj, property) && haskey(linecode, property) - eng_obj[property] = linecode[property] + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "switch.$(math_obj["index"])", + :unmap_function => :_map_math2eng_switch!, + :kron_reduced => kron_reduced, + :lossless => lossless, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["switch"]) + ) + else + # build virtual bus + f_bus = data_math["bus"]["$(data_math["bus_lookup"][eng_obj["f_bus"]])"] + + bus_obj = Dict{String,Any}( + "name" => "_virtual_bus.switch.$name", + "bus_i" => length(data_math["bus"])+1, + "bus_type" => 1, + "vmin" => f_bus["vmin"], + "vmax" => f_bus["vmax"], + "base_kv" => f_bus["base_kv"], + "status" => 1, + "index" => length(data_math["bus"])+1, + ) + + # data_math["bus"]["$(bus_obj["index"])"] = bus_obj + + # build virtual branch + if haskey(eng_obj, "linecode") + linecode = data_eng["linecode"][eng_obj["linecode"]] + + for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + if !haskey(eng_obj, property) && haskey(linecode, property) + eng_obj[property] = linecode[property] + end end end - end - branch_obj = _init_math_obj("switch", eng_obj, length(data_math["branch"])+1) - - Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) - Zbase = 1 - - _branch_obj = Dict{String,Any}( - "name" => "_virtual_branch.switch.$name", - "source_id" => "_virtual_branch.switch.$name", - # "f_bus" => bus_obj["bus_i"], # TODO enable real switches - "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], - "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, - "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, - "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9), - "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9), - "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9), - "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9), - "angmin" => fill(-60.0, nphases), - "angmax" => fill( 60.0, nphases), - "transformer" => false, - "shift" => zeros(nphases), - "tap" => ones(nphases), - "switch" => false, - "br_status" => 1, - ) + branch_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) + + Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) + Zbase = 1 + + _branch_obj = Dict{String,Any}( + "name" => "_virtual_branch.switch.$name", + "source_id" => "_virtual_branch.switch.$name", + # "f_bus" => bus_obj["bus_i"], # TODO enable real switches + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], + "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, + "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, + "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9), + "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9), + "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9), + "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9), + "angmin" => fill(-60.0, nphases), + "angmax" => fill( 60.0, nphases), + "transformer" => false, + "shift" => zeros(nphases), + "tap" => ones(nphases), + "switch" => false, + "br_status" => 1, + ) - merge!(branch_obj, _branch_obj) + merge!(branch_obj, _branch_obj) - if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") - filter = _kron_reduce_branch!(branch_obj, - ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], - eng_obj["f_connections"], kr_neutral - ) - _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) - connections = eng_obj["f_connections"][filter] - _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) - else - branch_obj["f_connections"] = eng_obj["f_connections"] - branch_obj["f_connections"] = eng_obj["t_connections"] - end + if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") + filter = _kron_reduce_branch!(branch_obj, + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], + eng_obj["f_connections"], kr_neutral + ) + _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) + connections = eng_obj["f_connections"][filter] + _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + else + branch_obj["f_connections"] = eng_obj["f_connections"] + branch_obj["f_connections"] = eng_obj["t_connections"] + end - data_math["branch"]["$(branch_obj["index"])"] = branch_obj + data_math["branch"]["$(branch_obj["index"])"] = branch_obj - # build switch - switch_obj = Dict{String,Any}( - "name" => name, - "source_id" => eng_obj["source_id"], - "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => bus_obj["bus_i"], - "status" => eng_obj["status"], - "index" => length(data_math["switch"])+1 - ) + # build switch + switch_obj = Dict{String,Any}( + "name" => name, + "source_id" => eng_obj["source_id"], + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => bus_obj["bus_i"], + "status" => eng_obj["status"], + "index" => length(data_math["switch"])+1 + ) - # data_math["switch"]["$(switch_obj["index"])"] = switch_obj + # data_math["switch"]["$(switch_obj["index"])"] = switch_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - # :to_id => ["switch.$(switch_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], # TODO enable real switches - :to => ["branch.$(branch_obj["index"])"], - :unmap_function => :_map_math2eng_switch!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["switch"]) - ) + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + # :to_id => ["switch.$(switch_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], # TODO enable real switches + :to => ["branch.$(branch_obj["index"])"], + :unmap_function => :_map_math2eng_switch!, + :kron_reduced => kron_reduced, + :lossless => lossless, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["switch"]) + ) + end end end @@ -831,73 +855,95 @@ end "" -function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) - # TODO create option for lossy vs lossless source connection +function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4, lossless::Bool=false) for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Any}()) nconductors = data_math["conductors"] - # TODO fix per unit problem - bus_obj = Dict{String,Any}( - "bus_i" => length(data_math["bus"])+1, - "index" => length(data_math["bus"])+1, - "name" => "_virtual_bus.voltage_source.$name", - "bus_type" => 3, - "vm" => eng_obj["vm"], - "va" => eng_obj["va"], - "vmin" => eng_obj["vm"], - "vmax" => eng_obj["vm"], - "basekv" => data_math["basekv"] - ) + if lossless + math_obj = _init_math_obj("voltage_source", eng_obj, length(data_math["gen"])+1) - data_math["bus"]["$(bus_obj["index"])"] = bus_obj - - gen_obj = Dict{String,Any}( - "gen_bus" => bus_obj["bus_i"], - "name" => "_virtual_gen.voltage_source.$name", - "gen_status" => 1, - "pg" => fill(0.0, nconductors), - "qg" => fill(0.0, nconductors), - "model" => 2, - "startup" => 0.0, - "shutdown" => 0.0, - "ncost" => 3, - "cost" => [0.0, 1.0, 0.0], - "configuration" => "wye", - "index" => length(data_math["gen"]) + 1, - "source_id" => "_virtual_gen.$(eng_obj["source_id"])" - ) + math_obj["name"] = name + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] + math_obj["pg"] = fill(0.0, nconductors) + math_obj["qg"] = fill(0.0, nconductors) + math_obj["configuration"] = "wye" - data_math["gen"]["$(gen_obj["index"])"] = gen_obj - - branch_obj = Dict{String,Any}( - "name" => "_virtual_branch.voltage_source.$name", - "source_id" => "_virtual_branch.$(eng_obj["source_id"])", - "f_bus" => bus_obj["bus_i"], - "t_bus" => data_math["bus_lookup"][eng_obj["bus"]], - "angmin" => fill(-60.0, nconductors), - "angmax" => fill( 60.0, nconductors), - "shift" => fill(0.0, nconductors), - "tap" => fill(1.0, nconductors), - "tranformer" => false, - "switch" => false, - "br_status" => 1, - "br_r" => eng_obj["rs"], - "br_x" => eng_obj["xs"], - "g_fr" => zeros(nconductors, nconductors), - "g_to" => zeros(nconductors, nconductors), - "b_fr" => zeros(nconductors, nconductors), - "b_to" => zeros(nconductors, nconductors), - "index" => length(data_math["branch"])+1 - ) + _add_gen_cost_model!(math_obj, eng_obj) - data_math["branch"]["$(branch_obj["index"])"] = branch_obj + data_math["gen"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], - :unmap_function => :_map_math2eng_voltage_source!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) - ) + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "gen.$(math_obj["index"])", + :unmap_function => :_map_math2eng_voltage_source!, + :kron_reduced => kron_reduced, + :lossless => lossless, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) + ) + else + bus_obj = Dict{String,Any}( + "bus_i" => length(data_math["bus"])+1, + "index" => length(data_math["bus"])+1, + "name" => "_virtual_bus.voltage_source.$name", + "bus_type" => 3, + "vm" => eng_obj["vm"], + "va" => eng_obj["va"], + "vmin" => eng_obj["vm"], + "vmax" => eng_obj["vm"], + "basekv" => data_math["basekv"] + ) + + data_math["bus"]["$(bus_obj["index"])"] = bus_obj + + gen_obj = Dict{String,Any}( + "gen_bus" => bus_obj["bus_i"], + "name" => "_virtual_gen.voltage_source.$name", + "gen_status" => eng_obj["status"], + "pg" => fill(0.0, nconductors), + "qg" => fill(0.0, nconductors), + "model" => 2, + "startup" => 0.0, + "shutdown" => 0.0, + "ncost" => 3, + "cost" => [0.0, 1.0, 0.0], + "configuration" => "wye", + "index" => length(data_math["gen"]) + 1, + "source_id" => "_virtual_gen.$(eng_obj["source_id"])" + ) + + data_math["gen"]["$(gen_obj["index"])"] = gen_obj + + branch_obj = Dict{String,Any}( + "name" => "_virtual_branch.voltage_source.$name", + "source_id" => "_virtual_branch.$(eng_obj["source_id"])", + "f_bus" => bus_obj["bus_i"], + "t_bus" => data_math["bus_lookup"][eng_obj["bus"]], + "angmin" => fill(-60.0, nconductors), + "angmax" => fill( 60.0, nconductors), + "shift" => fill(0.0, nconductors), + "tap" => fill(1.0, nconductors), + "tranformer" => false, + "switch" => false, + "br_status" => 1, + "br_r" => eng_obj["rs"], + "br_x" => eng_obj["xs"], + "g_fr" => zeros(nconductors, nconductors), + "g_to" => zeros(nconductors, nconductors), + "b_fr" => zeros(nconductors, nconductors), + "b_to" => zeros(nconductors, nconductors), + "index" => length(data_math["branch"])+1 + ) + + data_math["branch"]["$(branch_obj["index"])"] = branch_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], + :unmap_function => :_map_math2eng_voltage_source!, + :kron_reduced => kron_reduced, + :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) + ) + end end end diff --git a/src/io/common.jl b/src/io/common.jl index 552636048..601e57faa 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -3,12 +3,12 @@ Parses the IOStream of a file into a Three-Phase PowerModels data structure. """ -function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="mathematical", import_all::Bool=false, bank_transformers::Bool=true, lossless::Bool=false)::Dict{String,Any} +function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="mathematical", import_all::Bool=false, bank_transformers::Bool=true, lossless::Bool=false, use_dss_bounds::Bool=true)::Dict{String,Any} if filetype == "dss" data_eng = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) if data_model == "mathematical" - return transform_data_model(data_eng; make_pu=true) + return transform_data_model(data_eng; make_pu=true, lossless=lossless, use_dss_bounds=use_dss_bounds) else return data_eng end @@ -16,7 +16,7 @@ function parse_file(io::IO, filetype::AbstractString="json"; data_model::String= pmd_data = parse_json(io; validate=false) if get(pmd_data, "data_model", "mathematical") != data_model - return transform_data_model(pmd_data) + return transform_data_model(pmd_data; lossless=lossless, use_dss_bounds=use_dss_bounds) else return pmd_data end @@ -37,11 +37,11 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=false)::Dict{String,Any} +function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=false, lossless::Bool=false, use_dss_bounds::Bool=true)::Dict{String,Any} current_data_model = get(data, "data_model", "mathematical") if current_data_model == "engineering" - data_math = _map_eng2math(data; kron_reduced=kron_reduced) + data_math = _map_eng2math(data; kron_reduced=kron_reduced, lossless=lossless, use_dss_bounds=use_dss_bounds) correct_network_data!(data_math; make_pu=make_pu) From 5a9c2d57037ea65d793f502b3fb647f021caa3f2 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 25 Mar 2020 16:10:21 -0600 Subject: [PATCH 108/224] RM: unused function constraint_mc_transformer_flow --- src/form/acp.jl | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/form/acp.jl b/src/form/acp.jl index aa40566e6..bd61d9cb5 100644 --- a/src/form/acp.jl +++ b/src/form/acp.jl @@ -568,15 +568,6 @@ function constraint_mc_vm_neg_seq(pm::_PMs.AbstractACPModel, nw::Int, bus_id::In end -#= TODO unused function, remove? -"Links the power flowing into both windings of a variable tap transformer." -function constraint_mc_transformer_flow_var(pm::_PMs.AbstractPowerModel, i::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, Ti_fr, Ti_im; nw::Int=pm.cnw) - # for ac formulation, indentical to fixed tap - constraint_mc_transformer_flow(pm, i, f_bus, t_bus, f_idx, t_idx, Ti_fr, Ti_im) -end -=# - - """ a = exp(im*2π/3) U+ = (1*Ua + a*Ub a^2*Uc)/3 From 5627e1030aead254d92d64466a7db98b88ad32d5 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 26 Mar 2020 10:14:08 -0600 Subject: [PATCH 109/224] ADD: support non-standard dss parameters Adds support for parameters specified in the dss file that are not explicitly specified in the DSS manual Adds function to guess the data type of values we don't explicitly support. Will detect if Int, Float, or Complex Adds unit test Closes #242 --- src/io/dss_structs.jl | 30 +++++++--- src/io/utils.jl | 94 ++++++++++++++++++------------ test/data/opendss/test2_master.dss | 2 +- test/opendss.jl | 2 + 4 files changed, 81 insertions(+), 47 deletions(-) diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index 18cfe79f0..983898d9b 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -103,7 +103,8 @@ function _create_linecode(name::AbstractString=""; kwargs...)::Dict{String,Any} "rho" => get(kwargs, :rho, 100.0), "neutral" => get(kwargs, :neutral, 3), "b1" => b1 / _convert_to_meters[units], - "b0" => b0 / _convert_to_meters[units] + "b0" => b0 / _convert_to_meters[units], + "like" => get(kwargs, :like, "") ) end @@ -229,6 +230,7 @@ function _create_line(bus1="", bus2="", name::AbstractString=""; kwargs...)::Dic "repair" => get(kwargs, :repair, 3.0), "basefreq" => basefreq, "enabled" => get(kwargs, :enabled, true), + "like" => get(kwargs, :like, "") ) end @@ -311,7 +313,8 @@ function _create_load(bus1="", name::AbstractString=""; kwargs...)::Dict{String, # Inherited Properties "spectrum" => get(kwargs, :spectrum, "defaultload"), "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) + "enabled" => get(kwargs, :enabled, true), + "like" => get(kwargs, :like, "") ) end @@ -375,7 +378,8 @@ function _create_generator(bus1="", name::AbstractString=""; kwargs...)::Dict{St # Inherited Properties "spectrum" => get(kwargs, :spectrum, "defaultgen"), "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) + "enabled" => get(kwargs, :enabled, true), + "like" => get(kwargs, :like, "") ) end @@ -413,7 +417,8 @@ function _create_capacitor(bus1="", name::AbstractString=""; kwargs...)::Dict{St "faultrate" => get(kwargs, :faultrate, 0.1), "pctperm" => get(kwargs, :pctperm, 20.0), "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) + "enabled" => get(kwargs, :enabled, true), + "like" => get(kwargs, :like, "") ) end @@ -556,7 +561,8 @@ function _create_reactor(bus1="", name::AbstractString="", bus2=""; kwargs...):: "faultrate" => get(kwargs, :faultrate, 0.1), "pctperm" => get(kwargs, :pctperm, 20.0), "basefreq" => basefreq, - "enabled" => get(kwargs, :enabled, true) + "enabled" => get(kwargs, :enabled, true), + "like" => get(kwargs, :like, "") ) end @@ -766,7 +772,8 @@ function _create_vsource(bus1="", name::AbstractString="", bus2=0; kwargs...)::D # Derived Properties "rmatrix" => real(Z), "xmatrix" => imag(Z), - "vmag" => Vmag + "vmag" => Vmag, + "like" => get(kwargs, :like, "") ) end @@ -876,7 +883,8 @@ function _create_transformer(name::AbstractString=""; kwargs...) # Inherited Properties "faultrate" => get(kwargs, :faultrate, 0.1), "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) + "enabled" => get(kwargs, :enabled, true), + "like" => get(kwargs, :like, "") ) if windings == 3 @@ -991,7 +999,8 @@ function _create_xfmrcode(name::AbstractString=""; kwargs...) # Inherited Properties "faultrate" => get(kwargs, :faultrate, 0.1), "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) + "enabled" => get(kwargs, :enabled, true), + "like" => get(kwargs, :like, "") ) if windings == 3 @@ -1085,7 +1094,8 @@ function _create_pvsystem(bus1="", name::AbstractString=""; kwargs...) "spectrum" => get(kwargs, :spectrum, "defaultpvsystem"), # Inherited Properties "basefreq" => get(kwargs, :basefreq, 60.0), - "enabled" => get(kwargs, :enabled, true) + "enabled" => get(kwargs, :enabled, true), + "like" => get(kwargs, :like, "") ) end @@ -1141,6 +1151,7 @@ function _create_storage(bus1="", name::AbstractString=""; kwargs...) "vmaxpu" => get(kwargs, :vmaxpu, 1.1), "vminpu" => get(kwargs, :vimpu, 0.9), "yearly" => get(kwargs, :yearly, [1.0, 1.0]), + "like" => get(kwargs, :like, "") ) end @@ -1186,6 +1197,7 @@ function _create_loadshape(name::AbstractString=""; kwargs...) "pmax" => get(kwargs, :pmax, 1.0), "qmax" => get(kwargs, :qmax, 1.0), "pbase" => get(kwargs, :pbase, 0.0), + "like" => get(kwargs, :like, "") ) end diff --git a/src/io/utils.jl b/src/io/utils.jl index 3820a06bc..d21175b80 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -1,3 +1,6 @@ +import Base.Iterators: flatten + + "all edge types that can help define buses" const _dss_edge_components = Vector{String}(["line", "transformer", "reactor", "capacitor"]) @@ -45,11 +48,11 @@ const _like_exclusions = Dict{String,Vector{String}}( "linegeometry" => ["nconds"] ) -"data types of various dss option inputs" -const _dss_option_dtypes = Dict{String,Type}( - "defaultbasefreq" => Float64, - "voltagebases" => Float64, - "tolerance" => Float64 +"" +const _dtype_regex = Dict{Regex, Type}( + r"^[+-]{0,1}\d*\.{0,1}\d*[eE]{0,1}[+-]{0,1}\d*[+-]\d*\.{0,1}\d*[eE]{0,1}[+-]{0,1}\d*[ij]$" => ComplexF64, + r"^[+-]{0,1}\d*\.{0,1}\d*[eE]{0,1}[+-]{0,1}\d*$" => Float64, + r"^\d+$" => Int, ) @@ -556,7 +559,9 @@ function _apply_ordered_properties(defaults::Dict{String,<:Any}, raw_dss::Dict{S if prop in ["linecode", "loadshape"] merge!(defaults, code_dict) else - defaults[prop] = _defaults[prop] + if haskey(_defaults, prop) + defaults[prop] = _defaults[prop] + end end end @@ -613,25 +618,6 @@ function _apply_like!(raw_dss::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, end -"Parses options defined with the `set` command in OpenDSS" -function parse_dss_options!(data_dss::Dict{String,<:Any}) - if haskey(data_dss, "options") - for (k,v) in data_dss["options"] - if haskey(_dss_option_dtypes, k) - dtype = _dss_option_dtypes[k] - if _isa_array(v) - data_dss["options"][k] = _parse_array(dtype, v) - elseif _isa_matrix(v) - data_dss["options"][k] = _parse_matrix(dtype, v) - else - data_dss["options"][k] = parse(dtype, v) - end - end - end - end -end - - """ parse_dss_with_dtypes!(data_dss, to_parse) @@ -642,7 +628,7 @@ function parse_dss_with_dtypes!(data_dss::Dict{String,<:Any}, to_parse::Vector{S for obj_type in to_parse if haskey(data_dss, obj_type) dtypes = _dss_parameter_data_types[obj_type] - if obj_type in ["circuit", "options"] + if obj_type == "options" _parse_obj_dtypes!(obj_type, data_dss[obj_type], dtypes) else for object in values(data_dss[obj_type]) @@ -693,20 +679,24 @@ end "" function _parse_obj_dtypes!(obj_type::String, object::Dict{String,Any}, dtypes::Dict{String,Type}) for (k, v) in object - if haskey(dtypes, k) - if isa(v, Array) - arrout = [] - for el in v - if isa(v, AbstractString) - push!(arrout, _parse_element_with_dtype(dtypes[k], el)) - else - push!(arrout, el) + if isa(v, Vector) && eltype(v) == Any || isa(eltype(v), AbstractString) + _dtype = get(dtypes, k, _guess_dtype("[$(join(v, ","))]")) + for i in 1:length(v) + if isa(v[i], AbstractString) + v[i] = _parse_element_with_dtype(_dtype, v[i]) + end + end + elseif isa(v, Matrix) && eltype(v) == Any || isa(eltype(v), AbstractString) + _dtype = get(dtypes, k, _guess_dtype("$(join(collect(flatten(v)), " "))")) + for i in 1:size(v)[1] + for j in 1:size(v)[2] + if isa(v[i,j], AbstractString) + v[i,j] = _parse_element_with_dtype(_dtype, v[i,j]) end end - object[k] = arrout - elseif isa(v, AbstractString) - object[k] = _parse_element_with_dtype(dtypes[k], v) end + elseif isa(v, AbstractString) + object[k] = _parse_element_with_dtype(get(dtypes, k, _guess_dtype(v)), v) end end end @@ -798,3 +788,33 @@ function _add_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, eng_o data_eng[eng_obj_type][eng_obj_id] = eng_obj end + + +"guesses the data type of a value using regex, returning Float64, Int, ComplexF64, or String (if number type cannot be determined)" +function _guess_dtype(value::AbstractString)::Type + if _isa_matrix(value) || _isa_array(value) || _isa_rpn(value) + for delim in [keys(_double_operators)..., keys(_single_operators)..., _array_delimiters..., "|", ","] + value = replace(value, delim => " ") + end + _dtypes = unique([_guess_dtype(v) for v in split(value)]) + if length(_dtypes) == 1 + return _dtypes[1] + elseif all(isa(v, Int) for v in _dtypes) + return Int + elseif any(isa(v, Complex) for v in _dtypes) + return ComplexF64 + elseif any(isa(v, Float64) for v in _dtypes) + return Float64 + else + return String + end + else + for (re, typ) in _dtype_regex + if occursin(re, value) + return typ + end + end + + return String + end +end diff --git a/test/data/opendss/test2_master.dss b/test/data/opendss/test2_master.dss index 445ab0ce2..1c519a72f 100644 --- a/test/data/opendss/test2_master.dss +++ b/test/data/opendss/test2_master.dss @@ -17,7 +17,7 @@ New "Line.L3" phases=3 bus1=b7.1.2.3 bus2=b9.1.2.3 linecode=300 normamps=400 eme New "Line._L4" phases=3 bus1=b8.1.2.3 bus2=b10.1.2.3 linecode=300 normamps=400 emergamps=600 faultrate=0.1 pctperm=20 repair=0 length=1.73 switch=y New line.l5 phases=3 bus1=_b2.1.2.3 bus2=b7.1.2.3 linecode=lc8 New line.l6 phases=3 bus1=b1.1.2.3 bus2=b10.1.2.3 linecode=lc9 -new line.l7 bus1=_b2 bus2=b10 like=L2 linecode=lc10 +new line.l7 bus1=_b2 bus2=b10 like=L2 linecode=lc10 test_param=100.0 ! Loads New Load.ld1 phases=1 Bus1=b7.1.0 kv=0.208 status=variable model=1 conn=wye kW=3.89 pf=0.97 Vminpu=.88 diff --git a/test/opendss.jl b/test/opendss.jl index d2bf31c53..471a0b63e 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -119,6 +119,8 @@ @testset "opendss parse generic parser verification" begin dss = PMD.parse_dss("../test/data/opendss/test2_master.dss") + @test dss["line"]["l7"]["test_param"] == 100.0 + @test pmd["name"] == "test2" @test length(pmd) == 19 From 2958e9f67ca477a7e11d5e31540c39315799f862 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 26 Mar 2020 12:54:47 -0600 Subject: [PATCH 110/224] ADD: xycurve support Adds support for xycurve objects, which are needed by pvsystem to specify the p-tcurve. In OpenDSS, interpolation is used for XYCurve objects, so Dierckx.jl is added as a dependency for Spline1D Adds unit tests, and test2_master.dss updated with xycurve objects UPD: solar and storage values to multiphase at eng model level Changes to have the multiphase values be distributed at the engineering model level instead of at eng2math call. Updates limit values for solar (qmin/qmax should use minkvar instead of minkva). This changes the qg of a pv unit test, which needed to be updated. --- Project.toml | 2 ++ src/PowerModelsDistribution.jl | 2 ++ src/data_model/eng2math.jl | 32 ++++++++--------- src/io/dss_parse.jl | 57 ++++++++++++++++++++++-------- src/io/dss_structs.jl | 54 ++++++++++++++++++++++++++++ src/io/opendss.jl | 56 ++++++++++++++++++++++------- src/io/utils.jl | 2 +- test/data/opendss/test2_master.dss | 5 +++ test/opendss.jl | 9 ++++- test/opf.jl | 5 ++- 10 files changed, 174 insertions(+), 50 deletions(-) diff --git a/Project.toml b/Project.toml index 12774c703..72136d3a6 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ repo = "https://github.com/lanl-ansi/PowerModelsDistribution.jl.git" version = "0.8.1" [deps] +Dierckx = "39dd38d3-220a-591b-8e3c-4c3a8c710a94" InfrastructureModels = "2030c09a-7f63-5d83-885d-db604e0e9cc0" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" @@ -16,6 +17,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] Cbc = ">= 0.4" +Dierckx = "~0.4" InfrastructureModels = "~0.4" Ipopt = ">= 0.4" JSON = "~0.18, ~0.19, ~0.20, ~0.21" diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 205e9d34e..3fa2cabfa 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -7,6 +7,8 @@ module PowerModelsDistribution import InfrastructureModels import Memento + import Dierckx: Spline1D + import LinearAlgebra const _PMs = PowerModels diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 983a2a34c..e9d530e5a 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -419,7 +419,6 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("solar", eng_obj, length(data_math["gen"])+1) - phases = eng_obj["phases"] connections = eng_obj["connections"] nconductors = data_math["conductors"] @@ -428,14 +427,14 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An math_obj["gen_status"] = eng_obj["status"] math_obj["pg"] = eng_obj["kva"] - math_obj["qg"] = eng_obj["kva"] + math_obj["qg"] = eng_obj["kvar"] math_obj["vg"] = eng_obj["kv"] - math_obj["pmin"] = fill(0.0, phases) - math_obj["pmax"] = eng_obj["kva"] + math_obj["pmin"] = get(eng_obj, "minkva", zeros(size(eng_obj["kva"]))) + math_obj["pmax"] = get(eng_obj, "maxkva", eng_obj["kva"]) - math_obj["qmin"] = -eng_obj["kva"] - math_obj["qmax"] = eng_obj["kva"] + math_obj["qmin"] = get(eng_obj, "minkvar", -eng_obj["kvar"]) + math_obj["qmax"] = get(eng_obj, "maxkvar", eng_obj["kvar"]) _add_gen_cost_model!(math_obj, eng_obj) @@ -468,7 +467,6 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("storage", eng_obj, length(data_math["storage"])+1) - phases = eng_obj["phases"] connections = eng_obj["connections"] nconductors = data_math["conductors"] @@ -481,16 +479,16 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: math_obj["discharge_rating"] = eng_obj["%discharge"] * eng_obj["kwrated"] / 1e3 / 100.0 math_obj["charge_efficiency"] = eng_obj["%effcharge"] / 100.0 math_obj["discharge_efficiency"] = eng_obj["%effdischarge"] / 100.0 - math_obj["thermal_rating"] = fill(eng_obj["kva"] / 1e3 / phases, phases) - math_obj["qmin"] = fill(-eng_obj["kvar"] / 1e3 / phases, phases) - math_obj["qmax"] = fill( eng_obj["kvar"] / 1e3 / phases, phases) - math_obj["r"] = fill(eng_obj["%r"] / 100.0, phases) - math_obj["x"] = fill(eng_obj["%x"] / 100.0, phases) - math_obj["p_loss"] = eng_obj["%idlingkw"] * eng_obj["kwrated"] / 1e3 - math_obj["q_loss"] = eng_obj["%idlingkvar"] * eng_obj["kvar"] / 1e3 - - math_obj["ps"] = fill(0.0, phases) - math_obj["qs"] = fill(0.0, phases) + math_obj["thermal_rating"] = eng_obj["kva"] ./ 1e3 + math_obj["qmin"] = -eng_obj["kvar"] ./ 1e3 + math_obj["qmax"] = eng_obj["kvar"] ./ 1e3 + math_obj["r"] = eng_obj["%r"] ./ 100.0 + math_obj["x"] = eng_obj["%x"] ./ 100.0 + math_obj["p_loss"] = eng_obj["%idlingkw"] .* eng_obj["kwrated"] ./ 1e3 + math_obj["q_loss"] = eng_obj["%idlingkvar"] * sum(eng_obj["kvar"]) / 1e3 + + math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["kva"]))) + math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["kva"]))) if kron_reduced _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, kr_phases) diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index bc11496cb..6d1e5649d 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -39,6 +39,11 @@ const _loadshape_properties = Vector{String}([ "pmax", "qmax", "pbase", "like" ]) +const _xycurve_properties = Vector{String}([ + "npts", "points", "yarray", "xarray", "csvfile", "sngfile", "dblfile", + "x", "y", "xshift", "yshift", "xscale", "yscale", "like" +]) + const _growthshape_properties = Vector{String}([ "npts", "year", "mult", "csvfile", "sngfile", "dblfile", "like" ]) @@ -228,6 +233,7 @@ const _dss_object_properties = Dict{String,Vector{String}}( "linegeometry" => _linegeometry_properties, "linespacing" => _linespacing_properties, "loadshape" => _loadshape_properties, + "xycurve" => _xycurve_properties, "growthshape" => _growthshape_properties, "tcc_curve" => _tcc_curve_properties, "cndata" => _cndata_properties, @@ -260,7 +266,7 @@ const _dss_object_properties = Dict{String,Vector{String}}( "parses single column load profile files" -function _parse_loadshape_csv_file(path::AbstractString, type::AbstractString; header::Bool=false, column::Int=1, interval::Bool=false) +function _parse_csv_file(path::AbstractString, type::AbstractString; header::Bool=false, column::Int=1, interval::Bool=false) open(path, "r") do f lines = readlines(f) if header @@ -320,8 +326,6 @@ function _parse_binary_file(path::AbstractString, precision::Type; npts::Union{I break end end - - return data else data = Array{precision, 1}(undef, interval ? npts * 2 : npts) @@ -330,22 +334,22 @@ function _parse_binary_file(path::AbstractString, precision::Type; npts::Union{I catch EOFError error("Error reading binary file: likely npts is wrong") end + end - if interval - data = reshape(data, 2, :) - return data[1, :], data[2, :] - else - return data - end + if interval + data = reshape(data, 2, :) + return data[1, :], data[2, :] + else + return data end end end "parses csv or binary loadshape files" -function _parse_loadshape_file(path::AbstractString, type::AbstractString, npts::Union{Int,Nothing}; header::Bool=false, interval::Bool=false, column::Int=1) +function _parse_data_file(path::AbstractString, type::AbstractString, npts::Union{Int,Nothing}; header::Bool=false, interval::Bool=false, column::Int=1) if type in ["csvfile", "mult", "pqcsvfile"] - return _parse_loadshape_csv_file(path, type; header=header, column=column, interval=interval) + return _parse_csv_file(path, type; header=header, column=column, interval=interval) elseif type in ["sngfile", "dblfile"] return _parse_binary_file(path, Dict("sngfile" => Float32, "dblfile" => Float64)[type]; npts=npts, interval=interval) end @@ -371,13 +375,13 @@ function _parse_mult_parameter(mult_string::AbstractString; path::AbstractString full_path = path == "" ? props[file_key] : join([path, props[file_key]], '/') type = file_key == "file" ? "mult" : file_key - return "($(join(_parse_loadshape_file(full_path, type, npts; header=get(props, "header", false), column=parse(Int, get(props, "column", "1"))), ",")))" + return "($(join(_parse_data_file(full_path, type, npts; header=get(props, "header", false), column=parse(Int, get(props, "column", "1"))), ",")))" end end "parses loadshape component" -function _parse_loadshape!(current_obj::Dict{String,Any}; path::AbstractString="") +function _parse_loadshape!(current_obj::Dict{String,<:Any}; path::AbstractString="") if any(parse.(Float64, [get(current_obj, "interval", "1.0"), get(current_obj, "minterval", "60.0"), get(current_obj, "sinterval", "3600.0")]) .<= 0.0) interval = true else @@ -391,7 +395,7 @@ function _parse_loadshape!(current_obj::Dict{String,Any}; path::AbstractString=" current_obj[prop] = _parse_mult_parameter(current_obj[prop]; path=path, npts=npts) elseif prop in ["csvfile", "pqcsvfile", "sngfile", "dblfile"] full_path = path == "" ? current_obj[prop] : join([path, current_obj[prop]], '/') - data = _parse_loadshape_file(full_path, prop, parse(Int, get(current_obj, "npts", "1")); interval=interval, header=false) + data = _parse_data_file(full_path, prop, parse(Int, get(current_obj, "npts", "1")); interval=interval, header=false) if prop == "pqcsvfile" if interval current_obj["hour"], current_obj["pmult"], current_obj["qmult"] = data @@ -418,6 +422,27 @@ function _parse_loadshape!(current_obj::Dict{String,Any}; path::AbstractString=" end +"parse xycurve component" +function _parse_xycurve!(current_obj::Dict{String,<:Any}; path::AbstractString="") + for prop in current_obj["prop_order"] + if prop in ["csvfile", "sngfile", "dblfile"] + full_path = isempty(path) ? current_obj[prop] : join([path, current_obj[prop]], '/') + data = _parse_data_file(full_path, prop, nothing; interval=true, header=false) + current_obj["xarray"], current_obj["yarray"] = data + end + + end + + for prop in ["xarray", "yarray", "points"] + if haskey(current_obj, prop) && isa(current_obj[prop], Array) + current_obj[prop] = "($(join(current_obj[prop], ",")))" + elseif haskey(current_obj, prop) && isa(current_obj[prop], String) && !_isa_array(current_obj[prop]) + current_obj[prop] = "($(current_obj[prop]))" + end + end +end + + "strips lines that are either commented (block or single) or empty" function _strip_lines(lines::Array)::Array blockComment = false @@ -805,7 +830,7 @@ function parse_dss(io::IOStream)::Dict{String,Any} continue elseif cmd in ["disable", "enable"] - current_obj_type, current_obj_name = split(join(line_element[2:end], ""), ".") + current_obj_type, current_obj_name = split(join(line_elements[2:end], ""), ".") enabled = cmd == "enable" ? "true" : "false" @@ -823,6 +848,8 @@ function parse_dss(io::IOStream)::Dict{String,Any} if startswith(current_obj_type, "loadshape") _parse_loadshape!(current_obj; path=path) + elseif startswith(current_obj_type, "xycurve") + _parse_xycurve!(current_obj; path=path) end if startswith(current_obj_type, "circuit") diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index 983898d9b..ff3ef43b9 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -1202,6 +1202,60 @@ function _create_loadshape(name::AbstractString=""; kwargs...) end +""" +Creates a Dict{String,Any} containing all expected properties for a XYCurve +object. See OpenDSS documentation for valid fields and ways to specify +different properties. +""" +function _create_xycurve(name::AbstractString=""; kwargs...) + kwargs = Dict{Symbol,Any}(kwargs) + + if haskey(kwargs, :points) + xarray = Vector{Float64}([]) + yarray = Vector{Float64}([]) + + i = 1 + for point in kwargs[:points] + if i % 2 == 1 + push!(xarray, point) + else + push!(yarray, point) + end + i += 1 + end + else + xarray = get(kwargs, :xarray, Vector{Float64}([])) + yarray = get(kwargs, :yarray, Vector{Float64}([])) + end + + npts = min(length(xarray), length(yarray)) + + points = Vector{Float64}([]) + for (x, y) in zip(xarray, yarray) + push!(points, x) + push!(points, y) + end + + Dict{String,Any}( + "name" => name, + "npts" => npts, + "points" => points, + "yarray" => yarray, + "xarray" => xarray, + "csvfile" => get(kwargs, :csvfile, ""), + "sngfile" => get(kwargs, :sngfile, ""), + "dblfile" => get(kwargs, :dblfile, ""), + "x" => get(kwargs, :x, isempty(xarray) ? 0.0 : xarray[1]), + "y" => get(kwargs, :y, isempty(yarray) ? 0.0 : yarray[1]), + "xshift" => get(kwargs, :xshift, 0), + "yshift" => get(kwargs, :yshift, 0), + "xscale" => get(kwargs, :xscale, 1.0), + "yscale" => get(kwargs, :yscale, 1.0), + "like" => get(kwargs, :like, ""), + ) +end + + "" function _create_options(; kwargs...) kwargs = Dict{Symbol,Any}(kwargs) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index c2ff0ef94..fee0cc57e 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -42,7 +42,8 @@ function _dss2eng_loadshape!(data_eng::Dict{String,<:Any}, data_dss::Dict{String "hour" => defaults["hour"], "pmult" => defaults["pmult"], "qmult" => defaults["qmult"], - "use_actual" => defaults["useactual"] + "use_actual" => defaults["useactual"], + "source_id" => "loadshape.$name", ) if import_all @@ -54,6 +55,33 @@ function _dss2eng_loadshape!(data_eng::Dict{String,<:Any}, data_dss::Dict{String end +"Adds xycurve objects to `data_eng` from `data_dss`" +function _dss2eng_xycurve!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) + for (name, dss_obj) in get(data_dss, "xycurve", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "xycurve") + defaults = _apply_ordered_properties(_create_xycurve(name; _to_kwargs(dss_obj)...), dss_obj) + + xarray = defaults["xarray"] .* defaults["xscale"] .+ defaults["xshift"] + yarray = defaults["yarray"] .* defaults["yscale"] .+ defaults["yshift"] + + @assert length(xarray) >= 2 && length(yarray) >= 2 "XYCurve data must have two or more points" + + k = length(xarray) - 1 + + eng_obj = Dict{String,Any}( + "interpolated_curve" => Spline1D(xarray, yarray; k=min(k, 5)), + "source_id" => "xycurve.$name", + ) + + if import_all + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + end + + _add_eng_obj!(data_eng, "xycurve", name, eng_obj) + end +end + + """ Adds loads to `data_eng` from `data_dss` @@ -203,8 +231,8 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String eng_obj["f_bus"] = bus_name eng_obj["t_bus"] = bus2_name - eng_obj["kv"] = fill(defaults["kv"] / nphases, phases) - eng_obj["kvar"] = fill(defaults["kvar"] / nphases, phases) + eng_obj["kv"] = fill(defaults["kv"] / nphases, nphases) + eng_obj["kvar"] = fill(defaults["kvar"] / nphases, nphases) _add_eng_obj!(data_eng, "series_capacitor", name, eng_obj) end @@ -574,11 +602,13 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj = Dict{String,Any}( "bus" => _parse_busname(defaults["bus1"])[1], - "phases" => nphases, "configuration" => "wye", "connections" => _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)), "kva" => fill(defaults["kva"] / nphases, nphases), + "kvar" => fill(defaults["kvar"] / nphases, nphases), "kv" => fill(defaults["kv"] / nphases, nphases), + "irradiance" => defaults["irradiance"], + "p-tcurve" => defaults["p-tcurve"], "status" => convert(Int, defaults["enabled"]), "source_id" => "pvsystem.$name", ) @@ -605,7 +635,7 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< eng_obj = Dict{String,Any}() - eng_obj["phases"] = defaults["phases"] + nphases = defaults["phases"] eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], check_length=false) eng_obj["name"] = name @@ -624,10 +654,10 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< eng_obj["%discharge"] = defaults["%discharge"] eng_obj["%effcharge"] = defaults["%effcharge"] eng_obj["%effdischarge"] = defaults["%effdischarge"] - eng_obj["kva"] = defaults["kva"] - eng_obj["kvar"] = defaults["kvar"] - eng_obj["%r"] = defaults["%r"] - eng_obj["%x"] = defaults["%x"] + eng_obj["kva"] = fill(defaults["kva"] / nphases, nphases) + eng_obj["kvar"] = fill(defaults["kvar"] / nphases, nphases) + eng_obj["%r"] = fill(defaults["%r"] / nphases, nphases) + eng_obj["%x"] = fill(defaults["%x"] / nphases, nphases) eng_obj["%idlingkw"] = defaults["%idlingkw"] eng_obj["%idlingkvar"] = defaults["%idlingkvar"] @@ -673,12 +703,10 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban data_eng["data_model"] = "engineering" # TODO rename fields - # TODO fix scale factors data_eng["settings"]["v_var_scalar"] = 1e3 - # data_eng["settings"]["set_vbase_val"] = defaults["basekv"] / sqrt(3) * 1e3 - data_eng["settings"]["set_vbase_val"] = defaults["basekv"]/sqrt(3) + data_eng["settings"]["set_vbase_val"] = defaults["basekv"] / sqrt(3) data_eng["settings"]["set_vbase_bus"] = source_bus - data_eng["settings"]["set_sbase_val"] = defaults["basemva"]*1E3 + data_eng["settings"]["set_sbase_val"] = defaults["basemva"] * 1e3 data_eng["settings"]["basefreq"] = get(get(data_dss, "options", Dict{String,Any}()), "defaultbasefreq", 60.0) data_eng["files"] = data_dss["filename"] @@ -701,6 +729,8 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban _dss2eng_loadshape!(data_eng, data_dss, import_all) _dss2eng_load!(data_eng, data_dss, import_all) + _dss2eng_xycurve!(data_eng, data_dss, import_all) + _dss2eng_vsource!(data_eng, data_dss, import_all) _dss2eng_generator!(data_eng, data_dss, import_all) _dss2eng_pvsystem!(data_eng, data_dss, import_all) diff --git a/src/io/utils.jl b/src/io/utils.jl index d21175b80..cd4ddd517 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -8,7 +8,7 @@ const _dss_edge_components = Vector{String}(["line", "transformer", "reactor", " const _dss_supported_components = Vector{String}([ "line", "linecode", "load", "generator", "capacitor", "reactor", "transformer", "pvsystem", "storage", "loadshape", "options", - "xfmrcode", "vsource", + "xfmrcode", "vsource", "xycurve" ]) "two number operators for reverse polish notation" diff --git a/test/data/opendss/test2_master.dss b/test/data/opendss/test2_master.dss index 1c519a72f..bd5deb2c2 100644 --- a/test/data/opendss/test2_master.dss +++ b/test/data/opendss/test2_master.dss @@ -70,3 +70,8 @@ Solve show taps new Line.l8 bus1=b10 bus2=b11 linecode=lc/2 units=mi length=(0.013516796 1 1.6093 / *) + +new xycurve.test_curve1 xarray=(file=load_profile.csv) yarray=(sngfile=load_profile.sng) +new xycurve.test_curve2 points=[1 1 2 2 3 3] +new xycurve.test_curve3 csvfile=load_profile_pq.csv +new xycurve.test_curve4 dblfile=load_profile_interval.dbl diff --git a/test/opendss.jl b/test/opendss.jl index 471a0b63e..09891e9c3 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -124,7 +124,7 @@ @test pmd["name"] == "test2" @test length(pmd) == 19 - @test length(dss) == 14 + @test length(dss) == 15 for (key, len) in zip(["bus", "load", "shunt", "branch", "gen", "dcline", "transformer"], [33, 4, 5, 27, 4, 0, 10]) @test haskey(pmd, key) @@ -187,6 +187,13 @@ @test all(isapprox.(pmd["branch"]["4"]["b_fr"], diag(basekv_br4^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 * cmatrix * len / 1e9) / 2.0 / 3; atol=1e-6)) end + @testset "opendss parse xycurve" begin + @test eng["xycurve"]["test_curve1"]["interpolated_curve"](0.0226) == 4.52 + @test eng["xycurve"]["test_curve2"]["interpolated_curve"](2.5) == 2.5 + @test eng["xycurve"]["test_curve3"]["interpolated_curve"](0.55) == 5.5 + @test eng["xycurve"]["test_curve4"]["interpolated_curve"](0.55) == 5.5 + end + @testset "opendss parse switch length verify" begin @testset "branches with switches" begin @test eng["switch"]["_l4"]["length"] == 0.001 diff --git a/test/opf.jl b/test/opf.jl index 3bfccda8b..5b2180332 100644 --- a/test/opf.jl +++ b/test/opf.jl @@ -194,16 +194,15 @@ @test length(pmd["gen"]) == 2 @test all(pmd["gen"]["1"]["qmin"] .== -pmd["gen"]["1"]["qmax"]) - @test all(pmd["gen"]["1"]["pmax"] .== pmd["gen"]["1"]["qmax"]) @test all(pmd["gen"]["1"]["pmin"] .== 0.0) sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @test sum(sol["solution"]["gen"]["2"]["pg"] * sol["solution"]["baseMVA"]) < 0.0 - @test sum(sol["solution"]["gen"]["2"]["qg"] * sol["solution"]["baseMVA"]) < 0.0 + @test sum(sol["solution"]["gen"]["2"]["qg"] * sol["solution"]["baseMVA"]) < 0.005 @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183685; atol=1e-4) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00919404; atol=1e-4) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.0048248; atol=1e-4) end @testset "3-bus unbalanced single-phase pv acp opf" begin From 232a6ff8d878f7112b83f4dfc49c1e0feb19cfb4 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 26 Mar 2020 14:17:26 -0600 Subject: [PATCH 111/224] ADD: xfmrcode support Adds support for transformer codes (like linecodes for transformers) Adds xfmrcode to test2_master, no additional tests needed at this time --- src/data_model/eng2math.jl | 43 ++++++++++++ src/io/opendss.jl | 108 ++++++++++++++++++++++++++++- test/data/opendss/test2_master.dss | 6 +- test/opendss.jl | 2 +- 4 files changed, 154 insertions(+), 5 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index e9d530e5a..2516d0c3c 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -782,6 +782,49 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] + if haskey(eng_obj, "xfmrcode") && haskey(data_eng, "xfmrcode") && haskey(data_eng["xfmrcode"], eng_obj["xfmrcode"]) + xfmrcode = data_eng["xfmrcode"][eng_obj["xfmrcode"]] + + if haskey(xfmrcode, "leadlag") + eng_obj["leadlag"] = xfmrcode["leadlag"] + end + + for w in 1:length("configuration") + for prop in ["configuration", "tm", "tm_min", "tm_max", "vnom", "snom", "rs"] + if !ismissing(xfmrcode[prop][w]) + eng_obj[prop][w] = xfmrcode[prop][w] + end + end + + if w>1 + prim_conf = eng_obj["configuration"][1] + if eng_obj["leadlag"] in ["ansi", "lag"] + if prim_conf=="delta" && xfmrcode["configuration"][w]=="wye" + eng_obj["polarity"][w] = -1 + eng_obj["connections"][w] = [_barrel_roll(eng_obj["connections"][w][1:end-1], 1)..., eng_obj["connections"][w][end]] + end + else # hence eng_obj["leadlag"] in ["euro", "lead"] + if prim_conf=="wye" && xfmrcode["configuration"][w]=="delta" + eng_obj["polarity"][w] = -1 + eng_obj["connections"][w] = _barrel_roll(eng_obj["connections"][w], -1) + end + end + end + end + + for prop in ["noloadloss", "imag", "xsc"] + if prop == "xsc" + for (i, v) in enumerate(xfmrcode[prop]) + if !ismissing(v) + eng_obj[prop] = v + end + end + elseif !ismissing(xfmrcode[prop]) + eng_obj[prop] = xfmrcode[prop] + end + end + end + nrw = length(eng_obj["bus"]) # calculate zbase in which the data is specified, and convert to SI diff --git a/src/io/opendss.jl b/src/io/opendss.jl index fee0cc57e..de748c8ef 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -484,6 +484,110 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An end +"Adds transformers to `data_eng` from `data_dss`" +function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + for (name, dss_obj) in get(data_dss, "xfmrcode", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "xfmrcode") + defaults = _apply_ordered_properties(_create_xfmrcode(dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + + nphases = defaults["phases"] + nrw = defaults["windings"] + + _eng_obj = Dict{String,Any}( + "tm" => Vector{Any}(missing, nrw), + "tm_min" => Vector{Any}(missing, nrw), + "tm_max" => Vector{Any}(missing, nrw), + "fixed" => Vector{Any}(missing, nrw), + "vnom" => Vector{Any}(missing, nrw), + "snom" => Vector{Any}(missing, nrw), + "configuration" => Vector{Any}(missing, nrw), + "rs" => Vector{Any}(missing, nrw), + "noloadloss" => missing, + "imag" => missing, + "xsc" => Vector{Any}(missing, nrw == 2 ? 1 : 3), + "leadlag" => missing, + "source_id" => "xfmrcode.$name" + ) + + eng_obj = Dict{String,Any}() + + winding_key = ["", "_2", "_3"] + for w in 1:nrw + if haskey(dss_obj, "conns") || haskey(dss_obj, "conn$(winding_key[w])") + eng_obj["configuration"] = _eng_obj["configuration"] + eng_obj["configuration"][w] = defaults["conns"][w] + end + + if haskey(dss_obj, "taps") || haskey(dss_obj, "tap$(winding_key[w])") + eng_obj["tm"] = _eng_obj["tm"] + eng_obj["tm"][w] = fill(defaults["taps"][w], nphases) + end + + if haskey(dss_obj, "mintap") + eng_obj["tm_min"] = _eng_obj["tm_min"] + eng_obj["tm_min"][w] = fill(defaults["mintap"], nphases) + end + + if haskey(dss_obj, "maxtap") + eng_obj["tm_max"] = _eng_obj["tm_max"] + eng_obj["tm_max"][w] = fill(defaults["maxtap"], nphases) + end + + if haskey(dss_obj, "kvs") || haskey(dss_obj, "kv$(winding_key[w])") + eng_obj["vnom"] = _eng_obj["vnom"] + eng_obj["vnom"][w] = defaults["kvs"][w] + end + + if haskey(dss_obj, "kvas") || haskey(dss_obj, "kva$(winding_key[w])") + eng_obj["snom"] = _eng_obj["snom"] + eng_obj["snom"][w] = defaults["kvas"][w] + end + + if haskey(dss_obj, "%rs") || haskey(dss_obj, "%r$(winding_key[w])") + eng_obj["rs"] = _eng_obj["rs"] + eng_obj["rs"][w] = defaults["%rs"][w] / 100 + end + end + + if haskey(dss_obj, "%noloadloss") + eng_obj["noloadloss"] = _eng_obj["noloadloss"] + eng_obj["noloadloss"] = defaults["%noloadloss"] / 100 + end + + if haskey(dss_obj, "%imag") + eng_obj["imag"] = _eng_obj["imag"] + eng_obj["imag"] = defaults["%imag"] / 100 + end + + if haskey(dss_obj, "xhl") + eng_obj["xsc"] = _eng_obj["xsc"] + eng_obj["xsc"][1] = defaults["xhl"] / 100 + end + + if haskey(dss_obj, "xht") && nrw == 3 + eng_obj["xsc"] = _eng_obj["xsc"] + eng_obj["xsc"][2] = defaults["xht"] / 100 + end + + if haskey(dss_obj, "xlt") && nrw == 3 + eng_obj["xsc"] = _eng_obj["xsc"] + eng_obj["xsc"][3] = defaults["xlt"] / 100 + end + + if haskey(dss_obj, "leadlag") + eng_obj["leadlag"] = defaults["leadlag"] + end + + if import_all + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + end + + _add_eng_obj!(data_eng, "xfmrcode", name, eng_obj) + end +end + + + "Adds transformers to `data_eng` from `data_dss`" function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) @@ -518,6 +622,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) eng_obj["configuration"] = Array{String, 1}(undef, nrw) eng_obj["polarity"] = Array{Int, 1}(undef, nrw) + eng_obj["leadlag"] = defaults["leadlag"] eng_obj["nphases"] = defaults["phases"] @@ -554,7 +659,6 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["polarity"][w] = -1 eng_obj["connections"][w] = _barrel_roll(eng_obj["connections"][w], -1) end - end end end @@ -720,7 +824,7 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban _dss2eng_linecode!(data_eng, data_dss, import_all) _dss2eng_line!(data_eng, data_dss, import_all) - # _dss2eng_xfrmcode!(data_eng, data_dss, import_all) # TODO + _dss2eng_xfmrcode!(data_eng, data_dss, import_all) _dss2eng_transformer!(data_eng, data_dss, import_all) _dss2eng_capacitor!(data_eng, data_dss, import_all) diff --git a/test/data/opendss/test2_master.dss b/test/data/opendss/test2_master.dss index bd5deb2c2..7e432fba2 100644 --- a/test/data/opendss/test2_master.dss +++ b/test/data/opendss/test2_master.dss @@ -38,9 +38,9 @@ new reactor.reactor3 bus1=b9 kvar=10.0 new reactor.reactor4 bus1=b8 like=reactor3 ! Transformers -New "Transformer.t1" phases=1 windings=2 buses=[testsource, _b2, ] conns=[wye, wye, ] kVs=[15.0, 15.0, ] kVAs=[50000, 50000, ] Xhl=1 +New "Transformer.t1" phases=1 buses=[testsource, _b2, ] xfmrcode=t1 Xhl=1 New "Transformer.t2" phases=3 windings=2 Xhl=0.02 buses=[testsource, b3-1, ] conns=[delta, wye, ] kVs=[69, 24.9, ] kVAs=[25000, 25000, ] taps=[1, 1, ] wdg=1 %R=0.0005 wdg=2 %R=0.0005 -New Transformer.t3a phases=1 windings=2 buses=(b7.1, b10.1) conns=(wye, wye) kvs=(10.0, 10) kvas=(30000, 30000) xhl=0.1 %loadloss=.002 wdg=2 Maxtap=1.05 Mintap=0.95 ppm=0 +New Transformer.t3a phases=1 buses=(b7.1, b10.1) xfmrcode=t1 kvs=(10.0, 10) kvas=(30000, 30000) xhl=0.1 %loadloss=.002 wdg=2 Maxtap=1.05 Mintap=0.95 ppm=0 New Transformer.t4 phases=3 windings=2 buses=(b8, b9.1.2.3.0) rneut=0 xneut=0 ~ conns=(delta wye) ! 14.7->115 for kvs[2], otherwise inconsistent voltage base @@ -75,3 +75,5 @@ new xycurve.test_curve1 xarray=(file=load_profile.csv) yarray=(sngfile=load_prof new xycurve.test_curve2 points=[1 1 2 2 3 3] new xycurve.test_curve3 csvfile=load_profile_pq.csv new xycurve.test_curve4 dblfile=load_profile_interval.dbl + +new xfmrcode.t1 phases=1 windings=2 conns=[wye,wye] kvs=[15, 15] kvas=[50000 50000] diff --git a/test/opendss.jl b/test/opendss.jl index 09891e9c3..7ce4d487e 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -124,7 +124,7 @@ @test pmd["name"] == "test2" @test length(pmd) == 19 - @test length(dss) == 15 + @test length(dss) == 16 for (key, len) in zip(["bus", "load", "shunt", "branch", "gen", "dcline", "transformer"], [33, 4, 5, 27, 4, 0, 10]) @test haskey(pmd, key) From 3776363f406c1b51537fe8ba925f1e6e432f0877 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 27 Mar 2020 10:51:45 -0600 Subject: [PATCH 112/224] FIX: busname parsing for sourcebus --- src/io/opendss.jl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index de748c8ef..479545b04 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -706,13 +706,18 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj = Dict{String,Any}( "bus" => _parse_busname(defaults["bus1"])[1], - "configuration" => "wye", + "configuration" => defaults["conn"], "connections" => _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)), "kva" => fill(defaults["kva"] / nphases, nphases), "kvar" => fill(defaults["kvar"] / nphases, nphases), "kv" => fill(defaults["kv"] / nphases, nphases), + "max_rated_power" => fill(defaults["pmpp"] / nphases, nphases), "irradiance" => defaults["irradiance"], - "p-tcurve" => defaults["p-tcurve"], + "temperature" => defaults["temperature"], + "p-t_curve" => defaults["p-tcurve"], + "efficiency_curve" => defaults["effcurve"], + "rs" => diagm(0 => fill(defaults["%r"] / 100., nphases)), + "xs" => diagm(0 => fill(defaults["%x"] / 100., nphases)), "status" => convert(Int, defaults["enabled"]), "source_id" => "pvsystem.$name", ) @@ -801,7 +806,7 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban if haskey(data_dss, "vsource") && haskey(data_dss["vsource"], "source") && haskey(data_dss, "circuit") source = data_dss["vsource"]["source"] defaults = _create_vsource(get(source, "bus1", "sourcebus"), source["name"]; _to_kwargs(source)...) - source_bus = defaults["bus1"] + source_bus = _parse_busname(defaults["bus1"])[1] data_eng["name"] = data_dss["circuit"] data_eng["data_model"] = "engineering" From 2447621eb863d7a61453c8988783d0dae6cea497 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 27 Mar 2020 10:52:22 -0600 Subject: [PATCH 113/224] FIX: parsing of voltagebases --- src/io/utils.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/io/utils.jl b/src/io/utils.jl index cd4ddd517..f82a4dc13 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -239,7 +239,10 @@ function _parse_array(dtype::Type, data::AbstractString)::Vector{dtype} elements = _parse_properties(data[2:end-1]) end else - elements = split(strip(data, _array_delimiters), split_char) + for delim in _array_delimiters + data = replace(data, delim => "") + end + elements = split(data, split_char) elements = [strip(el) for el in elements if strip(el) != ""] end @@ -249,7 +252,7 @@ function _parse_array(dtype::Type, data::AbstractString)::Vector{dtype} push!(array, el) end else - array = zeros(dtype, length(elements)) + array = Vector{dtype}(undef, length(elements)) for (i, el) in enumerate(elements) if _isa_rpn(data) array[i] = _parse_rpn(el, dtype) From b3f81c88be26e1f028e4a72d98b876a63afc2c36 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 31 Mar 2020 11:36:59 -0600 Subject: [PATCH 114/224] REF: InfrastructureModels solution tools Refactors to use the new solution building tools in InfrastructureModels Exports ref, var, con, ids, nws, nw_ids, and ismultinetwork from InfrastructureModels Exports conductor_ids and ismulticonductor from PowerModels _PMs is changed to _PM to follow new naming convention --- Project.toml | 4 +- src/PowerModelsDistribution.jl | 6 +- src/core/constraint.jl | 58 +-- src/core/constraint_template.jl | 290 +++++++-------- src/core/data.jl | 2 +- src/core/export.jl | 8 +- src/core/objective.jl | 72 ++-- src/core/ref.jl | 32 +- src/core/types.jl | 14 +- src/core/variable.jl | 618 ++++++++++++++++---------------- src/data_model/units.jl | 2 +- src/form/acp.jl | 394 ++++++++++---------- src/form/acr.jl | 236 ++++++------ src/form/apo.jl | 184 +++++----- src/form/bf.jl | 14 +- src/form/bf_mx.jl | 362 +++++++++---------- src/form/bf_mx_lin.jl | 52 +-- src/form/bf_mx_sdp.jl | 12 +- src/form/bf_mx_soc.jl | 26 +- src/form/dcp.jl | 60 ++-- src/form/ivr.jl | 412 ++++++++++----------- src/form/shared.jl | 178 ++++----- src/form/wr.jl | 44 +-- src/io/common.jl | 20 +- src/prob/common.jl | 4 +- src/prob/debug.jl | 20 +- src/prob/mld.jl | 54 +-- src/prob/opf.jl | 24 +- src/prob/opf_bf.jl | 12 +- src/prob/opf_bf_lm.jl | 14 +- src/prob/opf_iv.jl | 16 +- src/prob/opf_oltc.jl | 18 +- src/prob/pf.jl | 22 +- src/prob/pf_bf.jl | 14 +- src/prob/pf_iv.jl | 16 +- src/prob/test.jl | 116 +++--- 36 files changed, 1718 insertions(+), 1712 deletions(-) diff --git a/Project.toml b/Project.toml index 72136d3a6..355a59380 100644 --- a/Project.toml +++ b/Project.toml @@ -18,14 +18,14 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] Cbc = ">= 0.4" Dierckx = "~0.4" -InfrastructureModels = "~0.4" +InfrastructureModels = "~0.4.3" Ipopt = ">= 0.4" JSON = "~0.18, ~0.19, ~0.20, ~0.21" JuMP = "~0.19.2, ~0.20, ~0.21" Juniper = ">= 0.4" MathOptInterface = "~0.8, ~0.9" Memento = "~0.10, ~0.11, ~0.12" -PowerModels = "~0.15" +PowerModels = "~0.15.4" SCS = ">= 0.4" julia = "^1" diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 3fa2cabfa..fc51f8ab1 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -11,7 +11,11 @@ module PowerModelsDistribution import LinearAlgebra - const _PMs = PowerModels + const _PM = PowerModels + const _IM = InfrastructureModels + + import PowerModels: ACPPowerModel, ACRPowerModel, DCPPowerModel, NFAPowerModel, SOCWRPowerModel, conductor_ids, ismulticonductor + import InfrastructureModels: ids, ref, var, con, sol, nw_ids, nws, ismultinetwork function __init__() global _LOGGER = Memento.getlogger(PowerModels) diff --git a/src/core/constraint.jl b/src/core/constraint.jl index 36f62f6d2..0f64b6685 100644 --- a/src/core/constraint.jl +++ b/src/core/constraint.jl @@ -1,39 +1,39 @@ "do nothing by default" -function constraint_mc_model_voltage(pm::_PMs.AbstractPowerModel, n::Int) +function constraint_mc_model_voltage(pm::_PM.AbstractPowerModel, n::Int) end # Generic thermal limit constraint "" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, n::Int, f_idx, rate_a) - p_fr = _PMs.var(pm, n, :p, f_idx) - q_fr = _PMs.var(pm, n, :q, f_idx) +function constraint_mc_thermal_limit_from(pm::_PM.AbstractPowerModel, n::Int, f_idx, rate_a) + p_fr = var(pm, n, :p, f_idx) + q_fr = var(pm, n, :q, f_idx) mu_sm_fr = JuMP.@constraint(pm.model, p_fr.^2 + q_fr.^2 .<= rate_a.^2) - if _PMs.report_duals(pm) - _PMs.sol(pm, n, :branch, f_idx[1])[:mu_sm_fr] = mu_sm_fr + if _PM.report_duals(pm) + sol(pm, n, :branch, f_idx[1])[:mu_sm_fr] = mu_sm_fr end end "" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractPowerModel, n::Int, t_idx, rate_a) - p_to = _PMs.var(pm, n, :p, t_idx) - q_to = _PMs.var(pm, n, :q, t_idx) +function constraint_mc_thermal_limit_to(pm::_PM.AbstractPowerModel, n::Int, t_idx, rate_a) + p_to = var(pm, n, :p, t_idx) + q_to = var(pm, n, :q, t_idx) mu_sm_to = JuMP.@constraint(pm.model, p_to.^2 + q_to.^2 .<= rate_a.^2) - if _PMs.report_duals(pm) - _PMs.sol(pm, n, :branch, t_idx[1])[:mu_sm_to] = mu_sm_to + if _PM.report_duals(pm) + sol(pm, n, :branch, t_idx[1])[:mu_sm_to] = mu_sm_to end end "on/off bus voltage magnitude constraint" -function constraint_mc_voltage_magnitude_on_off(pm::_PMs.AbstractPowerModel, n::Int, i::Int, vmin, vmax) - vm = _PMs.var(pm, n, :vm, i) - z_voltage = _PMs.var(pm, n, :z_voltage, i) +function constraint_mc_voltage_magnitude_on_off(pm::_PM.AbstractPowerModel, n::Int, i::Int, vmin, vmax) + vm = var(pm, n, :vm, i) + z_voltage = var(pm, n, :z_voltage, i) - for c in _PMs.conductor_ids(pm, n) + for c in conductor_ids(pm, n) if isfinite(vmax[c]) JuMP.@constraint(pm.model, vm[c] <= vmax[c]*z_voltage) end @@ -46,11 +46,11 @@ end "on/off bus voltage magnitude squared constraint for relaxed formulations" -function constraint_mc_voltage_magnitude_sqr_on_off(pm::_PMs.AbstractPowerModel, n::Int, i::Int, vmin, vmax) - w = _PMs.var(pm, n, :w, i) - z_voltage = _PMs.var(pm, n, :z_voltage, i) +function constraint_mc_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel, n::Int, i::Int, vmin, vmax) + w = var(pm, n, :w, i) + z_voltage = var(pm, n, :z_voltage, i) - for c in _PMs.conductor_ids(pm, n) + for c in conductor_ids(pm, n) if isfinite(vmax[c]) JuMP.@constraint(pm.model, w[c] <= vmax[c]^2*z_voltage) end @@ -62,19 +62,19 @@ function constraint_mc_voltage_magnitude_sqr_on_off(pm::_PMs.AbstractPowerModel, end -function constraint_mc_active_gen_setpoint(pm::_PMs.AbstractPowerModel, n::Int, i, pg) - pg_var = _PMs.var(pm, n, :pg, i) +function constraint_mc_active_gen_setpoint(pm::_PM.AbstractPowerModel, n::Int, i, pg) + pg_var = var(pm, n, :pg, i) JuMP.@constraint(pm.model, pg_var .== pg) end "on/off constraint for generators" -function constraint_mc_generation_on_off(pm::_PMs.AbstractPowerModel, n::Int, i::Int, pmin, pmax, qmin, qmax) - pg = _PMs.var(pm, n, :pg, i) - qg = _PMs.var(pm, n, :qg, i) - z = _PMs.var(pm, n, :z_gen, i) +function constraint_mc_generation_on_off(pm::_PM.AbstractPowerModel, n::Int, i::Int, pmin, pmax, qmin, qmax) + pg = var(pm, n, :pg, i) + qg = var(pm, n, :qg, i) + z = var(pm, n, :z_gen, i) - for c in _PMs.conductor_ids(pm, n) + for c in conductor_ids(pm, n) if isfinite(pmax[c]) JuMP.@constraint(pm.model, pg[c] .<= pmax[c].*z) end @@ -95,9 +95,9 @@ end "" -function constraint_mc_storage_thermal_limit(pm::_PMs.AbstractPowerModel, n::Int, i, rating) - ps = _PMs.var(pm, n, :ps, i) - qs = _PMs.var(pm, n, :qs, i) +function constraint_mc_storage_thermal_limit(pm::_PM.AbstractPowerModel, n::Int, i, rating) + ps = var(pm, n, :ps, i) + qs = var(pm, n, :qs, i) JuMP.@constraint(pm.model, ps.^2 + qs.^2 .<= rating.^2) end diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 8a44bf30c..8bbff64a6 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -1,47 +1,47 @@ "reference angle constraints" -function constraint_mc_theta_ref(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - va_ref = _PMs.ref(pm, nw, :bus, i, "va") +function constraint_mc_theta_ref(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + va_ref = ref(pm, nw, :bus, i, "va") constraint_mc_theta_ref(pm, nw, i, va_ref) end "" -function constraint_mc_power_balance_slack(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - bus = _PMs.ref(pm, nw, :bus, i) - bus_arcs = _PMs.ref(pm, nw, :bus_arcs, i) - bus_arcs_sw = _PMs.ref(pm, nw, :bus_arcs_sw, i) - bus_arcs_trans = _PMs.ref(pm, nw, :bus_arcs_trans, i) - bus_gens = _PMs.ref(pm, nw, :bus_gens, i) - bus_storage = _PMs.ref(pm, nw, :bus_storage, i) - bus_loads = _PMs.ref(pm, nw, :bus_loads, i) - bus_shunts = _PMs.ref(pm, nw, :bus_shunts, i) - - bus_pd = Dict(k => _PMs.ref(pm, nw, :load, k, "pd") for k in bus_loads) - bus_qd = Dict(k => _PMs.ref(pm, nw, :load, k, "qd") for k in bus_loads) - - bus_gs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) - bus_bs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) +function constraint_mc_power_balance_slack(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + bus = ref(pm, nw, :bus, i) + bus_arcs = ref(pm, nw, :bus_arcs, i) + bus_arcs_sw = ref(pm, nw, :bus_arcs_sw, i) + bus_arcs_trans = ref(pm, nw, :bus_arcs_trans, i) + bus_gens = ref(pm, nw, :bus_gens, i) + bus_storage = ref(pm, nw, :bus_storage, i) + bus_loads = ref(pm, nw, :bus_loads, i) + bus_shunts = ref(pm, nw, :bus_shunts, i) + + bus_pd = Dict(k => ref(pm, nw, :load, k, "pd") for k in bus_loads) + bus_qd = Dict(k => ref(pm, nw, :load, k, "qd") for k in bus_loads) + + bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) + bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) constraint_mc_power_balance_slack(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) end "" -function constraint_mc_model_voltage(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw) +function constraint_mc_model_voltage(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw) constraint_mc_model_voltage(pm, nw) end "ohms constraint for branches on the from-side" -function constraint_mc_ohms_yt_from(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_ohms_yt_from(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + 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 = _PMs.calc_branch_y(branch) - tr, ti = _PMs.calc_branch_t(branch) + g, b = _PM.calc_branch_y(branch) + tr, ti = _PM.calc_branch_t(branch) g_fr = branch["g_fr"] b_fr = branch["b_fr"] tm = branch["tap"] @@ -51,15 +51,15 @@ end "ohms constraint for branches on the to-side" -function constraint_mc_ohms_yt_to(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_ohms_yt_to(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + 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 = _PMs.calc_branch_y(branch) - tr, ti = _PMs.calc_branch_t(branch) + g, b = _PM.calc_branch_y(branch) + tr, ti = _PM.calc_branch_t(branch) g_to = branch["g_to"] b_to = branch["b_to"] tm = branch["tap"] @@ -69,8 +69,8 @@ end "" -function constraint_mc_model_voltage_magnitude_difference(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_model_voltage_magnitude_difference(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) @@ -88,7 +88,7 @@ end "" function constraint_mc_model_current(pm::AbstractUBFModels; nw::Int=pm.cnw) - for (i,branch) in _PMs.ref(pm, nw, :branch) + for (i,branch) in ref(pm, nw, :branch) f_bus = branch["f_bus"] t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) @@ -102,8 +102,8 @@ end "" -function constraint_mc_flow_losses(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_flow_losses(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) @@ -123,12 +123,12 @@ end "Transformer constraints, considering winding type, conductor order, polarity and tap settings." -function constraint_mc_trans(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw, fix_taps::Bool=true) - if _PMs.ref(pm, pm.cnw, :conductors)!=3 +function constraint_mc_trans(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, fix_taps::Bool=true) + if ref(pm, pm.cnw, :conductors)!=3 Memento.error(_LOGGER, "Transformers only work with networks with three conductors.") end - trans = _PMs.ref(pm, :transformer, i) + trans = ref(pm, :transformer, i) f_bus = trans["f_bus"] t_bus = trans["t_bus"] f_idx = (i, f_bus, t_bus) @@ -139,7 +139,7 @@ function constraint_mc_trans(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw t_cnd = trans["t_connections"][1:3] tm_set = trans["tm"] tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : trans["fixed"] - tm_scale = calculate_tm_scale(trans, _PMs.ref(pm, nw, :bus, f_bus), _PMs.ref(pm, nw, :bus, t_bus)) + tm_scale = calculate_tm_scale(trans, ref(pm, nw, :bus, f_bus), ref(pm, nw, :bus, t_bus)) #TODO change data model # there is redundancy in specifying polarity seperately on from and to side @@ -158,21 +158,21 @@ end "KCL including transformer arcs" -function constraint_mc_power_balance(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - bus = _PMs.ref(pm, nw, :bus, i) - bus_arcs = _PMs.ref(pm, nw, :bus_arcs, i) - bus_arcs_sw = _PMs.ref(pm, nw, :bus_arcs_sw, i) - bus_arcs_trans = _PMs.ref(pm, nw, :bus_arcs_trans, i) - bus_gens = _PMs.ref(pm, nw, :bus_gens, i) - bus_storage = _PMs.ref(pm, nw, :bus_storage, i) - bus_loads = _PMs.ref(pm, nw, :bus_loads, i) - bus_shunts = _PMs.ref(pm, nw, :bus_shunts, i) - - bus_pd = Dict(k => _PMs.ref(pm, nw, :load, k, "pd") for k in bus_loads) - bus_qd = Dict(k => _PMs.ref(pm, nw, :load, k, "qd") for k in bus_loads) - - bus_gs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) - bus_bs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) +function constraint_mc_power_balance(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + bus = ref(pm, nw, :bus, i) + bus_arcs = ref(pm, nw, :bus_arcs, i) + bus_arcs_sw = ref(pm, nw, :bus_arcs_sw, i) + bus_arcs_trans = ref(pm, nw, :bus_arcs_trans, i) + bus_gens = ref(pm, nw, :bus_gens, i) + bus_storage = ref(pm, nw, :bus_storage, i) + bus_loads = ref(pm, nw, :bus_loads, i) + bus_shunts = ref(pm, nw, :bus_shunts, i) + + bus_pd = Dict(k => ref(pm, nw, :load, k, "pd") for k in bus_loads) + bus_qd = Dict(k => ref(pm, nw, :load, k, "qd") for k in bus_loads) + + bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) + bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) constraint_mc_power_balance(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) end @@ -190,10 +190,10 @@ For a discussion of sequence components and voltage unbalance factor (VUF), see url={https://molzahn.github.io/pubs/girigoudar_molzahn_roald-2019.pdf} } """ -function constraint_mc_voltage_balance(pm::_PMs.AbstractPowerModel, bus_id::Int; nw=pm.cnw) - @assert(_PMs.ref(pm, nw, :conductors)==3) +function constraint_mc_voltage_balance(pm::_PM.AbstractPowerModel, bus_id::Int; nw=pm.cnw) + @assert(ref(pm, nw, :conductors)==3) - bus = _PMs.ref(pm, nw, :bus, bus_id) + bus = ref(pm, nw, :bus, bus_id) if haskey(bus, "vm_vuf_max") constraint_mc_vm_vuf(pm, nw, bus_id, bus["vm_vuf_max"]) @@ -220,18 +220,18 @@ end "KCL including transformer arcs and load variables." -function constraint_mc_power_balance_load(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - bus = _PMs.ref(pm, nw, :bus, i) - bus_arcs = _PMs.ref(pm, nw, :bus_arcs, i) - bus_arcs_sw = _PMs.ref(pm, nw, :bus_arcs_sw, i) - bus_arcs_trans = _PMs.ref(pm, nw, :bus_arcs_trans, i) - bus_gens = _PMs.ref(pm, nw, :bus_gens, i) - bus_storage = _PMs.ref(pm, nw, :bus_storage, i) - bus_loads = _PMs.ref(pm, nw, :bus_loads, i) - bus_shunts = _PMs.ref(pm, nw, :bus_shunts, i) - - bus_gs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) - bus_bs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) +function constraint_mc_power_balance_load(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + bus = ref(pm, nw, :bus, i) + bus_arcs = ref(pm, nw, :bus_arcs, i) + bus_arcs_sw = ref(pm, nw, :bus_arcs_sw, i) + bus_arcs_trans = ref(pm, nw, :bus_arcs_trans, i) + bus_gens = ref(pm, nw, :bus_gens, i) + bus_storage = ref(pm, nw, :bus_storage, i) + bus_loads = ref(pm, nw, :bus_loads, i) + bus_shunts = ref(pm, nw, :bus_shunts, i) + + bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) + bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) constraint_mc_power_balance_load(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) end @@ -271,9 +271,9 @@ sn_a = v_a.conj(i_a) = v_a.(s_ab/(v_a-v_b) - s_ca/(v_c-v_a)) So for delta, sn is constrained indirectly. """ -function constraint_mc_load(pm::_PMs.AbstractPowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) - load = _PMs.ref(pm, nw, :load, id) - bus = _PMs.ref(pm, nw,:bus, load["load_bus"]) +function constraint_mc_load(pm::_PM.AbstractPowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) + load = ref(pm, nw, :load, id) + bus = ref(pm, nw,:bus, load["load_bus"]) conn = haskey(load, "configuration") ? load["configuration"] : "wye" @@ -299,9 +299,9 @@ sn_a = v_a.conj(i_a) = v_a.(s_ab/(v_a-v_b) - s_ca/(v_c-v_a)) So for delta, sn is constrained indirectly. """ -function constraint_mc_generation(pm::_PMs.AbstractPowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true, bounded::Bool=true) - generator = _PMs.ref(pm, nw, :gen, id) - bus = _PMs.ref(pm, nw,:bus, generator["gen_bus"]) +function constraint_mc_generation(pm::_PM.AbstractPowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true, bounded::Bool=true) + generator = ref(pm, nw, :gen, id) + bus = ref(pm, nw,:bus, generator["gen_bus"]) N = 3 pmin = get(generator, "pmin", fill(-Inf, N)) @@ -318,51 +318,51 @@ end "KCL for load shed problem with transformers" -function constraint_mc_power_balance_shed(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - bus = _PMs.ref(pm, nw, :bus, i) - bus_arcs = _PMs.ref(pm, nw, :bus_arcs, i) - bus_arcs_sw = _PMs.ref(pm, nw, :bus_arcs_sw, i) - bus_arcs_trans = _PMs.ref(pm, nw, :bus_arcs_trans, i) - bus_gens = _PMs.ref(pm, nw, :bus_gens, i) - bus_storage = _PMs.ref(pm, nw, :bus_storage, i) - bus_loads = _PMs.ref(pm, nw, :bus_loads, i) - bus_shunts = _PMs.ref(pm, nw, :bus_shunts, i) - - bus_pd = Dict(k => _PMs.ref(pm, nw, :load, k, "pd") for k in bus_loads) - bus_qd = Dict(k => _PMs.ref(pm, nw, :load, k, "qd") for k in bus_loads) - - bus_gs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) - bus_bs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) +function constraint_mc_power_balance_shed(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + bus = ref(pm, nw, :bus, i) + bus_arcs = ref(pm, nw, :bus_arcs, i) + bus_arcs_sw = ref(pm, nw, :bus_arcs_sw, i) + bus_arcs_trans = ref(pm, nw, :bus_arcs_trans, i) + bus_gens = ref(pm, nw, :bus_gens, i) + bus_storage = ref(pm, nw, :bus_storage, i) + bus_loads = ref(pm, nw, :bus_loads, i) + bus_shunts = ref(pm, nw, :bus_shunts, i) + + bus_pd = Dict(k => ref(pm, nw, :load, k, "pd") for k in bus_loads) + bus_qd = Dict(k => ref(pm, nw, :load, k, "qd") for k in bus_loads) + + bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) + bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) constraint_mc_power_balance_shed(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) end "on/off constraint for bus voltages" -function constraint_mc_bus_voltage_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) +function constraint_mc_bus_voltage_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) constraint_mc_bus_voltage_on_off(pm, nw; kwargs...) end "on/off voltage magnitude constraint" -function constraint_mc_voltage_magnitude_on_off(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - bus = _PMs.ref(pm, nw, :bus, i) +function constraint_mc_voltage_magnitude_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + bus = ref(pm, nw, :bus, i) constraint_mc_voltage_magnitude_on_off(pm, nw, i, bus["vmin"], bus["vmax"]) end "on/off voltage magnitude squared constraint for relaxed formulations" -function constraint_mc_voltage_magnitude_sqr_on_off(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - bus = _PMs.ref(pm, nw, :bus, i) +function constraint_mc_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + bus = ref(pm, nw, :bus, i) constraint_mc_voltage_magnitude_sqr_on_off(pm, nw, i, bus["vmin"], bus["vmax"]) end "This is duplicated at PMD level to correctly handle the indexing of the shunts." -function constraint_mc_voltage_angle_difference(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_voltage_angle_difference(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) @@ -373,18 +373,18 @@ end "storage loss constraints, delegate to PowerModels" -function constraint_mc_storage_loss(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) - storage = _PMs.ref(pm, nw, :storage, i) +function constraint_mc_storage_loss(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) + storage = ref(pm, nw, :storage, i) - _PMs.constraint_storage_loss(pm, nw, i, storage["storage_bus"], storage["r"], storage["x"], storage["p_loss"], storage["q_loss"]; - conductors = _PMs.conductor_ids(pm, nw) + _PM.constraint_storage_loss(pm, nw, i, storage["storage_bus"], storage["r"], storage["x"], storage["p_loss"], storage["q_loss"]; + conductors = conductor_ids(pm, nw) ) end "branch thermal constraints from" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_thermal_limit_from(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) @@ -396,8 +396,8 @@ end "branch thermal constraints to" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_thermal_limit_to(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] t_bus = branch["t_bus"] t_idx = (i, t_bus, f_bus) @@ -409,16 +409,16 @@ end "voltage magnitude setpoint constraint" -function constraint_mc_voltage_magnitude_setpoint(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) - bus = _PMs.ref(pm, nw, :bus, i) +function constraint_mc_voltage_magnitude_setpoint(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) + bus = ref(pm, nw, :bus, i) vmref = bus["vm"] #Not sure why this is needed constraint_mc_voltage_magnitude_setpoint(pm, nw, i, vmref) end "generator active power setpoint constraint" -function constraint_mc_active_gen_setpoint(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) - pg_set = _PMs.ref(pm, nw, :gen, i)["pg"] +function constraint_mc_active_gen_setpoint(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) + pg_set = ref(pm, nw, :gen, i)["pg"] constraint_mc_active_gen_setpoint(pm, nw, i, pg_set) end @@ -428,17 +428,17 @@ This constraint captures problem agnostic constraints that define limits for voltage magnitudes (where variable bounds cannot be used) Notable examples include IVRPowerModel and ACRPowerModel """ -function constraint_mc_voltage_magnitude_bounds(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - bus = _PMs.ref(pm, nw, :bus, i) +function constraint_mc_voltage_magnitude_bounds(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + bus = ref(pm, nw, :bus, i) vmin = get(bus, "vmin", fill(0.0, 3)) #TODO update for four-wire vmax = get(bus, "vmax", fill(Inf, 3)) #TODO update for four-wire constraint_mc_voltage_magnitude_bounds(pm, nw, i, vmin, vmax) end -function constraint_mc_generation_on_off(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - gen = _PMs.ref(pm, nw, :gen, i) - ncnds = length(_PMs.conductor_ids(pm; nw=nw)) +function constraint_mc_generation_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + gen = ref(pm, nw, :gen, i) + ncnds = length(conductor_ids(pm; nw=nw)) pmin = get(gen, "pmin", fill(-Inf, ncnds)) pmax = get(gen, "pmax", fill( Inf, ncnds)) @@ -449,24 +449,24 @@ function constraint_mc_generation_on_off(pm::_PMs.AbstractPowerModel, i::Int; nw end "" -function constraint_mc_storage_thermal_limit(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - storage = _PMs.ref(pm, nw, :storage, i) +function constraint_mc_storage_thermal_limit(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + storage = ref(pm, nw, :storage, i) constraint_mc_storage_thermal_limit(pm, nw, i, storage["thermal_rating"]) end "" -function constraint_mc_storage_current_limit(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - storage = _PMs.ref(pm, nw, :storage, i) +function constraint_mc_storage_current_limit(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + storage = ref(pm, nw, :storage, i) constraint_mc_storage_current_limit(pm, nw, i, storage["storage_bus"], storage["current_rating"]) end "" -function constraint_mc_storage_on_off(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - storage = _PMs.ref(pm, nw, :storage, i) +function constraint_mc_storage_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + storage = ref(pm, nw, :storage, i) charge_ub = storage["charge_rating"] discharge_ub = storage["discharge_rating"] - cnds = _PMs.conductor_ids(pm, nw) + cnds = conductor_ids(pm, nw) ncnds = length(cnds) pmin = zeros(ncnds) pmax = zeros(ncnds) @@ -474,55 +474,55 @@ function constraint_mc_storage_on_off(pm::_PMs.AbstractPowerModel, i::Int; nw::I qmax = zeros(ncnds) for c in 1:ncnds - inj_lb, inj_ub = _PMs.ref_calc_storage_injection_bounds(_PMs.ref(pm, nw, :storage), _PMs.ref(pm, nw, :bus), c) + inj_lb, inj_ub = _PM.ref_calc_storage_injection_bounds(ref(pm, nw, :storage), ref(pm, nw, :bus), c) pmin[c] = inj_lb[i] pmax[c] = inj_ub[i] - qmin[c] = max(inj_lb[i], _PMs.ref(pm, nw, :storage, i, "qmin")[c]) - qmax[c] = min(inj_ub[i], _PMs.ref(pm, nw, :storage, i, "qmax")[c]) + qmin[c] = max(inj_lb[i], ref(pm, nw, :storage, i, "qmin")[c]) + qmax[c] = min(inj_ub[i], ref(pm, nw, :storage, i, "qmax")[c]) end constraint_mc_storage_on_off(pm, nw, i, pmin, pmax, qmin, qmax, charge_ub, discharge_ub) end "defines limits on active power output of a generator where bounds can't be used" -function constraint_mc_generation_active_power_limits(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - gen = _PMs.ref(pm, nw, :gen, i) +function constraint_mc_generation_active_power_limits(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + gen = ref(pm, nw, :gen, i) bus = gen["gen_bus"] constraint_mc_generation_active_power_limits(pm, nw, i, bus, gen["pmax"], gen["pmin"]) end "defines limits on reactive power output of a generator where bounds can't be used" -function constraint_mc_generation_reactive_power_limits(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - gen = _PMs.ref(pm, nw, :gen, i) +function constraint_mc_generation_reactive_power_limits(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + gen = ref(pm, nw, :gen, i) bus = gen["gen_bus"] constraint_mc_generation_reactive_power_limits(pm, nw, i, bus, gen["qmax"], gen["qmin"]) end "" -function constraint_mc_current_balance_load(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - bus = _PMs.ref(pm, nw, :bus, i) - bus_arcs = _PMs.ref(pm, nw, :bus_arcs, i) - bus_arcs_sw = _PMs.ref(pm, nw, :bus_arcs_sw, i) - bus_arcs_trans = _PMs.ref(pm, nw, :bus_arcs_trans, i) - bus_gens = _PMs.ref(pm, nw, :bus_gens, i) - bus_storage = _PMs.ref(pm, nw, :bus_storage, i) - bus_loads = _PMs.ref(pm, nw, :bus_loads, i) - bus_shunts = _PMs.ref(pm, nw, :bus_shunts, i) - - bus_gs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) - bus_bs = Dict(k => _PMs.ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) +function constraint_mc_current_balance_load(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + bus = ref(pm, nw, :bus, i) + bus_arcs = ref(pm, nw, :bus_arcs, i) + bus_arcs_sw = ref(pm, nw, :bus_arcs_sw, i) + bus_arcs_trans = ref(pm, nw, :bus_arcs_trans, i) + bus_gens = ref(pm, nw, :bus_gens, i) + bus_storage = ref(pm, nw, :bus_storage, i) + bus_loads = ref(pm, nw, :bus_loads, i) + bus_shunts = ref(pm, nw, :bus_shunts, i) + + bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) + bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) constraint_mc_current_balance_load(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) end "" -function constraint_mc_current_from(pm::_PMs.AbstractIVRModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_current_from(pm::_PM.AbstractIVRModel, i::Int; nw::Int=pm.cnw) + branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) - tr, ti = _PMs.calc_branch_t(branch) + tr, ti = _PM.calc_branch_t(branch) g_fr = branch["g_fr"] b_fr = branch["b_fr"] tm = branch["tap"] @@ -531,14 +531,14 @@ function constraint_mc_current_from(pm::_PMs.AbstractIVRModel, i::Int; nw::Int=p end "" -function constraint_mc_current_to(pm::_PMs.AbstractIVRModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_current_to(pm::_PM.AbstractIVRModel, i::Int; nw::Int=pm.cnw) + 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) - tr, ti = _PMs.calc_branch_t(branch) + tr, ti = _PM.calc_branch_t(branch) g_to = branch["g_to"] b_to = branch["b_to"] tm = branch["tap"] @@ -547,13 +547,13 @@ function constraint_mc_current_to(pm::_PMs.AbstractIVRModel, i::Int; nw::Int=pm. end "" -function constraint_mc_voltage_drop(pm::_PMs.AbstractPowerModel, i::Int; nw::Int=pm.cnw) - branch = _PMs.ref(pm, nw, :branch, i) +function constraint_mc_voltage_drop(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] t_bus = branch["t_bus"] f_idx = (i, f_bus, t_bus) - tr, ti = _PMs.calc_branch_t(branch) + tr, ti = _PM.calc_branch_t(branch) r = branch["br_r"] x = branch["br_x"] tm = branch["tap"] diff --git a/src/core/data.jl b/src/core/data.jl index dc0ee0cd2..ed2ed1e1a 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -685,7 +685,7 @@ end "" -function sol_polar_voltage!(pm::_PMs.AbstractPowerModel, solution::Dict) +function sol_polar_voltage!(pm::_PM.AbstractPowerModel, solution::Dict) if haskey(solution, "nw") nws_data = solution["nw"] else diff --git a/src/core/export.jl b/src/core/export.jl index 509983d50..d3c879c08 100644 --- a/src/core/export.jl +++ b/src/core/export.jl @@ -39,6 +39,8 @@ for status_code_enum in [TerminationStatusCode, ResultStatusCode] end end -# so that users do not need to import PowerModels -import PowerModels: ACPPowerModel, ACRPowerModel, DCPPowerModel, NFAPowerModel, SOCWRPowerModel -export ACPPowerModel, ACRPowerModel, DCPPowerModel, NFAPowerModel, SOCWRPowerModel +# PowerModels Exports +export ACPPowerModel, ACRPowerModel, DCPPowerModel, NFAPowerModel, SOCWRPowerModel, conductor_ids, ismulticonductor + +# InfrastructureModels Exports +export ids, ref, var, con, sol, nw_ids, nws, ismultinetwork, ismulticonductor, conductor_ids diff --git a/src/core/objective.jl b/src/core/objective.jl index 25704e2a3..cdd9ff607 100644 --- a/src/core/objective.jl +++ b/src/core/objective.jl @@ -1,72 +1,72 @@ "a quadratic penalty for bus power slack variables" -function objective_min_bus_power_slack(pm::_PMs.AbstractPowerModel) +function objective_min_bus_power_slack(pm::_PM.AbstractPowerModel) return JuMP.@objective(pm.model, Min, sum( sum( - sum( _PMs.var(pm, n, :p_slack, i)[c]^2 + _PMs.var(pm, n, :q_slack, i)[c]^2 for (i,bus) in nw_ref[:bus]) - for c in _PMs.conductor_ids(pm, n)) - for (n, nw_ref) in _PMs.nws(pm)) + sum( var(pm, n, :p_slack, i)[c]^2 + var(pm, n, :q_slack, i)[c]^2 for (i,bus) in nw_ref[:bus]) + for c in conductor_ids(pm, n)) + for (n, nw_ref) in nws(pm)) ) end "minimum load delta objective (continuous load shed) with storage" -function objective_mc_min_load_delta(pm::_PMs.AbstractPowerModel) - for (n, nw_ref) in _PMs.nws(pm) - _PMs.var(pm, n)[:delta_pg] = Dict(i => JuMP.@variable(pm.model, - [c in _PMs.conductor_ids(pm, n)], base_name="$(n)_$(i)_delta_pg", - start = 0.0) for i in _PMs.ids(pm, n, :gen)) +function objective_mc_min_load_delta(pm::_PM.AbstractPowerModel) + for (n, nw_ref) in nws(pm) + var(pm, n)[:delta_pg] = Dict(i => JuMP.@variable(pm.model, + [c in conductor_ids(pm, n)], base_name="$(n)_$(i)_delta_pg", + start = 0.0) for i in ids(pm, n, :gen)) - _PMs.var(pm, n)[:delta_ps] = Dict(i => JuMP.@variable(pm.model, - [c in _PMs.conductor_ids(pm, n)], base_name="$(n)_$(i)_delta_ps", - start = 0.0) for i in _PMs.ids(pm, n, :storage)) + var(pm, n)[:delta_ps] = Dict(i => JuMP.@variable(pm.model, + [c in conductor_ids(pm, n)], base_name="$(n)_$(i)_delta_ps", + start = 0.0) for i in ids(pm, n, :storage)) - for c in _PMs.conductor_ids(pm, n) + for c in conductor_ids(pm, n) for (i, gen) in nw_ref[:gen] - JuMP.@constraint(pm.model, _PMs.var(pm, n, :delta_pg, i)[c] >= (gen["pg"][c] - _PMs.var(pm, n, :pg, i)[c])) - JuMP.@constraint(pm.model, _PMs.var(pm, n, :delta_pg, i)[c] >= -(gen["pg"][c] - _PMs.var(pm, n, :pg, i)[c])) + JuMP.@constraint(pm.model, var(pm, n, :delta_pg, i)[c] >= (gen["pg"][c] - var(pm, n, :pg, i)[c])) + JuMP.@constraint(pm.model, var(pm, n, :delta_pg, i)[c] >= -(gen["pg"][c] - var(pm, n, :pg, i)[c])) end for (i, strg) in nw_ref[:storage] - JuMP.@constraint(pm.model, _PMs.var(pm, n, :delta_ps, i)[c] >= (strg["ps"][c] - _PMs.var(pm, n, :ps, i)[c])) - JuMP.@constraint(pm.model, _PMs.var(pm, n, :delta_ps, i)[c] >= -(strg["ps"][c] - _PMs.var(pm, n, :ps, i)[c])) + JuMP.@constraint(pm.model, var(pm, n, :delta_ps, i)[c] >= (strg["ps"][c] - var(pm, n, :ps, i)[c])) + JuMP.@constraint(pm.model, var(pm, n, :delta_ps, i)[c] >= -(strg["ps"][c] - var(pm, n, :ps, i)[c])) end end end - load_weight = Dict(n => Dict(i => get(load, "weight", 1.0) for (i,load) in _PMs.ref(pm, n, :load)) for n in _PMs.nw_ids(pm)) - M = Dict(n => Dict(c => 10*maximum([load_weight[n][i]*abs(load["pd"][c]) for (i,load) in _PMs.ref(pm, n, :load)]) for c in _PMs.conductor_ids(pm, n)) for n in _PMs.nw_ids(pm)) + load_weight = Dict(n => Dict(i => get(load, "weight", 1.0) for (i,load) in ref(pm, n, :load)) for n in nw_ids(pm)) + M = Dict(n => Dict(c => 10*maximum([load_weight[n][i]*abs(load["pd"][c]) for (i,load) in ref(pm, n, :load)]) for c in conductor_ids(pm, n)) for n in nw_ids(pm)) JuMP.@objective(pm.model, Min, sum( sum( - sum( ( 10*(1 - _PMs.var(pm, n, :z_voltage, i)) for i in keys(nw_ref[:bus]))) + - sum( ( M[n][c]*(1 - _PMs.var(pm, n, :z_demand, i))) for i in keys(nw_ref[:load])) + - sum( (abs(shunt["gs"][c])*(1 - _PMs.var(pm, n, :z_shunt, i))) for (i,shunt) in nw_ref[:shunt]) + - sum( ( _PMs.var(pm, n, :delta_pg, i)[c] for i in keys(nw_ref[:gen]))) + - sum( ( _PMs.var(pm, n, :delta_ps, i)[c] for i in keys(nw_ref[:storage]))) - for c in _PMs.conductor_ids(pm, n)) - for (n, nw_ref) in _PMs.nws(pm)) + sum( ( 10*(1 - var(pm, n, :z_voltage, i)) for i in keys(nw_ref[:bus]))) + + sum( ( M[n][c]*(1 - var(pm, n, :z_demand, i))) for i in keys(nw_ref[:load])) + + sum( (abs(shunt["gs"][c])*(1 - var(pm, n, :z_shunt, i))) for (i,shunt) in nw_ref[:shunt]) + + sum( ( var(pm, n, :delta_pg, i)[c] for i in keys(nw_ref[:gen]))) + + sum( ( var(pm, n, :delta_ps, i)[c] for i in keys(nw_ref[:storage]))) + for c in conductor_ids(pm, n)) + for (n, nw_ref) in nws(pm)) ) end "maximum loadability objective (continuous load shed) with storage" -function objective_mc_max_loadability(pm::_PMs.AbstractPowerModel) - load_weight = Dict(n => Dict(i => get(load, "weight", 1.0) for (i,load) in _PMs.ref(pm, n, :load)) for n in _PMs.nw_ids(pm)) - M = Dict(n => Dict(c => 10*maximum([load_weight[n][i]*abs(load["pd"][c]) for (i,load) in _PMs.ref(pm, n, :load)]) for c in _PMs.conductor_ids(pm, n)) for n in _PMs.nw_ids(pm)) +function objective_mc_max_loadability(pm::_PM.AbstractPowerModel) + load_weight = Dict(n => Dict(i => get(load, "weight", 1.0) for (i,load) in ref(pm, n, :load)) for n in nw_ids(pm)) + M = Dict(n => Dict(c => 10*maximum([load_weight[n][i]*abs(load["pd"][c]) for (i,load) in ref(pm, n, :load)]) for c in conductor_ids(pm, n)) for n in nw_ids(pm)) JuMP.@objective(pm.model, Max, sum( sum( - sum( ( 10*_PMs.var(pm, n, :z_voltage, i) for (i, bus) in nw_ref[:bus])) + - sum( ( M[n][c]*_PMs.var(pm, n, :z_demand, i)) for (i,load) in nw_ref[:load]) + - sum( (abs(shunt["gs"])*_PMs.var(pm, n, :z_shunt, i)) for (i,shunt) in nw_ref[:shunt]) + - sum( ( _PMs.var(pm, n, :z_gen, i) for (i,gen) in nw_ref[:gen])) + - sum( ( _PMs.var(pm, n, :z_storage, i) for (i,storage) in nw_ref[:storage])) - for c in _PMs.conductor_ids(pm, n)) - for (n, nw_ref) in _PMs.nws(pm)) + sum( ( 10*var(pm, n, :z_voltage, i) for (i, bus) in nw_ref[:bus])) + + sum( ( M[n][c]*var(pm, n, :z_demand, i)) for (i,load) in nw_ref[:load]) + + sum( (abs(shunt["gs"])*var(pm, n, :z_shunt, i)) for (i,shunt) in nw_ref[:shunt]) + + sum( ( var(pm, n, :z_gen, i) for (i,gen) in nw_ref[:gen])) + + sum( ( var(pm, n, :z_storage, i) for (i,storage) in nw_ref[:storage])) + for c in conductor_ids(pm, n)) + for (n, nw_ref) in nws(pm)) ) end diff --git a/src/core/ref.jl b/src/core/ref.jl index 7a7a2e324..2b545a574 100644 --- a/src/core/ref.jl +++ b/src/core/ref.jl @@ -2,7 +2,7 @@ import LinearAlgebra: diagm "" -function _calc_mc_voltage_product_bounds(pm::_PMs.AbstractPowerModel, buspairs; nw::Int=pm.cnw) +function _calc_mc_voltage_product_bounds(pm::_PM.AbstractPowerModel, buspairs; nw::Int=pm.cnw) wr_min = Dict([(bp, -Inf) for bp in buspairs]) wr_max = Dict([(bp, Inf) for bp in buspairs]) wi_min = Dict([(bp, -Inf) for bp in buspairs]) @@ -10,13 +10,13 @@ function _calc_mc_voltage_product_bounds(pm::_PMs.AbstractPowerModel, buspairs; for (i, j, c, d) in buspairs if i == j - bus = _PMs.ref(pm, nw, :bus)[i] + bus = ref(pm, nw, :bus)[i] vm_fr_max = bus["vmax"][c] vm_to_max = bus["vmax"][d] vm_fr_min = bus["vmin"][c] vm_to_min = bus["vmin"][d] else - buspair = _PMs.ref(pm, nw, :buspairs)[(i, j)] + buspair = ref(pm, nw, :buspairs)[(i, j)] vm_fr_max = buspair["vm_fr_max"][c] vm_to_max = buspair["vm_to_max"][d] vm_fr_min = buspair["vm_fr_min"][c] @@ -34,27 +34,27 @@ end "" -function _find_ref_buses(pm::_PMs.AbstractPowerModel, nw) - buses = _PMs.ref(pm, nw, :bus) +function _find_ref_buses(pm::_PM.AbstractPowerModel, nw) + buses = ref(pm, nw, :bus) return [b for (b,bus) in buses if bus["bus_type"]==3] # return [bus for (b,bus) in buses ] end "Adds arcs for PMD transformers; for dclines and branches this is done in PMs" -function ref_add_arcs_trans!(pm::_PMs.AbstractPowerModel) - for nw in _PMs.nw_ids(pm) - if !haskey(_PMs.ref(pm, nw), :transformer) +function ref_add_arcs_trans!(pm::_PM.AbstractPowerModel) + for nw in nw_ids(pm) + if !haskey(ref(pm, nw), :transformer) # this might happen when parsing data from matlab format # the OpenDSS parser always inserts a trans dict - _PMs.ref(pm, nw)[:transformer] = Dict{Int, Any}() + ref(pm, nw)[:transformer] = Dict{Int, Any}() end # dirty fix add arcs_from/to_trans and bus_arcs_trans - pm.ref[:nw][nw][:arcs_from_trans] = [(i, trans["f_bus"], trans["t_bus"]) for (i,trans) in _PMs.ref(pm, nw, :transformer)] - pm.ref[:nw][nw][:arcs_to_trans] = [(i, trans["t_bus"], trans["f_bus"]) for (i,trans) in _PMs.ref(pm, nw, :transformer)] + pm.ref[:nw][nw][:arcs_from_trans] = [(i, trans["f_bus"], trans["t_bus"]) for (i,trans) in ref(pm, nw, :transformer)] + pm.ref[:nw][nw][:arcs_to_trans] = [(i, trans["t_bus"], trans["f_bus"]) for (i,trans) in ref(pm, nw, :transformer)] pm.ref[:nw][nw][:arcs_trans] = [pm.ref[:nw][nw][:arcs_from_trans]..., pm.ref[:nw][nw][:arcs_to_trans]...] pm.ref[:nw][nw][:bus_arcs_trans] = Dict{Int64, Array{Any, 1}}() - for i in _PMs.ids(pm, nw, :bus) + for i in ids(pm, nw, :bus) pm.ref[:nw][nw][:bus_arcs_trans][i] = [e for e in pm.ref[:nw][nw][:arcs_trans] if e[2]==i] end end @@ -62,8 +62,8 @@ end "" -function _calc_mc_transformer_Tvi(pm::_PMs.AbstractPowerModel, i::Int; nw=pm.cnw) - trans = _PMs.ref(pm, nw, :transformer, i) +function _calc_mc_transformer_Tvi(pm::_PM.AbstractPowerModel, i::Int; nw=pm.cnw) + trans = ref(pm, nw, :transformer, i) # transformation matrices # Tv and Ti will be compositions of these Tbr = [0 0 1; 1 0 0; 0 1 0] # barrel roll @@ -131,8 +131,8 @@ function _calc_mc_transformer_Tvi(pm::_PMs.AbstractPowerModel, i::Int; nw=pm.cnw # make equations dimensionless # if vbase across a transformer scales according to the ratio of vnom_kv, # this will simplify to 1.0 - bkv_fr = _PMs.ref(pm, nw, :bus, trans["f_bus"], "base_kv") - bkv_to = _PMs.ref(pm, nw, :bus, trans["t_bus"], "base_kv") + bkv_fr = ref(pm, nw, :bus, trans["f_bus"], "base_kv") + bkv_to = ref(pm, nw, :bus, trans["t_bus"], "base_kv") Cv_to = trans["config_fr"]["vm_nom"]/trans["config_to"]["vm_nom"]*bkv_to/bkv_fr # compensate for change of LN voltage of a delta winding Cv_to *= vmult diff --git a/src/core/types.jl b/src/core/types.jl index 422d0c484..b50488406 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -1,9 +1,9 @@ "" -abstract type AbstractNLPUBFModel <: _PMs.AbstractBFQPModel end +abstract type AbstractNLPUBFModel <: _PM.AbstractBFQPModel end "" -abstract type AbstractConicUBFModel <: _PMs.AbstractBFConicModel end +abstract type AbstractConicUBFModel <: _PM.AbstractBFConicModel end AbstractUBFModels = Union{AbstractNLPUBFModel, AbstractConicUBFModel} @@ -41,21 +41,21 @@ const LinDist3FlowModel = LPUBFDiagModel # more popular name for it "default SDP unbalanced DistFlow constructor" -mutable struct SDPUBFPowerModel <: SDPUBFModel _PMs.@pm_fields end +mutable struct SDPUBFPowerModel <: SDPUBFModel _PM.@pm_fields end "default SDP unbalanced DistFlow with matrix KCL constructor" -mutable struct SDPUBFKCLMXPowerModel <: SDPUBFKCLMXModel _PMs.@pm_fields end +mutable struct SDPUBFKCLMXPowerModel <: SDPUBFKCLMXModel _PM.@pm_fields end "default SOC unbalanced DistFlow constructor" -mutable struct SOCNLPUBFPowerModel <: SOCNLPUBFModel _PMs.@pm_fields end +mutable struct SOCNLPUBFPowerModel <: SOCNLPUBFModel _PM.@pm_fields end "default SOC unbalanced DistFlow constructor" -mutable struct SOCConicUBFPowerModel <: SOCConicUBFModel _PMs.@pm_fields end +mutable struct SOCConicUBFPowerModel <: SOCConicUBFModel _PM.@pm_fields end "default LP unbalanced DistFlow constructor" -mutable struct LPUBFDiagPowerModel <: LPUBFDiagModel _PMs.@pm_fields end +mutable struct LPUBFDiagPowerModel <: LPUBFDiagModel _PM.@pm_fields end const LinDist3FlowPowerModel = LPUBFDiagPowerModel # more popular name diff --git a/src/core/variable.jl b/src/core/variable.jl index b6f4941c8..5ed27d6c9 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -9,37 +9,37 @@ end function comp_start_value(comp::Dict{String,<:Any}, key::String, default) - return _PMs.comp_start_value(comp, key, default) + return _PM.comp_start_value(comp, key, default) end "" -function variable_mc_voltage_angle(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_voltage_angle(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - va = _PMs.var(pm, nw)[:va] = Dict(i => JuMP.@variable(pm.model, + va = var(pm, nw)[:va] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_va_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "va_start", 0.0) - ) for i in _PMs.ids(pm, nw, :bus) + start = comp_start_value(ref(pm, nw, :bus, i), "va_start", 0.0) + ) for i in ids(pm, nw, :bus) ) - report && _PMs.sol_component_value(pm, nw, :bus, :va, _PMs.ids(pm, nw, :bus), va) + report && _IM.sol_component_value(pm, nw, :bus, :va, ids(pm, nw, :bus), va) end "" -function variable_mc_voltage_magnitude(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_voltage_magnitude(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - vm = _PMs.var(pm, nw)[:vm] = Dict(i => JuMP.@variable(pm.model, + vm = var(pm, nw)[:vm] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_vm_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "vm_start", c, 1.0) - ) for i in _PMs.ids(pm, nw, :bus) + start = comp_start_value(ref(pm, nw, :bus, i), "vm_start", c, 1.0) + ) for i in ids(pm, nw, :bus) ) if bounded - for (i,bus) in _PMs.ref(pm, nw, :bus) + for (i,bus) in ref(pm, nw, :bus) if haskey(bus, "vmin") set_lower_bound.(vm[i], bus["vmin"]) end @@ -49,22 +49,22 @@ function variable_mc_voltage_magnitude(pm::_PMs.AbstractPowerModel; nw::Int=pm.c end end - report && _PMs.sol_component_value(pm, nw, :bus, :vm, _PMs.ids(pm, nw, :bus), vm) + report && _IM.sol_component_value(pm, nw, :bus, :vm, ids(pm, nw, :bus), vm) end "" -function variable_mc_voltage_real(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_voltage_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - vr = _PMs.var(pm, nw)[:vr] = Dict(i => JuMP.@variable(pm.model, + vr = var(pm, nw)[:vr] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_vr_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "vr_start", c, 0.0) - ) for i in _PMs.ids(pm, nw, :bus) + start = comp_start_value(ref(pm, nw, :bus, i), "vr_start", c, 0.0) + ) for i in ids(pm, nw, :bus) ) if bounded - for (i,bus) in _PMs.ref(pm, nw, :bus) + for (i,bus) in ref(pm, nw, :bus) if haskey(bus, "vmax") set_lower_bound.(vr[i], -bus["vmax"]) set_upper_bound.(vr[i], bus["vmax"]) @@ -72,22 +72,22 @@ function variable_mc_voltage_real(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, b end end - report && _PMs.sol_component_value(pm, nw, :bus, :vr, _PMs.ids(pm, nw, :bus), vr) + report && _IM.sol_component_value(pm, nw, :bus, :vr, ids(pm, nw, :bus), vr) end "" -function variable_mc_voltage_imaginary(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_voltage_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - vi = _PMs.var(pm, nw)[:vi] = Dict(i => JuMP.@variable(pm.model, + vi = var(pm, nw)[:vi] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_vi_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "vi_start", c, 0.0) - ) for i in _PMs.ids(pm, nw, :bus) + start = comp_start_value(ref(pm, nw, :bus, i), "vi_start", c, 0.0) + ) for i in ids(pm, nw, :bus) ) if bounded - for (i,bus) in _PMs.ref(pm, nw, :bus) + for (i,bus) in ref(pm, nw, :bus) if haskey(bus, "vmax") set_lower_bound.(vi[i], -bus["vmax"]) set_upper_bound.(vi[i], bus["vmax"]) @@ -95,36 +95,36 @@ function variable_mc_voltage_imaginary(pm::_PMs.AbstractPowerModel; nw::Int=pm.c end end - report && _PMs.sol_component_value(pm, nw, :bus, :vi, _PMs.ids(pm, nw, :bus), vi) + report && _IM.sol_component_value(pm, nw, :bus, :vi, ids(pm, nw, :bus), vi) end "branch flow variables, delegated back to PowerModels" -function variable_mc_branch_flow(pm::_PMs.AbstractPowerModel; kwargs...) +function variable_mc_branch_flow(pm::_PM.AbstractPowerModel; kwargs...) variable_mc_branch_flow_active(pm; kwargs...) variable_mc_branch_flow_reactive(pm; kwargs...) end "variable: `p[l,i,j]` for `(l,i,j)` in `arcs`" -function variable_mc_branch_flow_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_branch_flow_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - p = _PMs.var(pm, nw)[:p] = Dict((l,i,j) => JuMP.@variable(pm.model, + p = var(pm, nw)[:p] = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_p_$((l,i,j))", - start = comp_start_value(_PMs.ref(pm, nw, :branch, l), "p_start", c, 0.0) - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs) + start = comp_start_value(ref(pm, nw, :branch, l), "p_start", c, 0.0) + ) for (l,i,j) in ref(pm, nw, :arcs) ) if bounded - for (l,i,j) in _PMs.ref(pm, nw, :arcs) - smax = _calc_branch_power_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + for (l,i,j) in ref(pm, nw, :arcs) + smax = _calc_branch_power_max(ref(pm, nw, :branch, l), ref(pm, nw, :bus, i)) set_upper_bound.(p[(l,i,j)], smax) set_lower_bound.(p[(l,i,j)], -smax) end end - for (l,branch) in _PMs.ref(pm, nw, :branch) + for (l,branch) in ref(pm, nw, :branch) if haskey(branch, "pf_start") f_idx = (l, branch["f_bus"], branch["t_bus"]) set_start_value(p[f_idx], branch["pf_start"]) @@ -135,29 +135,29 @@ function variable_mc_branch_flow_active(pm::_PMs.AbstractPowerModel; nw::Int=pm. end end - report && _PMs.sol_component_value_edge(pm, nw, :branch, :pf, :pt, _PMs.ref(pm, nw, :arcs_from), _PMs.ref(pm, nw, :arcs_to), p) + report && _IM.sol_component_value_edge(pm, nw, :branch, :pf, :pt, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), p) end "variable: `q[l,i,j]` for `(l,i,j)` in `arcs`" -function variable_mc_branch_flow_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_branch_flow_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - q = _PMs.var(pm, nw)[:q] = Dict((l,i,j) => JuMP.@variable(pm.model, + q = var(pm, nw)[:q] = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_q_$((l,i,j))", - start = comp_start_value(_PMs.ref(pm, nw, :branch, l), "q_start", c, 0.0) - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs) + start = comp_start_value(ref(pm, nw, :branch, l), "q_start", c, 0.0) + ) for (l,i,j) in ref(pm, nw, :arcs) ) if bounded - for (l,i,j) in _PMs.ref(pm, nw, :arcs) - smax = _calc_branch_power_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + for (l,i,j) in ref(pm, nw, :arcs) + smax = _calc_branch_power_max(ref(pm, nw, :branch, l), ref(pm, nw, :bus, i)) set_upper_bound.(q[(l,i,j)], smax) set_lower_bound.(q[(l,i,j)], -smax) end end - for (l,branch) in _PMs.ref(pm, nw, :branch) + for (l,branch) in ref(pm, nw, :branch) if haskey(branch, "qf_start") f_idx = (l, branch["f_bus"], branch["t_bus"]) set_start_value(q[f_idx], branch["qf_start"]) @@ -168,128 +168,128 @@ function variable_mc_branch_flow_reactive(pm::_PMs.AbstractPowerModel; nw::Int=p end end - report && _PMs.sol_component_value_edge(pm, nw, :branch, :qf, :qt, _PMs.ref(pm, nw, :arcs_from), _PMs.ref(pm, nw, :arcs_to), q) + report && _IM.sol_component_value_edge(pm, nw, :branch, :qf, :qt, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), q) end "variable: `cr[l,i,j]` for `(l,i,j)` in `arcs`" -function variable_mc_branch_current_real(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - branch = _PMs.ref(pm, nw, :branch) - bus = _PMs.ref(pm, nw, :bus) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_branch_current_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + branch = ref(pm, nw, :branch) + bus = ref(pm, nw, :bus) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - cr = _PMs.var(pm, nw)[:cr] = Dict((l,i,j) => JuMP.@variable(pm.model, + cr = var(pm, nw)[:cr] = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_cr_$((l,i,j))", - start = comp_start_value(_PMs.ref(pm, nw, :branch, l), "cr_start", c, 0.0) - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs) + start = comp_start_value(ref(pm, nw, :branch, l), "cr_start", c, 0.0) + ) for (l,i,j) in ref(pm, nw, :arcs) ) if bounded - for (l,i,j) in _PMs.ref(pm, nw, :arcs) - cmax = _calc_branch_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + for (l,i,j) in ref(pm, nw, :arcs) + cmax = _calc_branch_current_max(ref(pm, nw, :branch, l), ref(pm, nw, :bus, i)) set_upper_bound.(cr[(l,i,j)], cmax) set_lower_bound.(cr[(l,i,j)], -cmax) end end - report && _PMs.sol_component_value_edge(pm, nw, :branch, :cr_fr, :cr_to, _PMs.ref(pm, nw, :arcs_from), _PMs.ref(pm, nw, :arcs_to), cr) + report && _IM.sol_component_value_edge(pm, nw, :branch, :cr_fr, :cr_to, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), cr) end "variable: `ci[l,i,j] ` for `(l,i,j)` in `arcs`" -function variable_mc_branch_current_imaginary(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - branch = _PMs.ref(pm, nw, :branch) - bus = _PMs.ref(pm, nw, :bus) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_branch_current_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + branch = ref(pm, nw, :branch) + bus = ref(pm, nw, :bus) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - ci = _PMs.var(pm, nw)[:ci] = Dict((l,i,j) => JuMP.@variable(pm.model, + ci = var(pm, nw)[:ci] = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_ci_$((l,i,j))", - start = comp_start_value(_PMs.ref(pm, nw, :branch, l), "ci_start", c, 0.0) - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs) + start = comp_start_value(ref(pm, nw, :branch, l), "ci_start", c, 0.0) + ) for (l,i,j) in ref(pm, nw, :arcs) ) if bounded - for (l,i,j) in _PMs.ref(pm, nw, :arcs) - cmax = _calc_branch_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + for (l,i,j) in ref(pm, nw, :arcs) + cmax = _calc_branch_current_max(ref(pm, nw, :branch, l), ref(pm, nw, :bus, i)) set_upper_bound.(ci[(l,i,j)], cmax) set_lower_bound.(ci[(l,i,j)], -cmax) end end - report && _PMs.sol_component_value_edge(pm, nw, :branch, :ci_fr, :ci_to, _PMs.ref(pm, nw, :arcs_from), _PMs.ref(pm, nw, :arcs_to), ci) + report && _IM.sol_component_value_edge(pm, nw, :branch, :ci_fr, :ci_to, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), ci) end "variable: `csr[l]` for `l` in `branch`" -function variable_mc_branch_series_current_real(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - branch = _PMs.ref(pm, nw, :branch) - bus = _PMs.ref(pm, nw, :bus) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_branch_series_current_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + branch = ref(pm, nw, :branch) + bus = ref(pm, nw, :bus) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - csr = _PMs.var(pm, nw)[:csr] = Dict(l => JuMP.@variable(pm.model, + csr = var(pm, nw)[:csr] = Dict(l => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_csr_$(l)", - start = comp_start_value(_PMs.ref(pm, nw, :branch, l), "csr_start", c, 0.0) - ) for l in _PMs.ids(pm, nw, :branch) + start = comp_start_value(ref(pm, nw, :branch, l), "csr_start", c, 0.0) + ) for l in ids(pm, nw, :branch) ) if bounded - for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) - cmax = _calc_branch_series_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + for (l,i,j) in ref(pm, nw, :arcs_from) + cmax = _calc_branch_series_current_max(ref(pm, nw, :branch, l), ref(pm, nw, :bus, i), ref(pm, nw, :bus, j)) set_upper_bound.(csr[l], cmax) set_lower_bound.(csr[l], -cmax) end end - report && _PMs.sol_component_value(pm, nw, :branch, :csr_fr, _PMs.ids(pm, nw, :branch), csr) + report && _IM.sol_component_value(pm, nw, :branch, :csr_fr, ids(pm, nw, :branch), csr) end "variable: `csi[l]` for `l` in `branch`" -function variable_mc_branch_series_current_imaginary(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - branch = _PMs.ref(pm, nw, :branch) - bus = _PMs.ref(pm, nw, :bus) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_branch_series_current_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + branch = ref(pm, nw, :branch) + bus = ref(pm, nw, :bus) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - csi = _PMs.var(pm, nw)[:csi] = Dict(l => JuMP.@variable(pm.model, + csi = var(pm, nw)[:csi] = Dict(l => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_csi_$(l)", - start = comp_start_value(_PMs.ref(pm, nw, :branch, l), "csi_start", c, 0.0) - ) for l in _PMs.ids(pm, nw, :branch) + start = comp_start_value(ref(pm, nw, :branch, l), "csi_start", c, 0.0) + ) for l in ids(pm, nw, :branch) ) if bounded - for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) - cmax = _calc_branch_series_current_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + for (l,i,j) in ref(pm, nw, :arcs_from) + cmax = _calc_branch_series_current_max(ref(pm, nw, :branch, l), ref(pm, nw, :bus, i), ref(pm, nw, :bus, j)) set_upper_bound.(csi[l], cmax) set_lower_bound.(csi[l], -cmax) end end - report && _PMs.sol_component_value(pm, nw, :branch, :csi_fr, _PMs.ids(pm, nw, :branch), csi) + report && _IM.sol_component_value(pm, nw, :branch, :csi_fr, ids(pm, nw, :branch), csi) end "variable: `cr[l,i,j]` for `(l,i,j)` in `arcs`" -function variable_mc_transformer_current_real(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - #trans = _PMs.ref(pm, nw, :transformer) - #bus = _PMs.ref(pm, nw, :bus) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_transformer_current_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + #trans = ref(pm, nw, :transformer) + #bus = ref(pm, nw, :bus) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - cr = _PMs.var(pm, nw)[:crt] = Dict((l,i,j) => JuMP.@variable(pm.model, + cr = var(pm, nw)[:crt] = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_crt_$((l,i,j))", - start = comp_start_value(_PMs.ref(pm, nw, :transformer, l), "cr_start", c, 0.0) - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs_trans) + start = comp_start_value(ref(pm, nw, :transformer, l), "cr_start", c, 0.0) + ) for (l,i,j) in ref(pm, nw, :arcs_trans) ) if bounded - for (l,i,j) in _PMs.ref(pm, nw, :arcs_from_trans) - trans = _PMs.ref(pm, nw, :transformer, l) - f_bus = _PMs.ref(pm, nw, :bus, i) - t_bus = _PMs.ref(pm, nw, :bus, j) + for (l,i,j) in ref(pm, nw, :arcs_from_trans) + trans = ref(pm, nw, :transformer, l) + f_bus = ref(pm, nw, :bus, i) + t_bus = ref(pm, nw, :bus, j) cmax_fr, cmax_to = _calc_transformer_current_max_frto(trans, f_bus, t_bus) set_lower_bound(cr[(l,i,j)], -cmax_fr) set_upper_bound(cr[(l,i,j)], cmax_fr) @@ -298,28 +298,28 @@ function variable_mc_transformer_current_real(pm::_PMs.AbstractPowerModel; nw::I end end - report && _PMs.sol_component_value_edge(pm, nw, :transformer, :cr_fr, :cr_to, _PMs.ref(pm, nw, :arcs_from_trans), _PMs.ref(pm, nw, :arcs_to_trans), cr) + report && _IM.sol_component_value_edge(pm, nw, :transformer, :cr_fr, :cr_to, ref(pm, nw, :arcs_from_trans), ref(pm, nw, :arcs_to_trans), cr) end "variable: `ci[l,i,j] ` for `(l,i,j)` in `arcs`" -function variable_mc_transformer_current_imaginary(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - #trans = _PMs.ref(pm, nw, :transformer) - #bus = _PMs.ref(pm, nw, :bus) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_transformer_current_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + #trans = ref(pm, nw, :transformer) + #bus = ref(pm, nw, :bus) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - ci = _PMs.var(pm, nw)[:cit] = Dict((l,i,j) => JuMP.@variable(pm.model, + ci = var(pm, nw)[:cit] = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_cit_$((l,i,j))", - start = comp_start_value(_PMs.ref(pm, nw, :transformer, l), "ci_start", c, 0.0) - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs_trans) + start = comp_start_value(ref(pm, nw, :transformer, l), "ci_start", c, 0.0) + ) for (l,i,j) in ref(pm, nw, :arcs_trans) ) if bounded - for (l,i,j) in _PMs.ref(pm, nw, :arcs_from_trans) - trans = _PMs.ref(pm, nw, :transformer, l) - f_bus = _PMs.ref(pm, nw, :bus, i) - t_bus = _PMs.ref(pm, nw, :bus, j) + for (l,i,j) in ref(pm, nw, :arcs_from_trans) + trans = ref(pm, nw, :transformer, l) + f_bus = ref(pm, nw, :bus, i) + t_bus = ref(pm, nw, :bus, j) cmax_fr, cmax_to = _calc_transformer_current_max_frto(trans, f_bus, t_bus) set_lower_bound(ci[(l,i,j)], -cmax_fr) set_upper_bound(ci[(l,i,j)], cmax_fr) @@ -328,32 +328,32 @@ function variable_mc_transformer_current_imaginary(pm::_PMs.AbstractPowerModel; end end - report && _PMs.sol_component_value_edge(pm, nw, :transformer, :ci_fr, :ci_to, _PMs.ref(pm, nw, :arcs_from_trans), _PMs.ref(pm, nw, :arcs_to_trans), ci) + report && _IM.sol_component_value_edge(pm, nw, :transformer, :ci_fr, :ci_to, ref(pm, nw, :arcs_from_trans), ref(pm, nw, :arcs_to_trans), ci) end # "voltage variables, relaxed form" -# function variable_mc_voltage(pm::_PMs.AbstractWRModel; kwargs...) +# function variable_mc_voltage(pm::_PM.AbstractWRModel; kwargs...) # variable_mc_voltage_magnitude_sqr(pm; kwargs...) # variable_mc_voltage_product(pm; kwargs...) # end "variable: `w[i] >= 0` for `i` in `buses" -function variable_mc_voltage_magnitude_sqr(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_voltage_magnitude_sqr(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - w = _PMs.var(pm, nw)[:w] = Dict(i => JuMP.@variable(pm.model, + w = var(pm, nw)[:w] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_w_$(i)", lower_bound = 0.0, - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "w_start", 1.0) - ) for i in _PMs.ids(pm, nw, :bus) + start = comp_start_value(ref(pm, nw, :bus, i), "w_start", 1.0) + ) for i in ids(pm, nw, :bus) ) if bounded - for (i,bus) in _PMs.ref(pm, nw, :bus) + for (i,bus) in ref(pm, nw, :bus) if haskey(bus, "vmin") set_lower_bound.(w[i], bus["vmin"].^2) end @@ -363,35 +363,35 @@ function variable_mc_voltage_magnitude_sqr(pm::_PMs.AbstractPowerModel; nw::Int= end end - report && _PMs.sol_component_value(pm, nw, :bus, :w, _PMs.ids(pm, nw, :bus), w) + report && _IM.sol_component_value(pm, nw, :bus, :w, ids(pm, nw, :bus), w) end "variables for modeling storage units, includes grid injection and internal variables" -function variable_mc_storage(pm::_PMs.AbstractPowerModel; kwargs...) +function variable_mc_storage(pm::_PM.AbstractPowerModel; kwargs...) variable_mc_storage_active(pm; kwargs...) variable_mc_storage_reactive(pm; kwargs...) - _PMs.variable_storage_energy(pm; kwargs...) - _PMs.variable_storage_charge(pm; kwargs...) - _PMs.variable_storage_discharge(pm; kwargs...) + _PM.variable_storage_energy(pm; kwargs...) + _PM.variable_storage_charge(pm; kwargs...) + _PM.variable_storage_discharge(pm; kwargs...) end -function variable_mc_storage_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_storage_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - ps = _PMs.var(pm, nw)[:ps] = Dict(i => JuMP.@variable(pm.model, + ps = var(pm, nw)[:ps] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_ps_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :storage, i), "ps_start", c, 0.0) - ) for i in _PMs.ids(pm, nw, :storage) + start = comp_start_value(ref(pm, nw, :storage, i), "ps_start", c, 0.0) + ) for i in ids(pm, nw, :storage) ) if bounded for c in cnds - flow_lb, flow_ub = _PMs.ref_calc_storage_injection_bounds(_PMs.ref(pm, nw, :storage), _PMs.ref(pm, nw, :bus), c) + flow_lb, flow_ub = _PM.ref_calc_storage_injection_bounds(ref(pm, nw, :storage), ref(pm, nw, :bus), c) - for i in _PMs.ids(pm, nw, :storage) + for i in ids(pm, nw, :storage) if !isinf(flow_lb[i]) set_lower_bound(ps[i][c], flow_lb[i]) end @@ -402,24 +402,24 @@ function variable_mc_storage_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, end end - report && _PMs.sol_component_value(pm, nw, :storage, :ps, _PMs.ids(pm, nw, :storage), ps) + report && _IM.sol_component_value(pm, nw, :storage, :ps, ids(pm, nw, :storage), ps) end -function variable_mc_storage_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_storage_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - qs = _PMs.var(pm, nw)[:qs] = Dict(i => JuMP.@variable(pm.model, + qs = var(pm, nw)[:qs] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_qs_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :storage, i), "qs_start", c, 0.0) - ) for i in _PMs.ids(pm, nw, :storage) + start = comp_start_value(ref(pm, nw, :storage, i), "qs_start", c, 0.0) + ) for i in ids(pm, nw, :storage) ) if bounded for c in cnds - flow_lb, flow_ub = _PMs.ref_calc_storage_injection_bounds(_PMs.ref(pm, nw, :storage), _PMs.ref(pm, nw, :bus), c) + flow_lb, flow_ub = _PM.ref_calc_storage_injection_bounds(ref(pm, nw, :storage), ref(pm, nw, :bus), c) - for i in _PMs.ids(pm, nw, :storage) + for i in ids(pm, nw, :storage) if !isinf(flow_lb[i]) set_lower_bound(qs[i][c], flow_lb[i]) end @@ -430,69 +430,69 @@ function variable_mc_storage_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm.cn end end - report && _PMs.sol_component_value(pm, nw, :storage, :qs, _PMs.ids(pm, nw, :storage), qs) + report && _IM.sol_component_value(pm, nw, :storage, :qs, ids(pm, nw, :storage), qs) end "generates variables for both `active` and `reactive` slack at each bus" -function variable_mc_bus_power_slack(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) +function variable_mc_bus_power_slack(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) variable_mc_active_bus_power_slack(pm; nw=nw, kwargs...) variable_mc_reactive_bus_power_slack(pm; nw=nw, kwargs...) end "" -function variable_mc_active_bus_power_slack(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_active_bus_power_slack(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - p_slack = _PMs.var(pm, nw)[:p_slack] = Dict(i => JuMP.@variable(pm.model, + p_slack = var(pm, nw)[:p_slack] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_p_slack_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "p_slack_start", cnd, 0.0) - ) for i in _PMs.ids(pm, nw, :bus) + start = comp_start_value(ref(pm, nw, :bus, i), "p_slack_start", cnd, 0.0) + ) for i in ids(pm, nw, :bus) ) - report && _PMs.sol_component_value(pm, nw, :bus, :p_slack, _PMs.ids(pm, nw, :bus), p_slack) + report && _IM.sol_component_value(pm, nw, :bus, :p_slack, ids(pm, nw, :bus), p_slack) end "" -function variable_mc_reactive_bus_power_slack(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_reactive_bus_power_slack(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - q_slack = _PMs.var(pm, nw)[:q_slack] = Dict(i => JuMP.@variable(pm.model, + q_slack = var(pm, nw)[:q_slack] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_q_slack_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "q_slack_start", cnd, 0.0) - ) for i in _PMs.ids(pm, nw, :bus) + start = comp_start_value(ref(pm, nw, :bus, i), "q_slack_start", cnd, 0.0) + ) for i in ids(pm, nw, :bus) ) - report && _PMs.sol_component_value(pm, nw, :bus, :q_slack, _PMs.ids(pm, nw, :bus), q_slack) + report && _IM.sol_component_value(pm, nw, :bus, :q_slack, ids(pm, nw, :bus), q_slack) end "Creates variables for both `active` and `reactive` power flow at each transformer." -function variable_mc_transformer_flow(pm::_PMs.AbstractPowerModel; kwargs...) +function variable_mc_transformer_flow(pm::_PM.AbstractPowerModel; kwargs...) variable_mc_transformer_flow_active(pm; kwargs...) variable_mc_transformer_flow_reactive(pm; kwargs...) end "Create variables for the active power flowing into all transformer windings." -function variable_mc_transformer_flow_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_transformer_flow_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - pt = _PMs.var(pm, nw)[:pt] = Dict((l,i,j) => JuMP.@variable(pm.model, + pt = var(pm, nw)[:pt] = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_pt_$((l,i,j))", - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs_trans) + ) for (l,i,j) in ref(pm, nw, :arcs_trans) ) if bounded - for arc in _PMs.ref(pm, nw, :arcs_from_trans) + for arc in ref(pm, nw, :arcs_from_trans) (t,i,j) = arc - rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(ref(pm, nw, :transformer, t), ref(pm, nw, :bus, i), ref(pm, nw, :bus, j)) set_lower_bound.(pt[(t,i,j)], -rate_a_fr) set_upper_bound.(pt[(t,i,j)], rate_a_fr) set_lower_bound.(pt[(t,j,i)], -rate_a_fr) @@ -500,7 +500,7 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractPowerModel; nw::In end end - for (l,transformer) in _PMs.ref(pm, nw, :transformer) + for (l,transformer) in ref(pm, nw, :transformer) if haskey(transformer, "pf_start") f_idx = (l, transformer["f_bus"], transformer["t_bus"]) set_start_value(pt[f_idx], branch["pf_start"]) @@ -511,25 +511,25 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractPowerModel; nw::In end end - report && _PMs.sol_component_value_edge(pm, nw, :transformer, :pf, :pt, _PMs.ref(pm, nw, :arcs_from_trans), _PMs.ref(pm, nw, :arcs_to_trans), pt) + report && _IM.sol_component_value_edge(pm, nw, :transformer, :pf, :pt, ref(pm, nw, :arcs_from_trans), ref(pm, nw, :arcs_to_trans), pt) end "Create variables for the reactive power flowing into all transformer windings." -function variable_mc_transformer_flow_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_transformer_flow_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - qt = _PMs.var(pm, nw)[:qt] = Dict((l,i,j) => JuMP.@variable(pm.model, + qt = var(pm, nw)[:qt] = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_qt_$((l,i,j))", start = 0.0 - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs_trans) + ) for (l,i,j) in ref(pm, nw, :arcs_trans) ) if bounded - for arc in _PMs.ref(pm, nw, :arcs_from_trans) + for arc in ref(pm, nw, :arcs_from_trans) (t,i,j) = arc - rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(ref(pm, nw, :transformer, t), ref(pm, nw, :bus, i), ref(pm, nw, :bus, j)) set_lower_bound.(qt[(t,i,j)], -rate_a_fr) set_upper_bound.(qt[(t,i,j)], rate_a_fr) @@ -538,7 +538,7 @@ function variable_mc_transformer_flow_reactive(pm::_PMs.AbstractPowerModel; nw:: end end - for (l,transformer) in _PMs.ref(pm, nw, :transformer) + for (l,transformer) in ref(pm, nw, :transformer) if haskey(transformer, "qf_start") f_idx = (l, transformer["f_bus"], transformer["t_bus"]) set_start_value(qt[f_idx], branch["qf_start"]) @@ -549,31 +549,31 @@ function variable_mc_transformer_flow_reactive(pm::_PMs.AbstractPowerModel; nw:: end end - report && _PMs.sol_component_value_edge(pm, nw, :transformer, :qf, :qt, _PMs.ref(pm, nw, :arcs_from_trans), _PMs.ref(pm, nw, :arcs_to_trans), qt) + report && _IM.sol_component_value_edge(pm, nw, :transformer, :qf, :qt, ref(pm, nw, :arcs_from_trans), ref(pm, nw, :arcs_to_trans), qt) end "Create tap variables." -function variable_mc_oltc_tap(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_oltc_tap(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) # when extending to 4-wire, this should iterate only over the phase conductors - cnds = _PMs.conductor_ids(pm; nw=nw) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) nph = 3 - p_oltc_ids = [id for (id,trans) in _PMs.ref(pm, nw, :transformer) if !all(trans["fixed"])] - tap = _PMs.var(pm, nw)[:tap] = Dict(i => JuMP.@variable(pm.model, + p_oltc_ids = [id for (id,trans) in ref(pm, nw, :transformer) if !all(trans["fixed"])] + tap = var(pm, nw)[:tap] = Dict(i => JuMP.@variable(pm.model, [p in 1:nph], base_name="$(nw)_tm_$(i)", - start=_PMs.ref(pm, nw, :transformer, i, "tm")[p] + start=ref(pm, nw, :transformer, i, "tm")[p] ) for i in p_oltc_ids) if bounded for tr_id in p_oltc_ids, p in 1:nph - set_lower_bound(_PMs.var(pm, nw)[:tap][tr_id][p], _PMs.ref(pm, nw, :transformer, tr_id, "tm_min")[p]) - set_upper_bound(_PMs.var(pm, nw)[:tap][tr_id][p], _PMs.ref(pm, nw, :transformer, tr_id, "tm_max")[p]) + set_lower_bound(var(pm, nw)[:tap][tr_id][p], ref(pm, nw, :transformer, tr_id, "tm_min")[p]) + set_upper_bound(var(pm, nw)[:tap][tr_id][p], ref(pm, nw, :transformer, tr_id, "tm_max")[p]) end end - report && _PMs.sol_component_value(pm, nw, :transformer, :tap, _PMs.ids(pm, nw, :transformer), tap) + report && _IM.sol_component_value(pm, nw, :transformer, :tap, ids(pm, nw, :transformer), tap) end @@ -582,175 +582,175 @@ Create a dictionary with values of type Any for the load. Depending on the load model, this can be a parameter or a NLexpression. These will be inserted into KCL. """ -function variable_mc_load(pm::_PMs.AbstractPowerModel; nw=pm.cnw, bounded::Bool=true, report::Bool=true) - _PMs.var(pm, nw)[:pd] = Dict{Int, Any}() - _PMs.var(pm, nw)[:qd] = Dict{Int, Any}() - _PMs.var(pm, nw)[:pd_bus] = Dict{Int, Any}() - _PMs.var(pm, nw)[:qd_bus] = Dict{Int, Any}() +function variable_mc_load(pm::_PM.AbstractPowerModel; nw=pm.cnw, bounded::Bool=true, report::Bool=true) + var(pm, nw)[:pd] = Dict{Int, Any}() + var(pm, nw)[:qd] = Dict{Int, Any}() + var(pm, nw)[:pd_bus] = Dict{Int, Any}() + var(pm, nw)[:qd_bus] = Dict{Int, Any}() end "Create variables for demand status" -function variable_mc_indicator_demand(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) +function variable_mc_indicator_demand(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) # this is not indexedon cnd; why used in start value? cnd = 1 if relax - z_demand = _PMs.var(pm, nw)[:z_demand] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :load)], base_name="$(nw)_z_demand", + z_demand = var(pm, nw)[:z_demand] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :load)], base_name="$(nw)_z_demand", lower_bound = 0, upper_bound = 1, - start = comp_start_value(_PMs.ref(pm, nw, :load, i), "z_demand_on_start", 1.0) + start = comp_start_value(ref(pm, nw, :load, i), "z_demand_on_start", 1.0) ) else - z_demand = _PMs.var(pm, nw)[:z_demand] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :load)], base_name="$(nw)_z_demand", + z_demand = var(pm, nw)[:z_demand] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :load)], base_name="$(nw)_z_demand", binary = true, - start = comp_start_value(_PMs.ref(pm, nw, :load, i), "z_demand_on_start", 1.0) + start = comp_start_value(ref(pm, nw, :load, i), "z_demand_on_start", 1.0) ) end # expressions for pd and qd - pd = _PMs.var(pm, nw)[:pd] = Dict(i => _PMs.var(pm, nw)[:z_demand][i].*_PMs.ref(pm, nw, :load, i)["pd"] - for i in _PMs.ids(pm, nw, :load)) - qd = _PMs.var(pm, nw)[:qd] = Dict(i => _PMs.var(pm, nw)[:z_demand][i].*_PMs.ref(pm, nw, :load, i)["qd"] - for i in _PMs.ids(pm, nw, :load)) + pd = var(pm, nw)[:pd] = Dict(i => var(pm, nw)[:z_demand][i].*ref(pm, nw, :load, i)["pd"] + for i in ids(pm, nw, :load)) + qd = var(pm, nw)[:qd] = Dict(i => var(pm, nw)[:z_demand][i].*ref(pm, nw, :load, i)["qd"] + for i in ids(pm, nw, :load)) - report && _PMs.sol_component_value(pm, nw, :load, :status, _PMs.ids(pm, nw, :load), z_demand) - report && _PMs.sol_component_value(pm, nw, :load, :pd, _PMs.ids(pm, nw, :load), pd) - report && _PMs.sol_component_value(pm, nw, :load, :qd, _PMs.ids(pm, nw, :load), qd) + report && _IM.sol_component_value(pm, nw, :load, :status, ids(pm, nw, :load), z_demand) + report && _IM.sol_component_value(pm, nw, :load, :pd, ids(pm, nw, :load), pd) + report && _IM.sol_component_value(pm, nw, :load, :qd, ids(pm, nw, :load), qd) end "Create variables for shunt status" -function variable_mc_indicator_shunt(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, relax=false, report::Bool=true) +function variable_mc_indicator_shunt(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax=false, report::Bool=true) # this is not indexedon cnd; why used in start value? cnd = 1 if relax - z_shunt = _PMs.var(pm, nw)[:z_shunt] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :shunt)], base_name="$(nw)_z_shunt", + z_shunt = var(pm, nw)[:z_shunt] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :shunt)], base_name="$(nw)_z_shunt", lower_bound = 0, upper_bound = 1, - start = comp_start_value(_PMs.ref(pm, nw, :shunt, i), "z_shunt_on_start", 1.0) + start = comp_start_value(ref(pm, nw, :shunt, i), "z_shunt_on_start", 1.0) ) else - z_shunt = _PMs.var(pm, nw)[:z_shunt] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :shunt)], base_name="$(nw)_z_shunt", + z_shunt = var(pm, nw)[:z_shunt] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :shunt)], base_name="$(nw)_z_shunt", binary=true, - start = comp_start_value(_PMs.ref(pm, nw, :shunt, i), "z_shunt_on_start", 1.0) + start = comp_start_value(ref(pm, nw, :shunt, i), "z_shunt_on_start", 1.0) ) end - report && _PMs.sol_component_value(pm, nw, :shunt, :status, _PMs.ids(pm, nw, :shunt), z_shunt) + report && _IM.sol_component_value(pm, nw, :shunt, :status, ids(pm, nw, :shunt), z_shunt) end "Create variables for bus status" -function variable_mc_indicator_bus_voltage(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) +function variable_mc_indicator_bus_voltage(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) if !relax - z_voltage = _PMs.var(pm, nw)[:z_voltage] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :bus)], base_name="$(nw)_z_voltage", + z_voltage = var(pm, nw)[:z_voltage] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :bus)], base_name="$(nw)_z_voltage", binary = true, - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "z_voltage_start", 1.0) + start = comp_start_value(ref(pm, nw, :bus, i), "z_voltage_start", 1.0) ) else - z_voltage =_PMs.var(pm, nw)[:z_voltage] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :bus)], base_name="$(nw)_z_voltage", + z_voltage =var(pm, nw)[:z_voltage] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :bus)], base_name="$(nw)_z_voltage", lower_bound = 0, upper_bound = 1, - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "z_voltage_start", 1.0) + start = comp_start_value(ref(pm, nw, :bus, i), "z_voltage_start", 1.0) ) end - report && _PMs.sol_component_value(pm, nw, :bus, :status, _PMs.ids(pm, nw, :bus), z_voltage) + report && _IM.sol_component_value(pm, nw, :bus, :status, ids(pm, nw, :bus), z_voltage) end "Create variables for generator status" -function variable_mc_indicator_generation(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) +function variable_mc_indicator_generation(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) if !relax - z_gen = _PMs.var(pm, nw)[:z_gen] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :gen)], base_name="$(nw)_z_gen", + z_gen = var(pm, nw)[:z_gen] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :gen)], base_name="$(nw)_z_gen", binary = true, - start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "z_gen_start", 1.0) + start = comp_start_value(ref(pm, nw, :gen, i), "z_gen_start", 1.0) ) else - z_gen = _PMs.var(pm, nw)[:z_gen] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :gen)], base_name="$(nw)_z_gen", + z_gen = var(pm, nw)[:z_gen] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :gen)], base_name="$(nw)_z_gen", lower_bound = 0, upper_bound = 1, - start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "z_gen_start", 1.0) + start = comp_start_value(ref(pm, nw, :gen, i), "z_gen_start", 1.0) ) end - report && _PMs.sol_component_value(pm, nw, :gen, :gen_status, _PMs.ids(pm, nw, :gen), z_gen) + report && _IM.sol_component_value(pm, nw, :gen, :gen_status, ids(pm, nw, :gen), z_gen) end "Create variables for storage status" -function variable_mc_indicator_storage(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) +function variable_mc_indicator_storage(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) if !relax - z_storage = _PMs.var(pm, nw)[:z_storage] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :storage)], base_name="$(nw)-z_storage", + z_storage = var(pm, nw)[:z_storage] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :storage)], base_name="$(nw)-z_storage", binary = true, - start = comp_start_value(_PMs.ref(pm, nw, :storage, i), "z_storage_start", 1.0) + start = comp_start_value(ref(pm, nw, :storage, i), "z_storage_start", 1.0) ) else - z_storage = _PMs.var(pm, nw)[:z_storage] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, nw, :storage)], base_name="$(nw)_z_storage", + z_storage = var(pm, nw)[:z_storage] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :storage)], base_name="$(nw)_z_storage", lower_bound = 0, upper_bound = 1, - start = comp_start_value(_PMs.ref(pm, nw, :storage, i), "z_storage_start", 1.0) + start = comp_start_value(ref(pm, nw, :storage, i), "z_storage_start", 1.0) ) end - report && _PMs.sol_component_value(pm, nw, :storage, :status, _PMs.ids(pm, nw, :storage), z_storage) + report && _IM.sol_component_value(pm, nw, :storage, :status, ids(pm, nw, :storage), z_storage) end "Create variables for `active` and `reactive` storage injection" -function variable_mc_on_off_storage(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) +function variable_mc_on_off_storage(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) variable_mc_on_off_storage_active(pm; nw=nw, kwargs...) variable_mc_on_off_storage_reactive(pm; nw=nw, kwargs...) end "Create variables for `active` storage injection" -function variable_mc_on_off_storage_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_on_off_storage_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - ps = _PMs.var(pm, nw)[:ps] = Dict(i => JuMP.@variable(pm.model, + ps = var(pm, nw)[:ps] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_ps_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :storage, i), "ps_start", cnd, 0.0) - ) for i in _PMs.ids(pm, nw, :storage)) + start = comp_start_value(ref(pm, nw, :storage, i), "ps_start", cnd, 0.0) + ) for i in ids(pm, nw, :storage)) if bounded for cnd in 1:ncnds - inj_lb, inj_ub = _PMs.ref_calc_storage_injection_bounds(_PMs.ref(pm, nw, :storage), _PMs.ref(pm, nw, :bus), cnd) + inj_lb, inj_ub = _PM.ref_calc_storage_injection_bounds(ref(pm, nw, :storage), ref(pm, nw, :bus), cnd) - for (i, strg) in _PMs.ref(pm, nw, :storage) + for (i, strg) in ref(pm, nw, :storage) set_lower_bound.(ps[i], inj_lb[i]) set_upper_bound.(ps[i], inj_ub[i]) end end end - report && _PMs.sol_component_value(pm, nw, :storage, :ps, _PMs.ids(pm, nw, :storage), ps) + report && _IM.sol_component_value(pm, nw, :storage, :ps, ids(pm, nw, :storage), ps) end "Create variables for `reactive` storage injection" -function variable_mc_on_off_storage_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_on_off_storage_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - qs = _PMs.var(pm, nw)[:qs] = Dict(i => JuMP.@variable(pm.model, + qs = var(pm, nw)[:qs] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_qs_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :storage, i), "qs_start", cnd, 0.0) - ) for i in _PMs.ids(pm, nw, :storage)) + start = comp_start_value(ref(pm, nw, :storage, i), "qs_start", cnd, 0.0) + ) for i in ids(pm, nw, :storage)) if bounded - for (i, strg) in _PMs.ref(pm, nw, :storage) + for (i, strg) in ref(pm, nw, :storage) if haskey(strg, "qmin") set_lower_bound.(qs[i], strg["qmin"]) end @@ -761,22 +761,22 @@ function variable_mc_on_off_storage_reactive(pm::_PMs.AbstractPowerModel; nw::In end end - report && _PMs.sol_component_value(pm, nw, :storage, :qs, _PMs.ids(pm, nw, :storage), qs) + report && _IM.sol_component_value(pm, nw, :storage, :qs, ids(pm, nw, :storage), qs) end "voltage variable magnitude squared (relaxed form)" -function variable_mc_voltage_magnitude_sqr_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - w = _PMs.var(pm, nw)[:w] = Dict(i => JuMP.@variable(pm.model, + w = var(pm, nw)[:w] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_w_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "w_start", c, 1.001) - ) for i in _PMs.ids(pm, nw, :bus)) + start = comp_start_value(ref(pm, nw, :bus, i), "w_start", c, 1.001) + ) for i in ids(pm, nw, :bus)) if bounded - for (i, bus) in _PMs.ref(pm, nw, :bus) + for (i, bus) in ref(pm, nw, :bus) set_lower_bound.(w[i], 0.0) if haskey(bus, "vmax") @@ -785,22 +785,22 @@ function variable_mc_voltage_magnitude_sqr_on_off(pm::_PMs.AbstractPowerModel; n end end - report && _PMs.sol_component_value(pm, nw, :bus, :w, _PMs.ids(pm, nw, :bus), w) + report && _IM.sol_component_value(pm, nw, :bus, :w, ids(pm, nw, :bus), w) end "on/off voltage magnitude variable" -function variable_mc_voltage_magnitude_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_voltage_magnitude_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - vm = _PMs.var(pm, nw)[:vm] = Dict(i => JuMP.@variable(pm.model, + vm = var(pm, nw)[:vm] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_vm_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :bus, i), "vm_start", c, 1.0) - ) for i in _PMs.ids(pm, nw, :bus)) + start = comp_start_value(ref(pm, nw, :bus, i), "vm_start", c, 1.0) + ) for i in ids(pm, nw, :bus)) if bounded - for (i, bus) in _PMs.ref(pm, nw, :bus) + for (i, bus) in ref(pm, nw, :bus) set_lower_bound.(vm[i], 0.0) if haskey(bus, "vmax") @@ -809,30 +809,30 @@ function variable_mc_voltage_magnitude_on_off(pm::_PMs.AbstractPowerModel; nw::I end end - report && _PMs.sol_component_value(pm, nw, :bus, :vm, _PMs.ids(pm, nw, :bus), vm) + report && _IM.sol_component_value(pm, nw, :bus, :vm, ids(pm, nw, :bus), vm) end "create variables for generators, delegate to PowerModels" -function variable_mc_generation(pm::_PMs.AbstractPowerModel; kwargs...) +function variable_mc_generation(pm::_PM.AbstractPowerModel; kwargs...) variable_mc_generation_active(pm; kwargs...) variable_mc_generation_reactive(pm; kwargs...) end -function variable_mc_generation_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_generation_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - pg = _PMs.var(pm, nw)[:pg] = Dict(i => JuMP.@variable(pm.model, + pg = var(pm, nw)[:pg] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_pg_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "pg_start", c, 0.0) - ) for i in _PMs.ids(pm, nw, :gen) + start = comp_start_value(ref(pm, nw, :gen, i), "pg_start", c, 0.0) + ) for i in ids(pm, nw, :gen) ) if bounded - for (i,gen) in _PMs.ref(pm, nw, :gen) + for (i,gen) in ref(pm, nw, :gen) if haskey(gen, "pmin") set_lower_bound.(pg[i], gen["pmin"]) end @@ -842,24 +842,24 @@ function variable_mc_generation_active(pm::_PMs.AbstractPowerModel; nw::Int=pm.c end end - _PMs.var(pm, nw)[:pg_bus] = Dict{Int, Any}() + var(pm, nw)[:pg_bus] = Dict{Int, Any}() - report && _PMs.sol_component_value(pm, nw, :gen, :pg, _PMs.ids(pm, nw, :gen), pg) + report && _IM.sol_component_value(pm, nw, :gen, :pg, ids(pm, nw, :gen), pg) end -function variable_mc_generation_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_generation_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - qg = _PMs.var(pm, nw)[:qg] = Dict(i => JuMP.@variable(pm.model, + qg = var(pm, nw)[:qg] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_qg_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "qg_start", c, 0.0) - ) for i in _PMs.ids(pm, nw, :gen) + start = comp_start_value(ref(pm, nw, :gen, i), "qg_start", c, 0.0) + ) for i in ids(pm, nw, :gen) ) if bounded - for (i,gen) in _PMs.ref(pm, nw, :gen) + for (i,gen) in ref(pm, nw, :gen) if haskey(gen, "qmin") set_lower_bound.(qg[i], gen["qmin"]) end @@ -869,76 +869,76 @@ function variable_mc_generation_reactive(pm::_PMs.AbstractPowerModel; nw::Int=pm end end - _PMs.var(pm, nw)[:qg_bus] = Dict{Int, Any}() + var(pm, nw)[:qg_bus] = Dict{Int, Any}() - report && _PMs.sol_component_value(pm, nw, :gen, :qg, _PMs.ids(pm, nw, :gen), qg) + report && _IM.sol_component_value(pm, nw, :gen, :qg, ids(pm, nw, :gen), qg) end "variable: `crg[j]` for `j` in `gen`" -function variable_mc_generation_current_real(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - gen = _PMs.ref(pm, nw, :gen) - bus = _PMs.ref(pm, nw, :bus) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_generation_current_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + gen = ref(pm, nw, :gen) + bus = ref(pm, nw, :bus) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - crg = _PMs.var(pm, nw)[:crg] = Dict(i => JuMP.@variable(pm.model, + crg = var(pm, nw)[:crg] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_crg_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "crg_start", c, 0.0) - ) for i in _PMs.ids(pm, nw, :gen) + start = comp_start_value(ref(pm, nw, :gen, i), "crg_start", c, 0.0) + ) for i in ids(pm, nw, :gen) ) if bounded for (i, g) in gen - cmax = _calc_gen_current_max(g, _PMs.ref(pm, nw, :bus, g["gen_bus"])) + cmax = _calc_gen_current_max(g, ref(pm, nw, :bus, g["gen_bus"])) set_lower_bound.(crg[i], -cmax) set_upper_bound.(crg[i], cmax) end end - report && _PMs.sol_component_value(pm, nw, :gen, :crg, _PMs.ids(pm, nw, :gen), crg) + report && _IM.sol_component_value(pm, nw, :gen, :crg, ids(pm, nw, :gen), crg) end "variable: `cig[j]` for `j` in `gen`" -function variable_mc_generation_current_imaginary(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - gen = _PMs.ref(pm, nw, :gen) - bus = _PMs.ref(pm, nw, :bus) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_generation_current_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + gen = ref(pm, nw, :gen) + bus = ref(pm, nw, :bus) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - cig = _PMs.var(pm, nw)[:cig] = Dict(i => JuMP.@variable(pm.model, + cig = var(pm, nw)[:cig] = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_cig_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "cig_start", c, 0.0) - ) for i in _PMs.ids(pm, nw, :gen) + start = comp_start_value(ref(pm, nw, :gen, i), "cig_start", c, 0.0) + ) for i in ids(pm, nw, :gen) ) if bounded for (i, g) in gen - cmax = _calc_gen_current_max(g, _PMs.ref(pm, nw, :bus, g["gen_bus"])) + cmax = _calc_gen_current_max(g, ref(pm, nw, :bus, g["gen_bus"])) set_lower_bound.(cig[i], -cmax) set_upper_bound.(cig[i], cmax) end end - report && _PMs.sol_component_value(pm, nw, :gen, :cig, _PMs.ids(pm, nw, :gen), cig) + report && _IM.sol_component_value(pm, nw, :gen, :cig, ids(pm, nw, :gen), cig) end -function variable_mc_generation_on_off(pm::_PMs.AbstractPowerModel; kwargs...) +function variable_mc_generation_on_off(pm::_PM.AbstractPowerModel; kwargs...) variable_mc_active_generation_on_off(pm; kwargs...) variable_mc_reactive_generation_on_off(pm; kwargs...) end -function variable_mc_active_generation_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) - ncnds = length(_PMs.conductor_ids(pm, nw)) +function variable_mc_active_generation_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) + ncnds = length(conductor_ids(pm, nw)) - pg = _PMs.var(pm, nw)[:pg] = Dict(i => JuMP.@variable(pm.model, + pg = var(pm, nw)[:pg] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_pg_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "pg_start", cnd, 0.0) - ) for i in _PMs.ids(pm, nw, :gen)) + start = comp_start_value(ref(pm, nw, :gen, i), "pg_start", cnd, 0.0) + ) for i in ids(pm, nw, :gen)) if bounded - for (i, gen) in _PMs.ref(pm, nw, :gen) + for (i, gen) in ref(pm, nw, :gen) if haskey(gen, "pmin") set_lower_bound.(pg[i], gen["pmin"]) end @@ -949,21 +949,21 @@ function variable_mc_active_generation_on_off(pm::_PMs.AbstractPowerModel; nw::I end end - report && _PMs.sol_component_value(pm, nw, :gen, :pg, _PMs.ids(pm, nw, :gen), pg) + report && _IM.sol_component_value(pm, nw, :gen, :pg, ids(pm, nw, :gen), pg) end -function variable_mc_reactive_generation_on_off(pm::_PMs.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - cnds = _PMs.conductor_ids(pm; nw=nw) +function variable_mc_reactive_generation_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - qg = _PMs.var(pm, nw)[:qg] = Dict(i => JuMP.@variable(pm.model, + qg = var(pm, nw)[:qg] = Dict(i => JuMP.@variable(pm.model, [cnd in 1:ncnds], base_name="$(nw)_qg_$(i)", - start = comp_start_value(_PMs.ref(pm, nw, :gen, i), "qg_start", cnd, 0.0) - ) for i in _PMs.ids(pm, nw, :gen)) + start = comp_start_value(ref(pm, nw, :gen, i), "qg_start", cnd, 0.0) + ) for i in ids(pm, nw, :gen)) if bounded - for (i, gen) in _PMs.ref(pm, nw, :gen) + for (i, gen) in ref(pm, nw, :gen) if haskey(gen, "qmin") set_lower_bound.(qg[i], gen["qmin"]) end @@ -974,5 +974,5 @@ function variable_mc_reactive_generation_on_off(pm::_PMs.AbstractPowerModel; nw: end end - report && _PMs.sol_component_value(pm, nw, :gen, :qg, _PMs.ids(pm, nw, :gen), qg) + report && _IM.sol_component_value(pm, nw, :gen, :qg, ids(pm, nw, :gen), qg) end diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 48fa9a60b..12b25952a 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -315,7 +315,7 @@ function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar, data_ sbase_scale_cost = sbase_scale end - _PMs._rescale_cost_model!(gen, 1/sbase_scale_cost) + _PM._rescale_cost_model!(gen, 1/sbase_scale_cost) # save new vbase gen["vbase"] = vbase diff --git a/src/form/acp.jl b/src/form/acp.jl index bd61d9cb5..18e4c0bca 100644 --- a/src/form/acp.jl +++ b/src/form/acp.jl @@ -1,5 +1,5 @@ "" -function variable_mc_voltage(pm::_PMs.AbstractACPModel; nw=pm.cnw, kwargs...) +function variable_mc_voltage(pm::_PM.AbstractACPModel; nw=pm.cnw, kwargs...) variable_mc_voltage_angle(pm; nw=nw, kwargs...) variable_mc_voltage_magnitude(pm; nw=nw, kwargs...) @@ -7,27 +7,27 @@ function variable_mc_voltage(pm::_PMs.AbstractACPModel; nw=pm.cnw, kwargs...) # of voltage phasors. If the voltage phasors at one bus are initialized # in the same point, this would lead to division by zero. - ncnds = length(_PMs.conductor_ids(pm, nw)) + ncnds = length(conductor_ids(pm, nw)) - bus_t1 = [bus for (_, bus) in _PMs.ref(pm, nw, :bus) if bus["bus_type"]==3] + bus_t1 = [bus for (_, bus) in ref(pm, nw, :bus) if bus["bus_type"]==3] if length(bus_t1)>0 theta = bus_t1[1]["va"] else theta = [_wrap_to_pi(2 * pi / ncnds * (1-c)) for c in 1:ncnds] end vm = 1 - for id in _PMs.ids(pm, nw, :bus) - busref = _PMs.ref(pm, nw, :bus, id) + for id in ids(pm, nw, :bus) + busref = ref(pm, nw, :bus, id) if !haskey(busref, "va_start") # if it has this key, it was set at PM level - JuMP.set_start_value.(_PMs.var(pm, nw, :va, id), theta) + JuMP.set_start_value.(var(pm, nw, :va, id), theta) end end end "" -function variable_mc_bus_voltage_on_off(pm::_PMs.AbstractACPModel; kwargs...) +function variable_mc_bus_voltage_on_off(pm::_PM.AbstractACPModel; kwargs...) variable_mc_voltage_angle(pm; kwargs...) variable_mc_voltage_magnitude_on_off(pm; kwargs...) @@ -35,14 +35,14 @@ function variable_mc_bus_voltage_on_off(pm::_PMs.AbstractACPModel; kwargs...) vm = 1 - for id in _PMs.ids(pm, nw, :bus) - busref = _PMs.ref(pm, nw, :bus, id) - ncnd = length(_PMs.conductor_ids(pm, nw)) + for id in ids(pm, nw, :bus) + busref = ref(pm, nw, :bus, id) + ncnd = length(conductor_ids(pm, nw)) theta = [_wrap_to_pi(2 * pi / ncnd * (1-c)) for c in 1:ncnd] if !haskey(busref, "va_start") # if it has this key, it was set at PM level for c in 1:ncnd - JuMP.set_start_value(_PMs.var(pm, nw, :va, id)[c], theta[c]) + JuMP.set_start_value(var(pm, nw, :va, id)[c], theta[c]) end end end @@ -50,23 +50,23 @@ end "" -function constraint_mc_power_balance_slack(pm::_PMs.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) - vm = _PMs.var(pm, nw, :vm, i) - va = _PMs.var(pm, nw, :va, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(_PMs.var(pm, nw), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - p_slack = _PMs.var(pm, nw, :p_slack, i) - q_slack = _PMs.var(pm, nw, :q_slack, i) - - cnds = _PMs.conductor_ids(pm; nw=nw) +function constraint_mc_power_balance_slack(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + vm = var(pm, nw, :vm, i) + va = var(pm, nw, :va, i) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + p_slack = var(pm, nw, :p_slack, i) + q_slack = var(pm, nw, :q_slack, i) + + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) @@ -75,7 +75,7 @@ function constraint_mc_power_balance_slack(pm::_PMs.AbstractACPModel, nw::Int, i cstr_p = [] cstr_q = [] - for c in _PMs.conductor_ids(pm; nw=nw) + for c in conductor_ids(pm; nw=nw) cp = JuMP.@NLconstraint(pm.model, sum(p[a][c] for a in bus_arcs) + sum(psw[a_sw][c] for a_sw in bus_arcs_sw) @@ -114,31 +114,31 @@ function constraint_mc_power_balance_slack(pm::_PMs.AbstractACPModel, nw::Int, i end - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end "" -function constraint_mc_power_balance_shed(pm::_PMs.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) - vm = _PMs.var(pm, nw, :vm, i) - va = _PMs.var(pm, nw, :va, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(_PMs.var(pm, nw), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - z_demand = _PMs.var(pm, nw, :z_demand) - z_shunt = _PMs.var(pm, nw, :z_shunt) - - cnds = _PMs.conductor_ids(pm; nw=nw) +function constraint_mc_power_balance_shed(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + vm = var(pm, nw, :vm, i) + va = var(pm, nw, :va, i) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + z_demand = var(pm, nw, :z_demand) + z_shunt = var(pm, nw, :z_shunt) + + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) @@ -149,7 +149,7 @@ function constraint_mc_power_balance_shed(pm::_PMs.AbstractACPModel, nw::Int, i: bus_GsBs = [(n,bus_gs[n], bus_bs[n]) for n in keys(bus_gs)] - for c in _PMs.conductor_ids(pm; nw=nw) + for c in conductor_ids(pm; nw=nw) cp = JuMP.@NLconstraint(pm.model, sum(p[a][c] for a in bus_arcs) + sum(psw[a_sw][c] for a_sw in bus_arcs_sw) @@ -186,30 +186,30 @@ function constraint_mc_power_balance_shed(pm::_PMs.AbstractACPModel, nw::Int, i: end - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end "" -function constraint_mc_power_balance(pm::_PMs.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) - vm = _PMs.var(pm, nw, :vm, i) - va = _PMs.var(pm, nw, :va, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(_PMs.var(pm, nw), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - - - cnds = _PMs.conductor_ids(pm; nw=nw) +function constraint_mc_power_balance(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + vm = var(pm, nw, :vm, i) + va = var(pm, nw, :va, i) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + + + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) @@ -254,31 +254,31 @@ function constraint_mc_power_balance(pm::_PMs.AbstractACPModel, nw::Int, i::Int, push!(cstr_q, cq) end - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end "" -function constraint_mc_power_balance_load(pm::_PMs.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) - vm = _PMs.var(pm, nw, :vm, i) - va = _PMs.var(pm, nw, :va, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(_PMs.var(pm, nw), :pg_bus, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg_bus, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - pd = get(_PMs.var(pm, nw), :pd_bus, Dict()); _PMs._check_var_keys(pd, bus_loads, "active power", "load") - qd = get(_PMs.var(pm, nw), :qd_bus, Dict()); _PMs._check_var_keys(pd, bus_loads, "reactive power", "load") - - cnds = _PMs.conductor_ids(pm; nw=nw) +function constraint_mc_power_balance_load(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) + vm = var(pm, nw, :vm, i) + va = var(pm, nw, :va, i) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg_bus, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg_bus, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + pd = get(var(pm, nw), :pd_bus, Dict()); _PM._check_var_keys(pd, bus_loads, "active power", "load") + qd = get(var(pm, nw), :qd_bus, Dict()); _PM._check_var_keys(pd, bus_loads, "reactive power", "load") + + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) @@ -287,7 +287,7 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACPModel, nw::Int, i: cstr_p = [] cstr_q = [] - for c in _PMs.conductor_ids(pm; nw=nw) + for c in conductor_ids(pm; nw=nw) cp = JuMP.@NLconstraint(pm.model, sum(p[a][c] for a in bus_arcs) + sum(psw[a_sw][c] for a_sw in bus_arcs_sw) @@ -323,9 +323,9 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACPModel, nw::Int, i: push!(cstr_q, cq) end - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end @@ -336,33 +336,33 @@ Creates Ohms constraints (yt post fix indicates that Y and T values are in recta ``` p_fr == g[c,c] * vm_fr[c]^2 + sum( g[c,d]*vm_fr[c]*vm_fr[d]*cos(va_fr[c]-va_fr[d]) + - b[c,d]*vm_fr[c]*vm_fr[d]*sin(va_fr[c]-va_fr[d]) for d in _PMs.conductor_ids(pm) if d != c) + + b[c,d]*vm_fr[c]*vm_fr[d]*sin(va_fr[c]-va_fr[d]) for d in conductor_ids(pm) if d != c) + sum(-g[c,d]*vm_fr[c]*vm_to[d]*cos(va_fr[c]-va_to[d]) + - -b[c,d]*vm_fr[c]*vm_to[d]*sin(va_fr[c]-va_to[d]) for d in _PMs.conductor_ids(pm)) + -b[c,d]*vm_fr[c]*vm_to[d]*sin(va_fr[c]-va_to[d]) for d in conductor_ids(pm)) + g_fr[c,c] * vm_fr[c]^2 + sum( g_fr[c,d]*vm_fr[c]*vm_fr[d]*cos(va_fr[c]-va_fr[d]) + - b_fr[c,d]*vm_fr[c]*vm_fr[d]*sin(va_fr[c]-va_fr[d]) for d in _PMs.conductor_ids(pm) if d != c) + b_fr[c,d]*vm_fr[c]*vm_fr[d]*sin(va_fr[c]-va_fr[d]) for d in conductor_ids(pm) if d != c) ) q_fr == -b[c,c] *vm_fr[c]^2 - sum( b[c,d]*vm_fr[c]*vm_fr[d]*cos(va_fr[c]-va_fr[d]) - - g[c,d]*vm_fr[c]*vm_fr[d]*sin(va_fr[c]-va_fr[d]) for d in _PMs.conductor_ids(pm) if d != c) - + g[c,d]*vm_fr[c]*vm_fr[d]*sin(va_fr[c]-va_fr[d]) for d in conductor_ids(pm) if d != c) - sum(-b[c,d]*vm_fr[c]*vm_to[d]*cos(va_fr[c]-va_to[d]) + - g[c,d]*vm_fr[c]*vm_to[d]*sin(va_fr[c]-va_to[d]) for d in _PMs.conductor_ids(pm)) + g[c,d]*vm_fr[c]*vm_to[d]*sin(va_fr[c]-va_to[d]) for d in conductor_ids(pm)) -b_fr[c,c] *vm_fr[c]^2 - sum( b_fr[c,d]*vm_fr[c]*vm_fr[d]*cos(va_fr[c]-va_fr[d]) - - g_fr[c,d]*vm_fr[c]*vm_fr[d]*sin(va_fr[c]-va_fr[d]) for d in _PMs.conductor_ids(pm) if d != c) + g_fr[c,d]*vm_fr[c]*vm_fr[d]*sin(va_fr[c]-va_fr[d]) for d in conductor_ids(pm) if d != c) ) ``` """ -function constraint_mc_ohms_yt_from(pm::_PMs.AbstractACPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, tm) - p_fr = _PMs.var(pm, n, :p, f_idx) - q_fr = _PMs.var(pm, n, :q, f_idx) - vm_fr = _PMs.var(pm, n, :vm, f_bus) - vm_to = _PMs.var(pm, n, :vm, t_bus) - va_fr = _PMs.var(pm, n, :va, f_bus) - va_to = _PMs.var(pm, n, :va, t_bus) - - cnds = _PMs.conductor_ids(pm; nw=n) +function constraint_mc_ohms_yt_from(pm::_PM.AbstractACPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, 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) + + cnds = conductor_ids(pm; nw=n) for c in cnds JuMP.@NLconstraint(pm.model, p_fr[c] == (g[c,c]+g_fr[c,c])*vm_fr[c]^2 @@ -393,22 +393,22 @@ p[t_idx] == (g+g_to)*v[t_bus]^2 + (-g*tr-b*ti)/tm*(v[t_bus]*v[f_bus]*cos(t[t_bu q[t_idx] == -(b+b_to)*v[t_bus]^2 - (-b*tr+g*ti)/tm*(v[t_bus]*v[f_bus]*cos(t[f_bus]-t[t_bus])) + (-g*tr-b*ti)/tm*(v[t_bus]*v[f_bus]*sin(t[t_bus]-t[f_bus])) ``` """ -function constraint_mc_ohms_yt_to(pm::_PMs.AbstractACPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) +function constraint_mc_ohms_yt_to(pm::_PM.AbstractACPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) constraint_mc_ohms_yt_from(pm, n, t_bus, f_bus, t_idx, f_idx, g, b, g_to, b_to, tr, ti, tm) end -function constraint_mc_trans_yy(pm::_PMs.AbstractACPModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) - vm_fr = _PMs.var(pm, nw, :vm, f_bus)[f_cnd] - vm_to = _PMs.var(pm, nw, :vm, t_bus)[t_cnd] - va_fr = _PMs.var(pm, nw, :va, f_bus)[f_cnd] - va_to = _PMs.var(pm, nw, :va, t_bus)[t_cnd] +function constraint_mc_trans_yy(pm::_PM.AbstractACPModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) + vm_fr = var(pm, nw, :vm, f_bus)[f_cnd] + vm_to = var(pm, nw, :vm, t_bus)[t_cnd] + va_fr = var(pm, nw, :va, f_bus)[f_cnd] + va_to = var(pm, nw, :va, t_bus)[t_cnd] # construct tm as a parameter or scaled variable depending on whether it is fixed or not - tm = [tm_fixed[p] ? tm_set[p] : _PMs.var(pm, nw, :tap, trans_id)[p] for p in _PMs.conductor_ids(pm)] + tm = [tm_fixed[p] ? tm_set[p] : var(pm, nw, :tap, trans_id)[p] for p in conductor_ids(pm)] - for p in _PMs.conductor_ids(pm) + for p in conductor_ids(pm) if tm_fixed[p] JuMP.@constraint(pm.model, vm_fr[p] == tm_scale*tm[p]*vm_to[p]) else @@ -418,27 +418,27 @@ function constraint_mc_trans_yy(pm::_PMs.AbstractACPModel, nw::Int, trans_id::In JuMP.@constraint(pm.model, va_fr[p] == va_to[p] + pol_angle) end - p_fr = _PMs.var(pm, nw, :pt, f_idx)[f_cnd] - p_to = _PMs.var(pm, nw, :pt, t_idx)[t_cnd] - q_fr = _PMs.var(pm, nw, :qt, f_idx)[f_cnd] - q_to = _PMs.var(pm, nw, :qt, t_idx)[t_cnd] + p_fr = var(pm, nw, :pt, f_idx)[f_cnd] + p_to = var(pm, nw, :pt, t_idx)[t_cnd] + q_fr = var(pm, nw, :qt, f_idx)[f_cnd] + q_to = var(pm, nw, :qt, t_idx)[t_cnd] JuMP.@constraint(pm.model, p_fr + p_to .== 0) JuMP.@constraint(pm.model, q_fr + q_to .== 0) end -function constraint_mc_trans_dy(pm::_PMs.AbstractACPModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) - vm_fr = [_PMs.var(pm, nw, :vm, f_bus)[p] for p in f_cnd] - vm_to = [_PMs.var(pm, nw, :vm, t_bus)[p] for p in t_cnd] - va_fr = [_PMs.var(pm, nw, :va, f_bus)[p] for p in f_cnd] - va_to = [_PMs.var(pm, nw, :va, t_bus)[p] for p in t_cnd] +function constraint_mc_trans_dy(pm::_PM.AbstractACPModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) + vm_fr = [var(pm, nw, :vm, f_bus)[p] for p in f_cnd] + vm_to = [var(pm, nw, :vm, t_bus)[p] for p in t_cnd] + va_fr = [var(pm, nw, :va, f_bus)[p] for p in f_cnd] + va_to = [var(pm, nw, :va, t_bus)[p] for p in t_cnd] # construct tm as a parameter or scaled variable depending on whether it is fixed or not - tm = [tm_fixed[p] ? tm_set[p] : _PMs.var(pm, nw, p, :tap, trans_id) for p in _PMs.conductor_ids(pm)] + tm = [tm_fixed[p] ? tm_set[p] : var(pm, nw, p, :tap, trans_id) for p in conductor_ids(pm)] # introduce auxialiary variable vd = Md*v_fr - nph = length(_PMs.conductor_ids(pm)) + nph = length(conductor_ids(pm)) vd_re = Array{Any,1}(undef, nph) vd_im = Array{Any,1}(undef, nph) for p in 1:nph @@ -451,10 +451,10 @@ function constraint_mc_trans_dy(pm::_PMs.AbstractACPModel, nw::Int, trans_id::In JuMP.@NLconstraint(pm.model, vd_im[p] == pol*tm_scale*tm[p]*vm_to[p]*sin(va_to[p])) end - p_fr = [_PMs.var(pm, nw, :pt, f_idx)[p] for p in f_cnd] - p_to = [_PMs.var(pm, nw, :pt, t_idx)[p] for p in t_cnd] - q_fr = [_PMs.var(pm, nw, :qt, f_idx)[p] for p in f_cnd] - q_to = [_PMs.var(pm, nw, :qt, t_idx)[p] for p in t_cnd] + p_fr = [var(pm, nw, :pt, f_idx)[p] for p in f_cnd] + p_to = [var(pm, nw, :pt, t_idx)[p] for p in t_cnd] + q_fr = [var(pm, nw, :qt, f_idx)[p] for p in f_cnd] + q_to = [var(pm, nw, :qt, t_idx)[p] for p in t_cnd] id_re = Array{Any,1}(undef, nph) id_im = Array{Any,1}(undef, nph) @@ -462,12 +462,12 @@ function constraint_mc_trans_dy(pm::_PMs.AbstractACPModel, nw::Int, trans_id::In # = (p+jq)/|v|*(cos(va)-j*sin(va)) # Re(s/v) = (p*cos(va)+q*sin(va))/|v| # -Im(s/v) = -(q*cos(va)-p*sin(va))/|v| - for p in _PMs.conductor_ids(pm) + for p in conductor_ids(pm) # id = conj(s_to/v_to)./tm id_re[p] = JuMP.@NLexpression(pm.model, (p_to[p]*cos(va_to[p])+q_to[p]*sin(va_to[p]))/vm_to[p]/(tm_scale*tm[p])/pol) id_im[p] = JuMP.@NLexpression(pm.model, -(q_to[p]*cos(va_to[p])-p_to[p]*sin(va_to[p]))/vm_to[p]/(tm_scale*tm[p])/pol) end - for p in _PMs.conductor_ids(pm) + for p in conductor_ids(pm) # rotate by nph-1 to get 'previous' phase # e.g., for nph=3: 1->3, 2->1, 3->2 q = (p-1+nph-1)%nph+1 @@ -494,13 +494,13 @@ vuf = |U-|/|U+| |U-| <= vufmax*|U+| |U-|^2 <= vufmax^2*|U+|^2 """ -function constraint_mc_vm_vuf(pm::_PMs.AbstractACPModel, nw::Int, bus_id::Int, vufmax::Float64) - if !haskey(_PMs.var(pm, pm.cnw), :vmpossqr) - _PMs.var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() - _PMs.var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() +function constraint_mc_vm_vuf(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vufmax::Float64) + if !haskey(var(pm, pm.cnw), :vmpossqr) + var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() + var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() end - (vm_a, vm_b, vm_c) = [_PMs.var(pm, nw, i, :vm, bus_id) for i in 1:3] - (va_a, va_b, va_c) = [_PMs.var(pm, nw, i, :va, bus_id) for i in 1:3] + (vm_a, vm_b, vm_c) = [var(pm, nw, i, :vm, bus_id) for i in 1:3] + (va_a, va_b, va_c) = [var(pm, nw, i, :va, bus_id) for i in 1:3] a = exp(im*2*pi/3) # real and imag functions cannot be used in NLexpressions, so precalculate are = real(a) @@ -541,13 +541,13 @@ vuf = |U-|/|U+| |U-| <= vufmax*|U+| |U-|^2 <= vufmax^2*|U+|^2 """ -function constraint_mc_vm_neg_seq(pm::_PMs.AbstractACPModel, nw::Int, bus_id::Int, vmnegmax::Float64) - if !haskey(_PMs.var(pm, pm.cnw), :vmpossqr) - _PMs.var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() - _PMs.var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() +function constraint_mc_vm_neg_seq(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vmnegmax::Float64) + if !haskey(var(pm, pm.cnw), :vmpossqr) + var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() + var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() end - (vm_a, vm_b, vm_c) = [_PMs.var(pm, nw, i, :vm, bus_id) for i in 1:3] - (va_a, va_b, va_c) = [_PMs.var(pm, nw, i, :va, bus_id) for i in 1:3] + (vm_a, vm_b, vm_c) = [var(pm, nw, i, :vm, bus_id) for i in 1:3] + (va_a, va_b, va_c) = [var(pm, nw, i, :va, bus_id) for i in 1:3] a = exp(im*2*pi/3) # real and imag functions cannot be used in NLexpressions, so precalculate are = real(a) @@ -576,13 +576,13 @@ vuf = |U-|/|U+| |U-| <= vufmax*|U+| |U-|^2 <= vufmax^2*|U+|^2 """ -function constraint_mc_vm_pos_seq(pm::_PMs.AbstractACPModel, nw::Int, bus_id::Int, vmposmax::Float64) - if !haskey(_PMs.var(pm, pm.cnw), :vmpossqr) - _PMs.var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() - _PMs.var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() +function constraint_mc_vm_pos_seq(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vmposmax::Float64) + if !haskey(var(pm, pm.cnw), :vmpossqr) + var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() + var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() end - (vm_a, vm_b, vm_c) = [_PMs.var(pm, nw, i, :vm, bus_id) for i in 1:3] - (va_a, va_b, va_c) = [_PMs.var(pm, nw, i, :va, bus_id) for i in 1:3] + (vm_a, vm_b, vm_c) = [var(pm, nw, i, :vm, bus_id) for i in 1:3] + (va_a, va_b, va_c) = [var(pm, nw, i, :va, bus_id) for i in 1:3] a = exp(im*2*pi/3) # real and imag functions cannot be used in NLexpressions, so precalculate are = real(a) @@ -611,13 +611,13 @@ vuf = |U-|/|U+| |U-| <= vufmax*|U+| |U-|^2 <= vufmax^2*|U+|^2 """ -function constraint_mc_vm_zero_seq(pm::_PMs.AbstractACPModel, nw::Int, bus_id::Int, vmzeromax::Float64) - if !haskey(_PMs.var(pm, pm.cnw), :vmpossqr) - _PMs.var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() - _PMs.var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() +function constraint_mc_vm_zero_seq(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vmzeromax::Float64) + if !haskey(var(pm, pm.cnw), :vmpossqr) + var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() + var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() end - (vm_a, vm_b, vm_c) = [_PMs.var(pm, nw, i, :vm, bus_id) for i in 1:3] - (va_a, va_b, va_c) = [_PMs.var(pm, nw, i, :va, bus_id) for i in 1:3] + (vm_a, vm_b, vm_c) = [var(pm, nw, i, :vm, bus_id) for i in 1:3] + (va_a, va_b, va_c) = [var(pm, nw, i, :va, bus_id) for i in 1:3] # real and imaginary components of U+ vrezero = JuMP.@NLexpression(pm.model, (vm_a*cos(va_a) + vm_b*cos(va_b) + vm_c*cos(va_c))/3 @@ -641,11 +641,11 @@ And then s_a = v_a.conj(i_a) = v_a.conj(i_ab-i_ca) idem for s_b and s_c """ -function constraint_mc_load_current_delta(pm::_PMs.AbstractACPModel, nw::Int, load_id::Int, load_bus_id::Int, cp::Vector, cq::Vector) +function constraint_mc_load_current_delta(pm::_PM.AbstractACPModel, nw::Int, load_id::Int, load_bus_id::Int, cp::Vector, cq::Vector) cp_ab, cp_bc, cp_ca = cp cq_ab, cq_bc, cq_ca = cq - vm_a, vm_b, vm_c = _PMs.var(pm, nw, :vm, load_bus_id) - va_a, va_b, va_c = _PMs.var(pm, nw, :va, load_bus_id) + vm_a, vm_b, vm_c = var(pm, nw, :vm, load_bus_id) + va_a, va_b, va_c = var(pm, nw, :va, load_bus_id) # v_xy = v_x - v_y vre_xy(vm_x, va_x, vm_y, va_y) = JuMP.@NLexpression(pm.model, vm_x*cos(va_x)-vm_y*cos(va_y)) vim_xy(vm_x, va_x, vm_y, va_y) = JuMP.@NLexpression(pm.model, vm_x*sin(va_x)-vm_y*sin(va_y)) @@ -670,11 +670,11 @@ function constraint_mc_load_current_delta(pm::_PMs.AbstractACPModel, nw::Int, lo p_x(vm_x, va_x, ire_xy, iim_xy, ire_zx, iim_zx) = JuMP.@NLexpression(pm.model, vm_x*cos(va_x)*(ire_xy-ire_zx) + vm_x*sin(va_x)*(iim_xy-iim_zx)) q_x(vm_x, va_x, ire_xy, iim_xy, ire_zx, iim_zx) = JuMP.@NLexpression(pm.model, vm_x*sin(va_x)*(ire_xy-ire_zx) - vm_x*cos(va_x)*(iim_xy-iim_zx)) # s_x = s_x,ref - _PMs.var(pm, nw, :pd_bus)[load_id] = [ + var(pm, nw, :pd_bus)[load_id] = [ p_x(vm_a, va_a, ire_ab, iim_ab, ire_ca, iim_ca), p_x(vm_b, va_b, ire_bc, iim_bc, ire_ab, iim_ab), p_x(vm_c, va_c, ire_ca, iim_ca, ire_bc, iim_bc)] - _PMs.var(pm, nw, :qd_bus)[load_id] = [ + var(pm, nw, :qd_bus)[load_id] = [ q_x(vm_a, va_a, ire_ab, iim_ab, ire_ca, iim_ca), q_x(vm_b, va_b, ire_bc, iim_bc, ire_ab, iim_ab), q_x(vm_c, va_c, ire_ca, iim_ca, ire_bc, iim_bc)] @@ -682,10 +682,10 @@ end "" -function constraint_mc_vm_ll(pm::_PMs.AbstractACPModel, nw::Int, bus_id::Int, vm_ll_min::Vector, vm_ll_max::Vector) +function constraint_mc_vm_ll(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vm_ll_min::Vector, vm_ll_max::Vector) # 3 conductors asserted in template already - vm_ln = [_PMs.var(pm, nw, i, :vm, bus_id) for i in 1:3] - va_ln = [_PMs.var(pm, nw, i, :va, bus_id) for i in 1:3] + vm_ln = [var(pm, nw, i, :vm, bus_id) for i in 1:3] + va_ln = [var(pm, nw, i, :va, bus_id) for i in 1:3] vr_ll = JuMP.@NLexpression(pm.model, [i in 1:3], vm_ln[i]*cos(va_ln[i]) - vm_ln[i%3+1]*cos(va_ln[i%3+1]) ) @@ -706,21 +706,21 @@ end "bus voltage on/off constraint for load shed problem" -function constraint_mc_bus_voltage_on_off(pm::_PMs.AbstractACPModel; nw::Int=pm.cnw, kwargs...) - for (i,bus) in _PMs.ref(pm, nw, :bus) +function constraint_mc_bus_voltage_on_off(pm::_PM.AbstractACPModel; nw::Int=pm.cnw, kwargs...) + for (i,bus) in ref(pm, nw, :bus) constraint_mc_voltage_magnitude_on_off(pm, i; nw=nw) end end "`vm[i] == vmref`" -function constraint_mc_voltage_magnitude_setpoint(pm::_PMs.AbstractACPModel, n::Int, i::Int, vmref) - vm = _PMs.var(pm, n, :vm, i) +function constraint_mc_voltage_magnitude_setpoint(pm::_PM.AbstractACPModel, n::Int, i::Int, vmref) + vm = var(pm, n, :vm, i) JuMP.@constraint(pm.model, vm .== vmref) end "" -function constraint_mc_storage_current_limit(pm::_PMs.AbstractACPModel, n::Int, i, bus, rating) +function constraint_mc_storage_current_limit(pm::_PM.AbstractACPModel, n::Int, i, bus, rating) vm = var(pm, n, :vm, bus) ps = var(pm, n, :ps, i) qs = var(pm, n, :qs, i) @@ -730,9 +730,9 @@ end "" -function constraint_mc_load_wye(pm::_PMs.AbstractACPModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) - vm = _PMs.var(pm, nw, :vm, bus_id) - va = _PMs.var(pm, nw, :va, bus_id) +function constraint_mc_load_wye(pm::_PM.AbstractACPModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) + vm = var(pm, nw, :vm, bus_id) + va = var(pm, nw, :va, bus_id) nph = 3 @@ -754,25 +754,25 @@ function constraint_mc_load_wye(pm::_PMs.AbstractACPModel, nw::Int, id::Int, bus qd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], -vm[i]*cos(va[i])*cid[i]+vm[i]*sin(va[i])*crd[i]) end - _PMs.var(pm, nw, :pd_bus)[id] = pd_bus - _PMs.var(pm, nw, :qd_bus)[id] = qd_bus + var(pm, nw, :pd_bus)[id] = pd_bus + var(pm, nw, :qd_bus)[id] = qd_bus if report - _PMs.sol(pm, nw, :load, id)[:pd_bus] = pd_bus - _PMs.sol(pm, nw, :load, id)[:qd_bus] = qd_bus + sol(pm, nw, :load, id)[:pd_bus] = pd_bus + sol(pm, nw, :load, id)[:qd_bus] = qd_bus pd = JuMP.@NLexpression(pm.model, [i in 1:nph], a[i]*vm[i]^alpha[i] ) qd = JuMP.@NLexpression(pm.model, [i in 1:nph], b[i]*vm[i]^beta[i] ) - _PMs.sol(pm, nw, :load, id)[:pd] = pd - _PMs.sol(pm, nw, :load, id)[:qd] = qd + sol(pm, nw, :load, id)[:pd] = pd + sol(pm, nw, :load, id)[:qd] = qd end end "" -function constraint_mc_load_delta(pm::_PMs.AbstractACPModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) - vm = _PMs.var(pm, nw, :vm, bus_id) - va = _PMs.var(pm, nw, :va, bus_id) +function constraint_mc_load_delta(pm::_PM.AbstractACPModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) + vm = var(pm, nw, :vm, bus_id) + va = var(pm, nw, :va, bus_id) nph = 3 prev = Dict(i=>(i+nph-2)%nph+1 for i in 1:nph) @@ -796,27 +796,27 @@ function constraint_mc_load_delta(pm::_PMs.AbstractACPModel, nw::Int, id::Int, b pd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], vm[i]*cos(va[i])*crd_bus[i]+vm[i]*sin(va[i])*cid_bus[i]) qd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], -vm[i]*cos(va[i])*cid_bus[i]+vm[i]*sin(va[i])*crd_bus[i]) - _PMs.var(pm, nw, :pd_bus)[id] = pd_bus - _PMs.var(pm, nw, :qd_bus)[id] = qd_bus + var(pm, nw, :pd_bus)[id] = pd_bus + var(pm, nw, :qd_bus)[id] = qd_bus if report - _PMs.sol(pm, nw, :load, id)[:pd_bus] = pd_bus - _PMs.sol(pm, nw, :load, id)[:qd_bus] = qd_bus + sol(pm, nw, :load, id)[:pd_bus] = pd_bus + sol(pm, nw, :load, id)[:qd_bus] = qd_bus pd = JuMP.@NLexpression(pm.model, [i in 1:nph], a[i]*(vrd[i]^2+vid[i]^2)^(alpha[i]/2) ) qd = JuMP.@NLexpression(pm.model, [i in 1:nph], b[i]*(vrd[i]^2+vid[i]^2)^(beta[i]/2) ) - _PMs.sol(pm, nw, :load, id)[:pd] = pd - _PMs.sol(pm, nw, :load, id)[:qd] = qd + sol(pm, nw, :load, id)[:pd] = pd + sol(pm, nw, :load, id)[:qd] = qd end end "" -function constraint_mc_generation_delta(pm::_PMs.AbstractACPModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) - vm = _PMs.var(pm, nw, :vm, bus_id) - va = _PMs.var(pm, nw, :va, bus_id) - pg = _PMs.var(pm, nw, :pg, id) - qg = _PMs.var(pm, nw, :qg, id) +function constraint_mc_generation_delta(pm::_PM.AbstractACPModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) + vm = var(pm, nw, :vm, bus_id) + va = var(pm, nw, :va, bus_id) + pg = var(pm, nw, :pg, id) + qg = var(pm, nw, :qg, id) crg = [] cig = [] @@ -837,11 +837,11 @@ function constraint_mc_generation_delta(pm::_PMs.AbstractACPModel, nw::Int, id:: pg_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], vm[i]*cos(va[i])*crg_bus[i]+vm[i]*sin(va[i])*cig_bus[i]) qg_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], -vm[i]*cos(va[i])*cig_bus[i]+vm[i]*sin(va[i])*crg_bus[i]) - _PMs.var(pm, nw, :pg_bus)[id] = pg_bus - _PMs.var(pm, nw, :qg_bus)[id] = qg_bus + var(pm, nw, :pg_bus)[id] = pg_bus + var(pm, nw, :qg_bus)[id] = qg_bus if report - _PMs.sol(pm, nw, :gen, id)[:pg_bus] = pg_bus - _PMs.sol(pm, nw, :gen, id)[:qg_bus] = qg_bus + sol(pm, nw, :gen, id)[:pg_bus] = pg_bus + sol(pm, nw, :gen, id)[:qg_bus] = qg_bus end end diff --git a/src/form/acr.jl b/src/form/acr.jl index 58b933dd9..5aec5d625 100644 --- a/src/form/acr.jl +++ b/src/form/acr.jl @@ -1,32 +1,32 @@ "" -function variable_mc_voltage(pm::_PMs.AbstractACRModel; nw=pm.cnw, bounded::Bool=true, kwargs...) +function variable_mc_voltage(pm::_PM.AbstractACRModel; nw=pm.cnw, bounded::Bool=true, kwargs...) variable_mc_voltage_real(pm; nw=nw, bounded=bounded, kwargs...) variable_mc_voltage_imaginary(pm; nw=nw, bounded=bounded, kwargs...) # local infeasbility issues without proper initialization; # convergence issues start when the equivalent angles of the starting point # are further away than 90 degrees from the solution (as given by ACP) - # this is the default behaviour of _PMs, initialize all phases as (1,0) + # this is the default behaviour of _PM, initialize all phases as (1,0) # the magnitude seems to have little effect on the convergence (>0.05) # updating the starting point to a balanced phasor does the job - ncnd = length(_PMs.conductor_ids(pm)) + ncnd = length(conductor_ids(pm)) theta = [_wrap_to_pi(2 * pi / ncnd * (1-c)) for c in 1:ncnd] vm = 1 - for id in _PMs.ids(pm, nw, :bus) - busref = _PMs.ref(pm, nw, :bus, id) + for id in ids(pm, nw, :bus) + busref = ref(pm, nw, :bus, id) if !haskey(busref, "va_start") for c in 1:ncnd vr = vm*cos(theta[c]) vi = vm*sin(theta[c]) - JuMP.set_start_value(_PMs.var(pm, nw, :vr, id)[c], vr) - JuMP.set_start_value(_PMs.var(pm, nw, :vi, id)[c], vi) + JuMP.set_start_value(var(pm, nw, :vr, id)[c], vr) + JuMP.set_start_value(var(pm, nw, :vi, id)[c], vi) end end end # apply bounds if bounded if bounded - for i in _PMs.ids(pm, nw, :bus) + for i in ids(pm, nw, :bus) constraint_mc_voltage_magnitude_bounds(pm, i, nw=nw) end end @@ -34,10 +34,10 @@ end "`vmin <= vm[i] <= vmax`" -function constraint_mc_voltage_magnitude_bounds(pm::_PMs.AbstractACRModel, n::Int, i, vmin, vmax) +function constraint_mc_voltage_magnitude_bounds(pm::_PM.AbstractACRModel, n::Int, i, vmin, vmax) @assert all(vmin .<= vmax) - vr = _PMs.var(pm, n, :vr, i) - vi = _PMs.var(pm, n, :vi, i) + vr = var(pm, n, :vr, i) + vi = var(pm, n, :vi, i) for t in 1:length(vr) JuMP.@constraint(pm.model, vmin[t]^2 .<= vr[t]^2 + vi[t]^2) @@ -49,10 +49,10 @@ end "Creates phase angle constraints at reference buses" -function constraint_mc_theta_ref(pm::_PMs.AbstractACRModel, n::Int, d::Int, va_ref) - vr = _PMs.var(pm, n, :vr, d) - vi = _PMs.var(pm, n, :vi, d) - cnds = _PMs.conductor_ids(pm; nw=n) +function constraint_mc_theta_ref(pm::_PM.AbstractACRModel, n::Int, d::Int, va_ref) + vr = var(pm, n, :vr, d) + vi = var(pm, n, :vi, d) + cnds = conductor_ids(pm; nw=n) # deal with cases first where tan(theta)==Inf or tan(theta)==0 for c in cnds @@ -80,15 +80,15 @@ function constraint_mc_theta_ref(pm::_PMs.AbstractACRModel, n::Int, d::Int, va_r end end -function constraint_mc_voltage_angle_difference(pm::_PMs.AbstractACRModel, n::Int, f_idx, angmin, angmax) +function constraint_mc_voltage_angle_difference(pm::_PM.AbstractACRModel, n::Int, f_idx, angmin, angmax) i, f_bus, t_bus = f_idx - vr_fr = _PMs.var(pm, n, :vr, f_bus) - vi_fr = _PMs.var(pm, n, :vi, f_bus) - vr_to = _PMs.var(pm, n, :vr, t_bus) - vi_to = _PMs.var(pm, n, :vi, t_bus) + vr_fr = var(pm, n, :vr, f_bus) + vi_fr = var(pm, n, :vi, f_bus) + vr_to = var(pm, n, :vr, t_bus) + vi_to = var(pm, n, :vi, t_bus) - for c in _PMs.conductor_ids(pm; nw=n) + for c in conductor_ids(pm; nw=n) JuMP.@constraint(pm.model, (vi_fr[c]*vr_to[c] - vr_fr[c]*vi_to[c]) <= tan(angmax[c])*(vr_fr[c]*vr_to[c] + vi_fr[c]*vi_to[c])) JuMP.@constraint(pm.model, (vi_fr[c]*vr_to[c] - vr_fr[c]*vi_to[c]) >= tan(angmin[c])*(vr_fr[c]*vr_to[c] + vi_fr[c]*vi_to[c])) end @@ -97,26 +97,26 @@ end "" -function constraint_mc_power_balance_slack(pm::_PMs.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) - vr = _PMs.var(pm, nw, :vr, i) - vi = _PMs.var(pm, nw, :vi, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(_PMs.var(pm, nw), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - p_slack = _PMs.var(pm, nw, :p_slack, i) - q_slack = _PMs.var(pm, nw, :q_slack, i) +function constraint_mc_power_balance_slack(pm::_PM.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + vr = var(pm, nw, :vr, i) + vi = var(pm, nw, :vi, i) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + p_slack = var(pm, nw, :p_slack, i) + q_slack = var(pm, nw, :q_slack, i) cstr_p = [] cstr_q = [] - for c in _PMs.conductor_ids(pm; nw=nw) + for c in conductor_ids(pm; nw=nw) cp = JuMP.@constraint(pm.model, sum(p[a][c] for a in bus_arcs) + sum(psw[a_sw][c] for a_sw in bus_arcs_sw) @@ -144,29 +144,29 @@ function constraint_mc_power_balance_slack(pm::_PMs.AbstractACRModel, nw::Int, i push!(cstr_q, cq) end - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end "" -function constraint_mc_power_balance(pm::_PMs.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) - vr = _PMs.var(pm, nw, :vr, i) - vi = _PMs.var(pm, nw, :vi, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(_PMs.var(pm, nw), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - - cnds = _PMs.conductor_ids(pm; nw=nw) +function constraint_mc_power_balance(pm::_PM.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + vr = var(pm, nw, :vr, i) + vi = var(pm, nw, :vi, i) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) @@ -196,31 +196,31 @@ function constraint_mc_power_balance(pm::_PMs.AbstractACRModel, nw::Int, i::Int, - (-vr.*(Gt*vi+Bt*vr) + vi.*(Gt*vr-Bt*vi)) ) - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end "" -function constraint_mc_power_balance_load(pm::_PMs.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) - vr = _PMs.var(pm, nw, :vr, i) - vi = _PMs.var(pm, nw, :vi, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(_PMs.var(pm, nw), :pg_bus, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg_bus, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - pd = get(_PMs.var(pm, nw), :pd_bus, Dict()); _PMs._check_var_keys(pd, bus_loads, "active power", "load") - qd = get(_PMs.var(pm, nw), :qd_bus, Dict()); _PMs._check_var_keys(pd, bus_loads, "reactive power", "load") - - cnds = _PMs.conductor_ids(pm; nw=nw) +function constraint_mc_power_balance_load(pm::_PM.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) + vr = var(pm, nw, :vr, i) + vi = var(pm, nw, :vi, i) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg_bus, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg_bus, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + pd = get(var(pm, nw), :pd_bus, Dict()); _PM._check_var_keys(pd, bus_loads, "active power", "load") + qd = get(var(pm, nw), :qd_bus, Dict()); _PM._check_var_keys(pd, bus_loads, "reactive power", "load") + + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) @@ -230,7 +230,7 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACRModel, nw::Int, i: cstr_q = [] # pd/qd can be NLexpressions, so cannot be vectorized - for c in _PMs.conductor_ids(pm; nw=nw) + for c in conductor_ids(pm; nw=nw) cp = JuMP.@NLconstraint(pm.model, sum(p[a][c] for a in bus_arcs) + sum(psw[a_sw][c] for a_sw in bus_arcs_sw) @@ -262,9 +262,9 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractACRModel, nw::Int, i: push!(cstr_q, cq) end - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end @@ -276,15 +276,15 @@ s_fr = v_fr.*conj(Y*(v_fr-v_to)) s_fr = (vr_fr+im*vi_fr).*(G-im*B)*([vr_fr-vr_to]-im*[vi_fr-vi_to]) s_fr = (vr_fr+im*vi_fr).*([G*vr_fr-G*vr_to-B*vi_fr+B*vi_to]-im*[G*vi_fr-G*vi_to+B*vr_fr-B*vr_to]) """ -function constraint_mc_ohms_yt_from(pm::_PMs.AbstractACRModel, n::Int, f_bus, t_bus, f_idx, t_idx, G, B, G_fr, B_fr, tr, ti, tm) - p_fr = _PMs.var(pm, n, :p, f_idx) - q_fr = _PMs.var(pm, n, :q, f_idx) - vr_fr = _PMs.var(pm, n, :vr, f_bus) - vr_to = _PMs.var(pm, n, :vr, t_bus) - vi_fr = _PMs.var(pm, n, :vi, f_bus) - vi_to = _PMs.var(pm, n, :vi, t_bus) +function constraint_mc_ohms_yt_from(pm::_PM.AbstractACRModel, n::Int, f_bus, t_bus, f_idx, t_idx, G, B, G_fr, B_fr, tr, ti, tm) + p_fr = var(pm, n, :p, f_idx) + q_fr = var(pm, n, :q, f_idx) + vr_fr = var(pm, n, :vr, f_bus) + vr_to = var(pm, n, :vr, t_bus) + vi_fr = var(pm, n, :vi, f_bus) + vi_to = var(pm, n, :vi, t_bus) - cnds = _PMs.conductor_ids(pm; nw=n) + cnds = conductor_ids(pm; nw=n) JuMP.@constraint(pm.model, p_fr .== vr_fr.*(G*vr_fr-G*vr_to-B*vi_fr+B*vi_to) @@ -338,15 +338,15 @@ p[t_idx] == (g+g_to)*v[t_bus]^2 + (-g*tr-b*ti)/tm*(v[t_bus]*v[f_bus]*cos(t[t_bu q[t_idx] == -(b+b_to)*v[t_bus]^2 - (-b*tr+g*ti)/tm*(v[t_bus]*v[f_bus]*cos(t[f_bus]-t[t_bus])) + (-g*tr-b*ti)/tm*(v[t_bus]*v[f_bus]*sin(t[t_bus]-t[f_bus])) ``` """ -function constraint_mc_ohms_yt_to(pm::_PMs.AbstractACRModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) +function constraint_mc_ohms_yt_to(pm::_PM.AbstractACRModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) constraint_mc_ohms_yt_from(pm, n, t_bus, f_bus, t_idx, f_idx, g, b, g_to, b_to, tr, ti, tm) end "" -function constraint_mc_load_wye(pm::_PMs.AbstractACRModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) - vr = _PMs.var(pm, nw, :vr, bus_id) - vi = _PMs.var(pm, nw, :vi, bus_id) +function constraint_mc_load_wye(pm::_PM.AbstractACRModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) + vr = var(pm, nw, :vr, bus_id) + vi = var(pm, nw, :vi, bus_id) nph = 3 @@ -368,25 +368,25 @@ function constraint_mc_load_wye(pm::_PMs.AbstractACRModel, nw::Int, id::Int, bus qd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], -vr[i]*cid[i]+vi[i]*crd[i]) end - _PMs.var(pm, nw, :pd_bus)[id] = pd_bus - _PMs.var(pm, nw, :qd_bus)[id] = qd_bus + var(pm, nw, :pd_bus)[id] = pd_bus + var(pm, nw, :qd_bus)[id] = qd_bus if report - _PMs.sol(pm, nw, :load, id)[:pd_bus] = pd_bus - _PMs.sol(pm, nw, :load, id)[:qd_bus] = qd_bus + sol(pm, nw, :load, id)[:pd_bus] = pd_bus + sol(pm, nw, :load, id)[:qd_bus] = qd_bus pd = JuMP.@NLexpression(pm.model, [i in 1:nph], a[i]*(vr[i]^2+vi[i]^2)^(alpha[i]/2) ) qd = JuMP.@NLexpression(pm.model, [i in 1:nph], b[i]*(vr[i]^2+vi[i]^2)^(beta[i]/2) ) - _PMs.sol(pm, nw, :load, id)[:pd] = pd - _PMs.sol(pm, nw, :load, id)[:qd] = qd + sol(pm, nw, :load, id)[:pd] = pd + sol(pm, nw, :load, id)[:qd] = qd end end "" -function constraint_mc_load_delta(pm::_PMs.AbstractACRModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) - vr = _PMs.var(pm, nw, :vr, bus_id) - vi = _PMs.var(pm, nw, :vi, bus_id) +function constraint_mc_load_delta(pm::_PM.AbstractACRModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) + vr = var(pm, nw, :vr, bus_id) + vi = var(pm, nw, :vi, bus_id) nph = 3 prev = Dict(i=>(i+nph-2)%nph+1 for i in 1:nph) @@ -410,36 +410,36 @@ function constraint_mc_load_delta(pm::_PMs.AbstractACRModel, nw::Int, id::Int, b pd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], vr[i]*crd_bus[i]+vi[i]*cid_bus[i]) qd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], -vr[i]*cid_bus[i]+vi[i]*crd_bus[i]) - _PMs.var(pm, nw, :pd_bus)[id] = pd_bus - _PMs.var(pm, nw, :qd_bus)[id] = qd_bus + var(pm, nw, :pd_bus)[id] = pd_bus + var(pm, nw, :qd_bus)[id] = qd_bus if report - _PMs.sol(pm, nw, :load, id)[:pd_bus] = pd_bus - _PMs.sol(pm, nw, :load, id)[:qd_bus] = qd_bus + sol(pm, nw, :load, id)[:pd_bus] = pd_bus + sol(pm, nw, :load, id)[:qd_bus] = qd_bus pd = JuMP.@NLexpression(pm.model, [i in 1:nph], a[i]*(vrd[i]^2+vid[i]^2)^(alpha[i]/2) ) qd = JuMP.@NLexpression(pm.model, [i in 1:nph], b[i]*(vrd[i]^2+vid[i]^2)^(beta[i]/2) ) - _PMs.sol(pm, nw, :load, id)[:pd] = pd - _PMs.sol(pm, nw, :load, id)[:qd] = qd + sol(pm, nw, :load, id)[:pd] = pd + sol(pm, nw, :load, id)[:qd] = qd end end "`vm[i] == vmref`" -function constraint_mc_voltage_magnitude_setpoint(pm::_PMs.AbstractACRModel, n::Int, i::Int, vmref) - vr = _PMs.var(pm, n, :vr, i) - vi = _PMs.var(pm, n, :vi, i) +function constraint_mc_voltage_magnitude_setpoint(pm::_PM.AbstractACRModel, n::Int, i::Int, vmref) + vr = var(pm, n, :vr, i) + vi = var(pm, n, :vi, i) JuMP.@constraint(pm.model, vr.^2 + vi.^2 .== vmref.^2) end "" -function constraint_mc_generation_delta(pm::_PMs.AbstractACRModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) - vr = _PMs.var(pm, nw, :vr, bus_id) - vi = _PMs.var(pm, nw, :vi, bus_id) - pg = _PMs.var(pm, nw, :pg, id) - qg = _PMs.var(pm, nw, :qg, id) +function constraint_mc_generation_delta(pm::_PM.AbstractACRModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) + vr = var(pm, nw, :vr, bus_id) + vi = var(pm, nw, :vi, bus_id) + pg = var(pm, nw, :pg, id) + qg = var(pm, nw, :qg, id) crg = [] cig = [] @@ -460,11 +460,11 @@ function constraint_mc_generation_delta(pm::_PMs.AbstractACRModel, nw::Int, id:: pg_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], vr[i]*crg_bus[i]+vi[i]*cig_bus[i]) qg_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], -vr[i]*cig_bus[i]+vi[i]*crg_bus[i]) - _PMs.var(pm, nw, :pg_bus)[id] = pg_bus - _PMs.var(pm, nw, :qg_bus)[id] = qg_bus + var(pm, nw, :pg_bus)[id] = pg_bus + var(pm, nw, :qg_bus)[id] = qg_bus if report - _PMs.sol(pm, nw, :gen, id)[:pg_bus] = pg_bus - _PMs.sol(pm, nw, :gen, id)[:qg_bus] = qg_bus + sol(pm, nw, :gen, id)[:pg_bus] = pg_bus + sol(pm, nw, :gen, id)[:qg_bus] = qg_bus end end diff --git a/src/form/apo.jl b/src/form/apo.jl index b082ad4da..c7dffaa35 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -2,21 +2,21 @@ import LinearAlgebra: diag "apo models ignore reactive power flows" -function variable_mc_generation_reactive(pm::_PMs.AbstractActivePowerModel; kwargs...) +function variable_mc_generation_reactive(pm::_PM.AbstractActivePowerModel; kwargs...) end "apo models ignore reactive power flows" -function variable_mc_reactive_generation_on_off(pm::_PMs.AbstractActivePowerModel; kwargs...) +function variable_mc_reactive_generation_on_off(pm::_PM.AbstractActivePowerModel; kwargs...) end "on/off constraint for generators" -function constraint_mc_generation_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i::Int, pmin, pmax, qmin, qmax) - pg = _PMs.var(pm, n, :pg, i) - z = _PMs.var(pm, n, :z_gen, i) +function constraint_mc_generation_on_off(pm::_PM.AbstractActivePowerModel, n::Int, i::Int, pmin, pmax, qmin, qmax) + pg = var(pm, n, :pg, i) + z = var(pm, n, :z_gen, i) - for c in _PMs.conductor_ids(pm, n) + for c in conductor_ids(pm, n) if isfinite(pmax[c]) JuMP.@constraint(pm.model, pg[c] .<= pmax[c].*z) end @@ -29,58 +29,58 @@ end "apo models ignore reactive power flows" -function variable_mc_storage_reactive(pm::_PMs.AbstractActivePowerModel; kwargs...) +function variable_mc_storage_reactive(pm::_PM.AbstractActivePowerModel; kwargs...) end "apo models ignore reactive power flows" -function variable_mc_on_off_storage_reactive(pm::_PMs.AbstractActivePowerModel; kwargs...) +function variable_mc_on_off_storage_reactive(pm::_PM.AbstractActivePowerModel; kwargs...) end "apo models ignore reactive power flows" -function variable_mc_branch_flow_reactive(pm::_PMs.AbstractActivePowerModel; kwargs...) +function variable_mc_branch_flow_reactive(pm::_PM.AbstractActivePowerModel; kwargs...) end "apo models ignore reactive power flows" -function variable_mc_branch_flow_ne_reactive(pm::_PMs.AbstractActivePowerModel; kwargs...) +function variable_mc_branch_flow_ne_reactive(pm::_PM.AbstractActivePowerModel; kwargs...) end # "do nothing, apo models do not have reactive variables" -# function constraint_mc_gen_setpoint_reactive(pm::_PMs.AbstractActivePowerModel, n::Int, c::Int, i, qg) +# function constraint_mc_gen_setpoint_reactive(pm::_PM.AbstractActivePowerModel, n::Int, c::Int, i, qg) # end "nothing to do, these models do not have complex voltage variables" -function variable_mc_voltage(pm::_PMs.AbstractNFAModel; nw=pm.cnw, kwargs...) +function variable_mc_voltage(pm::_PM.AbstractNFAModel; nw=pm.cnw, kwargs...) end "nothing to do, these models do not have angle difference constraints" -function constraint_mc_voltage_angle_difference(pm::_PMs.AbstractNFAModel, n::Int, f_idx, angmin, angmax) +function constraint_mc_voltage_angle_difference(pm::_PM.AbstractNFAModel, n::Int, f_idx, angmin, angmax) end "apo models ignore reactive power flows" -function variable_mc_transformer_flow_reactive(pm::_PMs.AbstractActivePowerModel; nw::Int=pm.cnw, bounded=true) +function variable_mc_transformer_flow_reactive(pm::_PM.AbstractActivePowerModel; nw::Int=pm.cnw, bounded=true) end "power balanace constraint with line shunts and transformers, active power only" -function constraint_mc_power_balance_load(pm::_PMs.AbstractActivePowerModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - pg = get(_PMs.var(pm, nw), :pg_bus, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - pd = get(_PMs.var(pm, nw), :pd_bus, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") +function constraint_mc_power_balance_load(pm::_PM.AbstractActivePowerModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + pg = get(var(pm, nw), :pg_bus, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + pd = get(var(pm, nw), :pd_bus, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") cstr_p = [] - for c in _PMs.conductor_ids(pm; nw=nw) + for c in conductor_ids(pm; nw=nw) cp = JuMP.@constraint(pm.model, sum(p[a][c] for a in bus_arcs) + sum(psw[a_sw][c] for a_sw in bus_arcs_sw) @@ -94,40 +94,40 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractActivePowerModel, nw: push!(cstr_p, cp) end # omit reactive constraint - cnds = _PMs.conductor_ids(pm, nw) + cnds = conductor_ids(pm, nw) ncnds = length(cnds) - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = [NaN for i in 1:ncnds] + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = [NaN for i in 1:ncnds] end end ######## Lossless Models ######## "Create variables for the active power flowing into all transformer windings" -function variable_mc_transformer_flow_active(pm::_PMs.AbstractAPLossLessModels; nw::Int=pm.cnw, bounded=true) - ncnds = length(_PMs.conductor_ids(pm)) +function variable_mc_transformer_flow_active(pm::_PM.AbstractAPLossLessModels; nw::Int=pm.cnw, bounded=true) + ncnds = length(conductor_ids(pm)) - pt = _PMs.var(pm, nw)[:pt] = Dict((l,i,j) => JuMP.@variable(pm.model, + pt = var(pm, nw)[:pt] = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_pt_$((l,i,j))", start=0 - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs_from_trans) + ) for (l,i,j) in ref(pm, nw, :arcs_from_trans) ) if bounded - for arc in _PMs.ref(pm, nw, :arcs_from_trans) + for arc in ref(pm, nw, :arcs_from_trans) (t,i,j) = arc - rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(_PMs.ref(pm, nw, :transformer, t), _PMs.ref(pm, nw, :bus, i), _PMs.ref(pm, nw, :bus, j)) + rate_a_fr, rate_a_to = _calc_transformer_power_ub_frto(ref(pm, nw, :transformer, t), ref(pm, nw, :bus, i), ref(pm, nw, :bus, j)) set_lower_bound.(pt[(t,i,j)], -min.(rate_a_fr, rate_a_to)) set_upper_bound.(pt[(t,i,j)], min.(rate_a_fr, rate_a_to)) end end - for cnd in _PMs.conductor_ids(pm) + for cnd in conductor_ids(pm) #TODO what does this dfo, and p does not seem to be defined! - for (l,branch) in _PMs.ref(pm, nw, :branch) + for (l,branch) in ref(pm, nw, :branch) if haskey(branch, "pf_start") f_idx = (l, branch["f_bus"], branch["t_bus"]) JuMP.set_value(p[f_idx], branch["pf_start"]) @@ -137,46 +137,46 @@ function variable_mc_transformer_flow_active(pm::_PMs.AbstractAPLossLessModels; end # this explicit type erasure is necessary - p_expr = Dict{Any,Any}( ((l,i,j), pt[(l,i,j)]) for (l,i,j) in _PMs.ref(pm, nw, :arcs_from_trans) ) - p_expr = merge(p_expr, Dict( ((l,j,i), -1.0*pt[(l,i,j)]) for (l,i,j) in _PMs.ref(pm, nw, :arcs_from_trans))) - _PMs.var(pm, nw)[:pt] = p_expr + p_expr = Dict{Any,Any}( ((l,i,j), pt[(l,i,j)]) for (l,i,j) in ref(pm, nw, :arcs_from_trans) ) + p_expr = merge(p_expr, Dict( ((l,j,i), -1.0*pt[(l,i,j)]) for (l,i,j) in ref(pm, nw, :arcs_from_trans))) + var(pm, nw)[:pt] = p_expr end "Do nothing, this model is symmetric" -function constraint_mc_ohms_yt_to(pm::_PMs.AbstractAPLossLessModels, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) +function constraint_mc_ohms_yt_to(pm::_PM.AbstractAPLossLessModels, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) end ### Network Flow Approximation ### "nothing to do, no voltage angle variables" -function constraint_mc_theta_ref(pm::_PMs.AbstractNFAModel, n::Int, d::Int, va_ref) +function constraint_mc_theta_ref(pm::_PM.AbstractNFAModel, n::Int, d::Int, va_ref) end "nothing to do, no voltage angle variables" -function constraint_mc_ohms_yt_from(pm::_PMs.AbstractNFAModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, tm) +function constraint_mc_ohms_yt_from(pm::_PM.AbstractNFAModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, tm) end "nothing to do, this model is symmetric" -function constraint_mc_ohms_yt_to(pm::_PMs.AbstractNFAModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) +function constraint_mc_ohms_yt_to(pm::_PM.AbstractNFAModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) end "nothing to do, this model is symmetric" -function constraint_mc_trans(pm::_PMs.AbstractNFAModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_trans(pm::_PM.AbstractNFAModel, i::Int; nw::Int=pm.cnw) end ## From PowerModels "`-rate_a <= p[f_idx] <= rate_a`" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, rate_a) - cnds = _PMs.conductor_ids(pm, n) +function constraint_mc_thermal_limit_from(pm::_PM.AbstractActivePowerModel, n::Int, f_idx, rate_a) + cnds = conductor_ids(pm, n) ncnds = length(cnds) mu_sm_fr = [] for c in 1:ncnds - p_fr =_PMs.var(pm, n, :p, f_idx)[c] + p_fr =var(pm, n, :p, f_idx)[c] if isa(p_fr, JuMP.VariableRef) && JuMP.has_lower_bound(p_fr) push!(mu_sm_fr,JuMP.LowerBoundRef(p_fr)) JuMP.lower_bound(p_fr) < -rate_a[c] && JuMP.set_lower_bound(p_fr, -rate_a[c]) @@ -188,19 +188,19 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractActivePowerModel, n:: end end - if _PMs.report_duals(pm) - _PMs.sol(pm, n, :branch, f_idx[1])[:mu_sm_fr] = mu_sm_fr + if _PM.report_duals(pm) + sol(pm, n, :branch, f_idx[1])[:mu_sm_fr] = mu_sm_fr end end "" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::Int, t_idx, rate_a) - cnds = _PMs.conductor_ids(pm, n) +function constraint_mc_thermal_limit_to(pm::_PM.AbstractActivePowerModel, n::Int, t_idx, rate_a) + cnds = conductor_ids(pm, n) ncnds = length(cnds) mu_sm_to = [] for c in 1:ncnds - p_to =_PMs.var(pm, n, :p, t_idx)[c] + p_to =var(pm, n, :p, t_idx)[c] if isa(p_to, JuMP.VariableRef) && JuMP.has_lower_bound(p_to) push!(mu_sm_to, JuMP.LowerBoundRef(p_to)) JuMP.lower_bound(p_to) < -rate_a[c] && JuMP.set_lower_bound(p_to, -rate_a[c]) @@ -212,16 +212,16 @@ function constraint_mc_thermal_limit_to(pm::_PMs.AbstractActivePowerModel, n::In end end - if _PMs.report_duals(pm) - _PMs.sol(pm, n, :branch, t_idx[1])[:mu_sm_to] = mu_sm_to + if _PM.report_duals(pm) + sol(pm, n, :branch, t_idx[1])[:mu_sm_to] = mu_sm_to end end "" -function constraint_mc_current_limit(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, c_rating_a) - p_fr =_PMs.var(pm, n, :p, f_idx) +function constraint_mc_current_limit(pm::_PM.AbstractActivePowerModel, n::Int, f_idx, c_rating_a) + p_fr =var(pm, n, :p, f_idx) - cnds = _PMs.conductor_ids(pm, n) + cnds = conductor_ids(pm, n) ncnds = length(cnds) for c in 1:ncnds @@ -232,36 +232,36 @@ end "" -function constraint_mc_thermal_limit_from_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, rate_a) - p_fr =_PMs.var(pm, n, :p, f_idx) - z =_PMs.var(pm, n, :z_branch, i) +function constraint_mc_thermal_limit_from_on_off(pm::_PM.AbstractActivePowerModel, n::Int, i, f_idx, rate_a) + p_fr =var(pm, n, :p, f_idx) + z =var(pm, n, :z_branch, i) JuMP.@constraint(pm.model, p_fr .<= rate_a.*z) JuMP.@constraint(pm.model, p_fr .>= -rate_a.*z) end "" -function constraint_mc_thermal_limit_to_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, rate_a) - p_to =_PMs.var(pm, n, :p, t_idx) - z =_PMs.var(pm, n, :z_branch, i) +function constraint_mc_thermal_limit_to_on_off(pm::_PM.AbstractActivePowerModel, n::Int, i, t_idx, rate_a) + p_to =var(pm, n, :p, t_idx) + z =var(pm, n, :z_branch, i) JuMP.@constraint(pm.model, p_to .<= rate_a.*z) JuMP.@constraint(pm.model, p_to .>= -rate_a.*z) end "" -function constraint_mc_thermal_limit_from_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, f_idx, rate_a) - p_fr =_PMs.var(pm, n, :p_ne, f_idx) - z =_PMs.var(pm, n, :branch_ne, i) +function constraint_mc_thermal_limit_from_ne(pm::_PM.AbstractActivePowerModel, n::Int, i, f_idx, rate_a) + p_fr =var(pm, n, :p_ne, f_idx) + z =var(pm, n, :branch_ne, i) JuMP.@constraint(pm.model, p_fr .<= rate_a.*z) JuMP.@constraint(pm.model, p_fr .>= -rate_a.*z) end "" -function constraint_mc_thermal_limit_to_ne(pm::_PMs.AbstractActivePowerModel, n::Int, i, t_idx, rate_a) - p_to =_PMs.var(pm, n, :p_ne, t_idx) - z =_PMs.var(pm, n, :branch_ne, i) +function constraint_mc_thermal_limit_to_ne(pm::_PM.AbstractActivePowerModel, n::Int, i, t_idx, rate_a) + p_to =var(pm, n, :p_ne, t_idx) + z =var(pm, n, :branch_ne, i) JuMP.@constraint(pm.model, p_to .<= rate_a.*z) JuMP.@constraint(pm.model, p_to .>= -rate_a.*z) @@ -269,10 +269,10 @@ end "" -function constraint_mc_switch_thermal_limit(pm::_PMs.AbstractActivePowerModel, n::Int, f_idx, rating) - psw =_PMs.var(pm, n, :psw, f_idx) +function constraint_mc_switch_thermal_limit(pm::_PM.AbstractActivePowerModel, n::Int, f_idx, rating) + psw =var(pm, n, :psw, f_idx) - cnds = _PMs.conductor_ids(pm, n) + cnds = conductor_ids(pm, n) ncnds = length(cnds) for c in 1:ncnds @@ -283,10 +283,10 @@ end "" -function constraint_mc_storage_thermal_limit(pm::_PMs.AbstractActivePowerModel, n::Int, i, rating) - ps =_PMs.var(pm, n, :ps, i) +function constraint_mc_storage_thermal_limit(pm::_PM.AbstractActivePowerModel, n::Int, i, rating) + ps =var(pm, n, :ps, i) - cnds = _PMs.conductor_ids(pm, n) + cnds = conductor_ids(pm, n) ncnds = length(cnds) for c in 1:ncnds @@ -296,10 +296,10 @@ function constraint_mc_storage_thermal_limit(pm::_PMs.AbstractActivePowerModel, end "" -function constraint_mc_storage_current_limit(pm::_PMs.AbstractActivePowerModel, n::Int, i, bus, rating) - ps =_PMs.var(pm, n, :ps, i) +function constraint_mc_storage_current_limit(pm::_PM.AbstractActivePowerModel, n::Int, i, bus, rating) + ps =var(pm, n, :ps, i) - cnds = _PMs.conductor_ids(pm, n) + cnds = conductor_ids(pm, n) ncnds = length(cnds) for c in 1:ncnds @@ -309,10 +309,10 @@ function constraint_mc_storage_current_limit(pm::_PMs.AbstractActivePowerModel, end "" -function constraint_mc_storage_loss(pm::_PMs.AbstractActivePowerModel, n::Int, i, bus, conductors, r, x, p_loss, q_loss) - ps = _PMs.var(pm, n, :ps, i) - sc = _PMs.var(pm, n, :sc, i) - sd = _PMs.var(pm, n, :sd, i) +function constraint_mc_storage_loss(pm::_PM.AbstractActivePowerModel, n::Int, i, bus, conductors, r, x, p_loss, q_loss) + ps = var(pm, n, :ps, i) + sc = var(pm, n, :sc, i) + sd = var(pm, n, :sd, i) JuMP.@constraint(pm.model, sum(ps[c] for c in conductors) + (sd - sc) @@ -321,10 +321,10 @@ function constraint_mc_storage_loss(pm::_PMs.AbstractActivePowerModel, n::Int, i ) end -function constraint_mc_storage_on_off(pm::_PMs.AbstractActivePowerModel, n::Int, i, pmin, pmax, qmin, qmax, charge_ub, discharge_ub) +function constraint_mc_storage_on_off(pm::_PM.AbstractActivePowerModel, n::Int, i, pmin, pmax, qmin, qmax, charge_ub, discharge_ub) - z_storage =_PMs.var(pm, n, :z_storage, i) - ps =_PMs.var(pm, n, :ps, i) + z_storage =var(pm, n, :z_storage, i) + ps =var(pm, n, :ps, i) JuMP.@constraint(pm.model, ps .<= pmax.*z_storage) JuMP.@constraint(pm.model, ps .>= pmin.*z_storage) @@ -333,7 +333,7 @@ end # # "" -# function add_setpoint_switch_flow!(sol, pm::_PMs.AbstractActivePowerModel) +# function add_setpoint_switch_flow!(sol, pm::_PM.AbstractActivePowerModel) # add_setpoint!(sol, pm, "switch", "psw", :psw, var_key = (idx,item) -> (idx, item["f_bus"], item["t_bus"])) # add_setpoint_fixed!(sol, pm, "switch", "qsw") # end @@ -343,23 +343,23 @@ end """ Only support wye-connected generators. """ -function constraint_mc_generation(pm::_PMs.AbstractActivePowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) - _PMs.var(pm, nw, :pg_bus)[id] = _PMs.var(pm, nw, :pg, id) +function constraint_mc_generation(pm::_PM.AbstractActivePowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) + var(pm, nw, :pg_bus)[id] = var(pm, nw, :pg, id) end "Only support wye-connected, constant-power loads." -function constraint_mc_load(pm::_PMs.AbstractActivePowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) - load = _PMs.ref(pm, nw, :load, id) +function constraint_mc_load(pm::_PM.AbstractActivePowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) + load = ref(pm, nw, :load, id) pd = load["pd"] - _PMs.var(pm, nw, :pd)[id] = pd - _PMs.var(pm, nw, :pd_bus)[id] = _PMs.var(pm, nw, :pd, id) + var(pm, nw, :pd)[id] = pd + var(pm, nw, :pd_bus)[id] = var(pm, nw, :pd, id) if report - _PMs.sol(pm, nw, :load, id)[:pd] = _PMs.var(pm, nw, :pd, id) - _PMs.sol(pm, nw, :load, id)[:pd_bus] = _PMs.var(pm, nw, :pd_bus, id) + sol(pm, nw, :load, id)[:pd] = var(pm, nw, :pd, id) + sol(pm, nw, :load, id)[:pd_bus] = var(pm, nw, :pd_bus, id) end end diff --git a/src/form/bf.jl b/src/form/bf.jl index b6b453722..7df88c8f8 100644 --- a/src/form/bf.jl +++ b/src/form/bf.jl @@ -1,27 +1,27 @@ "This is duplicated at PMD level to correctly handle the indexing of the shunts." -function constraint_mc_voltage_angle_difference(pm::_PMs.AbstractBFModel, n::Int, f_idx, angmin, angmax) +function constraint_mc_voltage_angle_difference(pm::_PM.AbstractBFModel, n::Int, f_idx, angmin, angmax) i, f_bus, t_bus = f_idx t_idx = (i, t_bus, f_bus) - branch = _PMs.ref(pm, n, :branch, i) + branch = ref(pm, n, :branch, i) - for c in _PMs.conductor_ids(pm; nw=n) + for c in conductor_ids(pm; nw=n) tm = branch["tap"][c] g_fr = branch["g_fr"][c,c] g_to = branch["g_to"][c,c] b_fr = branch["b_fr"][c,c] b_to = branch["b_to"][c,c] - tr, ti = _PMs.calc_branch_t(branch) + tr, ti = _PM.calc_branch_t(branch) tr, ti = tr[c], ti[c] r = branch["br_r"][c,c] x = branch["br_x"][c,c] # getting the variables - w_fr = _PMs.var(pm, n, :w, f_bus)[c] - p_fr = _PMs.var(pm, n, :p, f_idx)[c] - q_fr = _PMs.var(pm, n, :q, f_idx)[c] + w_fr = var(pm, n, :w, f_bus)[c] + p_fr = var(pm, n, :p, f_idx)[c] + q_fr = var(pm, n, :q, f_idx)[c] tzr = r*tr + x*ti tzi = r*ti - x*tr diff --git a/src/form/bf_mx.jl b/src/form/bf_mx.jl index 23b00e137..00e701317 100644 --- a/src/form/bf_mx.jl +++ b/src/form/bf_mx.jl @@ -14,12 +14,12 @@ end "" function variable_mc_voltage_prod_hermitian(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - bus_ids = collect(_PMs.ids(pm, nw, :bus)) + bus_ids = collect(ids(pm, nw, :bus)) if bounded # get bounds - vmax = Dict([(id, _PMs.ref(pm, nw, :bus, id, "vmax")) for id in bus_ids]) - vmin = Dict([(id, _PMs.ref(pm, nw, :bus, id, "vmin")) for id in bus_ids]) + vmax = Dict([(id, ref(pm, nw, :bus, id, "vmax")) for id in bus_ids]) + vmin = Dict([(id, ref(pm, nw, :bus, id, "vmin")) for id in bus_ids]) # create bounded Hermitian matrix variables (Wr,Wi) = variable_mx_hermitian(pm.model, bus_ids, n_cond; sqrt_upper_bound=vmax, sqrt_lower_bound=vmin, name="W", prefix="$nw") @@ -30,20 +30,20 @@ function variable_mc_voltage_prod_hermitian(pm::AbstractUBFModels; n_cond::Int=3 end # save references in dict - _PMs.var(pm, nw)[:Wr] = Wr - _PMs.var(pm, nw)[:Wi] = Wi + var(pm, nw)[:Wr] = Wr + var(pm, nw)[:Wi] = Wi # maintain compatibility - _PMs.var(pm, nw)[:w] = Dict{Int, Any}([(id, diag(Wr[id])) for id in bus_ids]) + var(pm, nw)[:w] = Dict{Int, Any}([(id, diag(Wr[id])) for id in bus_ids]) - report && _PMs.sol_component_value(pm, nw, :bus, :Wr, _PMs.ids(pm, nw, :bus), Wr) - report && _PMs.sol_component_value(pm, nw, :bus, :Wi, _PMs.ids(pm, nw, :bus), Wi) + report && _IM.sol_component_value(pm, nw, :bus, :Wr, ids(pm, nw, :bus), Wr) + report && _IM.sol_component_value(pm, nw, :bus, :Wi, ids(pm, nw, :bus), Wi) end "" function variable_mc_branch_series_current_prod_hermitian(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) - branches = _PMs.ref(pm, nw, :branch) - buses = _PMs.ref(pm, nw, :bus) + branches = ref(pm, nw, :branch) + buses = ref(pm, nw, :bus) branch_ids = collect(keys(branches)) @@ -65,12 +65,12 @@ function variable_mc_branch_series_current_prod_hermitian(pm::AbstractUBFModels; end # save reference - _PMs.var(pm, nw)[:CCr] = Lr - _PMs.var(pm, nw)[:CCi] = Li - _PMs.var(pm, nw)[:cm] = Dict([(id, diag(Lr[id])) for id in branch_ids]) + var(pm, nw)[:CCr] = Lr + var(pm, nw)[:CCi] = Li + var(pm, nw)[:cm] = Dict([(id, diag(Lr[id])) for id in branch_ids]) - report && _PMs.sol_component_value(pm, nw, :branch, :CCr, _PMs.ids(pm, nw, :branch), Lr) - report && _PMs.sol_component_value(pm, nw, :branch, :CCi, _PMs.ids(pm, nw, :branch), Li) + report && _IM.sol_component_value(pm, nw, :branch, :CCr, ids(pm, nw, :branch), Lr) + report && _IM.sol_component_value(pm, nw, :branch, :CCi, ids(pm, nw, :branch), Li) end @@ -79,12 +79,12 @@ function variable_mc_branch_flow(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=p @assert n_cond<=5 # calculate S bound - branch_arcs = _PMs.ref(pm, nw, :arcs) + branch_arcs = ref(pm, nw, :arcs) if bounded bound = Dict{eltype(branch_arcs), Array{Real,2}}() - for (br, branch) in _PMs.ref(pm, nw, :branch) - bus_fr = _PMs.ref(pm, nw, :bus, branch["f_bus"]) - bus_to = _PMs.ref(pm, nw, :bus, branch["t_bus"]) + for (br, branch) in ref(pm, nw, :branch) + bus_fr = ref(pm, nw, :bus, branch["f_bus"]) + bus_to = ref(pm, nw, :bus, branch["t_bus"]) smax_fr, smax_to = _calc_branch_power_ub_frto(branch, bus_fr, bus_to) cmax_fr, cmax_to = _calc_branch_current_max_frto(branch, bus_fr, bus_to) @@ -108,33 +108,33 @@ function variable_mc_branch_flow(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=p name=("P", "Q"), prefix="$nw") end # save reference - _PMs.var(pm, nw)[:P] = P - _PMs.var(pm, nw)[:Q] = Q + var(pm, nw)[:P] = P + var(pm, nw)[:Q] = Q - _PMs.var(pm, nw)[:p] = Dict([(id,diag(P[id])) for id in branch_arcs]) - _PMs.var(pm, nw)[:q] = Dict([(id,diag(Q[id])) for id in branch_arcs]) + var(pm, nw)[:p] = Dict([(id,diag(P[id])) for id in branch_arcs]) + var(pm, nw)[:q] = Dict([(id,diag(Q[id])) for id in branch_arcs]) - report && _PMs.sol_component_value_edge(pm, nw, :branch, :Pf, :Pt, _PMs.ref(pm, nw, :arcs_from), _PMs.ref(pm, nw, :arcs_to), P) - report && _PMs.sol_component_value_edge(pm, nw, :branch, :Qf, :Qt, _PMs.ref(pm, nw, :arcs_from), _PMs.ref(pm, nw, :arcs_to), Q) + report && _IM.sol_component_value_edge(pm, nw, :branch, :Pf, :Pt, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), P) + report && _IM.sol_component_value_edge(pm, nw, :branch, :Qf, :Qt, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), Q) end "Defines branch flow model power flow equations" function constraint_mc_flow_losses(pm::AbstractUBFModels, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm) - P_to = _PMs.var(pm, n, :P)[t_idx] - Q_to = _PMs.var(pm, n, :Q)[t_idx] + P_to = var(pm, n, :P)[t_idx] + Q_to = var(pm, n, :Q)[t_idx] - P_fr = _PMs.var(pm, n, :P)[f_idx] - Q_fr = _PMs.var(pm, n, :Q)[f_idx] + P_fr = var(pm, n, :P)[f_idx] + Q_fr = var(pm, n, :Q)[f_idx] - Wr_to = _PMs.var(pm, n, :Wr)[t_bus] - Wr_fr = _PMs.var(pm, n, :Wr)[f_bus] + Wr_to = var(pm, n, :Wr)[t_bus] + Wr_fr = var(pm, n, :Wr)[f_bus] - Wi_to = _PMs.var(pm, n, :Wi)[t_bus] - Wi_fr = _PMs.var(pm, n, :Wi)[f_bus] + Wi_to = var(pm, n, :Wi)[t_bus] + Wi_fr = var(pm, n, :Wi)[f_bus] - CCr = _PMs.var(pm, n, :CCr)[i] - CCi = _PMs.var(pm, n, :CCi)[i] + CCr = var(pm, n, :CCr)[i] + CCi = var(pm, n, :CCi)[i] JuMP.@constraint(pm.model, P_fr + P_to .== Wr_fr*(g_sh_fr)' + Wi_fr*(b_sh_fr)' + r*CCr - x*CCi + Wr_to*(g_sh_to)' + Wi_to*(b_sh_to)') JuMP.@constraint(pm.model, Q_fr + Q_to .== Wi_fr*(g_sh_fr)' - Wr_fr*(b_sh_fr)' + x*CCr + r*CCi + Wi_to*(g_sh_to)' - Wr_to*(b_sh_to)') @@ -143,10 +143,10 @@ end "" function constraint_mc_theta_ref(pm::AbstractUBFModels, n::Int, i::Int, va_ref) - nconductors = length(_PMs.conductor_ids(pm)) + nconductors = length(conductor_ids(pm)) - Wr = _PMs.var(pm, n, :Wr)[i] - Wi = _PMs.var(pm, n, :Wi)[i] + Wr = var(pm, n, :Wr)[i] + Wi = var(pm, n, :Wi)[i] beta = exp.(im.*va_ref) gamma = beta*beta' @@ -161,20 +161,20 @@ end "Defines voltage drop over a branch, linking from and to side voltage" function constraint_mc_model_voltage_magnitude_difference(pm::AbstractUBFModels, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, b_sh_fr, tm) - Wr_fr = _PMs.var(pm, n, :Wr)[f_bus] - Wi_fr = _PMs.var(pm, n, :Wi)[f_bus] + Wr_fr = var(pm, n, :Wr)[f_bus] + Wi_fr = var(pm, n, :Wi)[f_bus] - Wr_to = _PMs.var(pm, n, :Wr)[t_bus] - Wi_to = _PMs.var(pm, n, :Wi)[t_bus] + Wr_to = var(pm, n, :Wr)[t_bus] + Wi_to = var(pm, n, :Wi)[t_bus] - p_fr = _PMs.var(pm, n, :P)[f_idx] - q_fr = _PMs.var(pm, n, :Q)[f_idx] + p_fr = var(pm, n, :P)[f_idx] + q_fr = var(pm, n, :Q)[f_idx] p_s_fr = p_fr - (Wr_fr*(g_sh_fr)' + Wi_fr*(b_sh_fr)') q_s_fr = q_fr - (Wi_fr*(g_sh_fr)' - Wr_fr*(b_sh_fr)') - CCr = _PMs.var(pm, n, :CCr)[i] - CCi = _PMs.var(pm, n, :CCi)[i] + CCr = var(pm, n, :CCr)[i] + CCi = var(pm, n, :CCi)[i] #KVL over the line: JuMP.@constraint(pm.model, diag(Wr_to) .== diag( @@ -206,13 +206,13 @@ variable. function variable_mc_generation_power(pm::SDPUBFKCLMXModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) @assert(bounded) - gen_ids = collect(_PMs.ids(pm, nw, :gen)) - ncnds = length(_PMs.conductor_ids(pm, nw)) + gen_ids = collect(ids(pm, nw, :gen)) + ncnds = length(conductor_ids(pm, nw)) # calculate bounds for matrix variable bound = Dict{eltype(gen_ids), Array{Real,2}}() - for (id, gen) in _PMs.ref(pm, nw, :gen) - bus = _PMs.ref(pm, nw, :bus, gen["gen_bus"]) + for (id, gen) in ref(pm, nw, :gen) + bus = ref(pm, nw, :bus, gen["gen_bus"]) vmax = bus["vmax"] cmax = _calc_gen_current_max(gen, bus) bound[id] = vmax*cmax' @@ -222,8 +222,8 @@ function variable_mc_generation_power(pm::SDPUBFKCLMXModel; nw::Int=pm.cnw, boun symm_bound=bound, name=("Pg", "Qg"), prefix="$nw") #overwrite diagonal bounds - for (id, gen) in _PMs.ref(pm, nw, :gen) - for c in _PMs.conductor_ids(pm, nw) + for (id, gen) in ref(pm, nw, :gen) + for c in conductor_ids(pm, nw) set_lower_bound(Pg[id][c,c], gen["pmin"][c]) set_upper_bound(Pg[id][c,c], gen["pmax"][c]) set_lower_bound(Qg[id][c,c], gen["qmin"][c]) @@ -231,13 +231,13 @@ function variable_mc_generation_power(pm::SDPUBFKCLMXModel; nw::Int=pm.cnw, boun end end # save references - _PMs.var(pm, nw)[:Pg] = Pg - _PMs.var(pm, nw)[:Qg] = Qg - _PMs.var(pm, nw)[:pg] = Dict{Int, Any}([(id, diag(Pg[id])) for id in gen_ids]) - _PMs.var(pm, nw)[:qg] = Dict{Int, Any}([(id, diag(Qg[id])) for id in gen_ids]) + var(pm, nw)[:Pg] = Pg + var(pm, nw)[:Qg] = Qg + var(pm, nw)[:pg] = Dict{Int, Any}([(id, diag(Pg[id])) for id in gen_ids]) + var(pm, nw)[:qg] = Dict{Int, Any}([(id, diag(Qg[id])) for id in gen_ids]) - report && _PMs.sol_component_value(pm, nw, :gen, :Pg, _PMs.ids(pm, nw, :gen), Pg) - report && _PMs.sol_component_value(pm, nw, :gen, :Qg, _PMs.ids(pm, nw, :gen), Qg) + report && _IM.sol_component_value(pm, nw, :gen, :Pg, ids(pm, nw, :gen), Pg) + report && _IM.sol_component_value(pm, nw, :gen, :Qg, ids(pm, nw, :gen), Qg) end @@ -248,23 +248,23 @@ variable. function variable_mc_generation_current(pm::AbstractUBFModels; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) @assert(bounded) - gen_ids = collect(_PMs.ids(pm, nw, :gen)) - ncnds = length(_PMs.conductor_ids(pm, nw)) + gen_ids = collect(ids(pm, nw, :gen)) + ncnds = length(conductor_ids(pm, nw)) # calculate bounds bound = Dict{eltype(gen_ids), Array{Real,2}}() - for (id, gen) in _PMs.ref(pm, nw, :gen) - bus = _PMs.ref(pm, nw, :bus, gen["gen_bus"]) + for (id, gen) in ref(pm, nw, :gen) + bus = ref(pm, nw, :bus, gen["gen_bus"]) cmax = _calc_gen_current_max(gen, bus) bound[id] = cmax*cmax' end # create matrix variables (CCgr,CCgi) = variable_mx_hermitian(pm.model, gen_ids, ncnds; symm_bound=bound, name="CCg", prefix="$nw") # save references - _PMs.var(pm, nw)[:CCgr] = CCgr - _PMs.var(pm, nw)[:CCgi] = CCgi + var(pm, nw)[:CCgr] = CCgr + var(pm, nw)[:CCgi] = CCgi - report && _PMs.sol_component_value(pm, nw, :gen, :CCgr, _PMs.ids(pm, nw, :gen), CCgr) - report && _PMs.sol_component_value(pm, nw, :gen, :CCgi, _PMs.ids(pm, nw, :gen), CCgi) + report && _IM.sol_component_value(pm, nw, :gen, :CCgr, ids(pm, nw, :gen), CCgr) + report && _IM.sol_component_value(pm, nw, :gen, :CCgi, ids(pm, nw, :gen), CCgi) end @@ -279,14 +279,14 @@ constant power or constant impedance. In all other cases (e.g. when a cone is used to constrain the power), variables need to be created. """ function variable_mc_load(pm::AbstractUBFModels; nw=pm.cnw) - load_wye_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["configuration"]=="wye"] - load_del_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["configuration"]=="delta"] - load_cone_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if _check_load_needs_cone(load)] + load_wye_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="wye"] + load_del_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="delta"] + load_cone_ids = [id for (id, load) in ref(pm, nw, :load) if _check_load_needs_cone(load)] # create dictionaries - _PMs.var(pm, nw)[:pd] = Dict() - _PMs.var(pm, nw)[:qd] = Dict() - _PMs.var(pm, nw)[:pl] = Dict() - _PMs.var(pm, nw)[:ql] = Dict() + var(pm, nw)[:pd] = Dict() + var(pm, nw)[:qd] = Dict() + var(pm, nw)[:pl] = Dict() + var(pm, nw)[:ql] = Dict() # now, create auxilary power variable X for delta loads variable_mc_load_delta_aux(pm, load_del_ids) # only delta loads need a current variable @@ -310,18 +310,18 @@ all other load model variables are then linear transformations of these (linear Expressions). """ function variable_mc_load(pm::SDPUBFKCLMXModel; nw=pm.cnw) - load_wye_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["configuration"]=="wye"] - load_del_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if load["configuration"]=="delta"] - load_cone_ids = [id for (id, load) in _PMs.ref(pm, nw, :load) if _check_load_needs_cone(load)] + load_wye_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="wye"] + load_del_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="delta"] + load_cone_ids = [id for (id, load) in ref(pm, nw, :load) if _check_load_needs_cone(load)] # create dictionaries - _PMs.var(pm, nw)[:Pd] = Dict{Int, Any}() - _PMs.var(pm, nw)[:Qd] = Dict{Int, Any}() - _PMs.var(pm, nw)[:pl] = Dict{Int, Any}() - _PMs.var(pm, nw)[:ql] = Dict{Int, Any}() + var(pm, nw)[:Pd] = Dict{Int, Any}() + var(pm, nw)[:Qd] = Dict{Int, Any}() + var(pm, nw)[:pl] = Dict{Int, Any}() + var(pm, nw)[:ql] = Dict{Int, Any}() # now, create auxilary power variable X for delta loads variable_mc_load_delta_aux(pm, load_del_ids) # all loads need a current variable now - variable_mc_load_current(pm, collect(_PMs.ids(pm, nw, :load))) + variable_mc_load_current(pm, collect(ids(pm, nw, :load))) # for all wye-connected loads, we need variables for the off-diagonals of Pd/Qd variable_mc_load_power_bus(pm, load_wye_ids) # for wye loads with a cone inclusion constraint, we need to create a variable for the diagonal @@ -342,13 +342,13 @@ function variable_mc_load_power(pm::AbstractUBFModels, load_ids::Array{Int,1}; n qmin = Dict() qmax = Dict() for id in load_ids - load = _PMs.ref(pm, nw, :load, id) - bus = _PMs.ref(pm, nw, :bus, load["load_bus"]) + load = ref(pm, nw, :load, id) + bus = ref(pm, nw, :bus, load["load_bus"]) pmin[id], pmax[id], qmin[id], qmax[id] = _calc_load_pq_bounds(load, bus) end # create variables - ncnds = length(_PMs.conductor_ids(pm, nw)) + ncnds = length(conductor_ids(pm, nw)) pl = Dict(i => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_pl_$(i)", @@ -362,12 +362,12 @@ function variable_mc_load_power(pm::AbstractUBFModels, load_ids::Array{Int,1}; n ) #store in dict, but do not overwrite for i in load_ids - _PMs.var(pm, nw)[:pl][i] = pl[i] - _PMs.var(pm, nw)[:ql][i] = ql[i] + var(pm, nw)[:pl][i] = pl[i] + var(pm, nw)[:ql][i] = ql[i] end - report && _PMs.sol_component_value(pm, nw, :load, :pl, load_ids, pl) - report && _PMs.sol_component_value(pm, nw, :load, :ql, load_ids, ql) + report && _IM.sol_component_value(pm, nw, :load, :pl, load_ids, pl) + report && _IM.sol_component_value(pm, nw, :load, :ql, load_ids, ql) end @@ -379,25 +379,25 @@ formulation. """ function variable_mc_load_power_bus(pm::SDPUBFKCLMXModel, load_ids::Array{Int,1}; nw=pm.cnw, bounded::Bool=true, report::Bool=true) @assert(bounded) - ncnds = length(_PMs.conductor_ids(pm, nw)) + ncnds = length(conductor_ids(pm, nw)) # calculate bounds bound = Dict{eltype(load_ids), Array{Real,2}}() for id in load_ids - load = _PMs.ref(pm, nw, :load, id) + load = ref(pm, nw, :load, id) @assert(load["configuration"]=="wye") - bus = _PMs.ref(pm, nw, :bus, load["load_bus"]) + bus = ref(pm, nw, :bus, load["load_bus"]) cmax = _calc_load_current_max(load, bus) bound[id] = bus["vmax"]*cmax' end # create matrix variables (Pd,Qd) = variable_mx_complex_with_diag(pm.model, load_ids, ncnds; symm_bound=bound, name=("Pd", "Qd"), prefix="$nw") for id in load_ids - _PMs.var(pm, nw, :Pd)[id] = Pd[id] - _PMs.var(pm, nw, :Qd)[id] = Qd[id] + var(pm, nw, :Pd)[id] = Pd[id] + var(pm, nw, :Qd)[id] = Qd[id] end - report && _PMs.sol_component_value(pm, nw, :load, :Pd, load_ids, Pd) - report && _PMs.sol_component_value(pm, nw, :load, :Qd, load_ids, Qd) + report && _IM.sol_component_value(pm, nw, :load, :Pd, load_ids, Pd) + report && _IM.sol_component_value(pm, nw, :load, :Qd, load_ids, Qd) end @@ -420,13 +420,13 @@ See upcoming paper for discussion of bounds. [reference added when accepted] """ function variable_mc_load_delta_aux(pm::AbstractUBFModels, load_ids::Array{Int,1}; nw=pm.cnw, eps=0.1, bounded::Bool=true, report::Bool=true) @assert(bounded) - ncnds = length(_PMs.conductor_ids(pm, nw)) + ncnds = length(conductor_ids(pm, nw)) # calculate bounds bound = Dict{eltype(load_ids), Array{Real,2}}() for id in load_ids - load = _PMs.ref(pm, nw, :load, id) + load = ref(pm, nw, :load, id) bus_id = load["load_bus"] - bus = _PMs.ref(pm, nw, :bus, bus_id) + bus = ref(pm, nw, :bus, bus_id) cmax = _calc_load_current_max(load, bus) bound[id] = bus["vmax"]*cmax' end @@ -434,11 +434,11 @@ function variable_mc_load_delta_aux(pm::AbstractUBFModels, load_ids::Array{Int,1 (Xdr,Xdi) = variable_mx_complex(pm.model, load_ids, ncnds, ncnds; symm_bound=bound, name="Xd", prefix="$nw") # save references - _PMs.var(pm, nw)[:Xdr] = Xdr - _PMs.var(pm, nw)[:Xdi] = Xdi + var(pm, nw)[:Xdr] = Xdr + var(pm, nw)[:Xdi] = Xdi - report && _PMs.sol_component_value(pm, nw, :load, :Xdr, load_ids, Xdr) - report && _PMs.sol_component_value(pm, nw, :load, :Xdi, load_ids, Xdi) + report && _IM.sol_component_value(pm, nw, :load, :Xdr, load_ids, Xdr) + report && _IM.sol_component_value(pm, nw, :load, :Xdi, load_ids, Xdi) end @@ -450,24 +450,24 @@ frame. function variable_mc_load_current(pm::AbstractUBFModels, load_ids::Array{Int,1}; nw=pm.cnw, bounded::Bool=true, report::Bool=true) @assert(bounded) - ncnds = length(_PMs.conductor_ids(pm, nw)) + ncnds = length(conductor_ids(pm, nw)) # calculate bounds cmin = Dict{eltype(load_ids), Array{Real,1}}() cmax = Dict{eltype(load_ids), Array{Real,1}}() - for (id, load) in _PMs.ref(pm, nw, :load) + for (id, load) in ref(pm, nw, :load) bus_id = load["load_bus"] - bus = _PMs.ref(pm, nw, :bus, bus_id) + bus = ref(pm, nw, :bus, bus_id) cmin[id], cmax[id] = _calc_load_current_magnitude_bounds(load, bus) end # create matrix variables (CCdr, CCdi) = variable_mx_hermitian(pm.model, load_ids, ncnds; sqrt_upper_bound=cmax, sqrt_lower_bound=cmin, name="CCd", prefix="$nw") # save references - _PMs.var(pm, nw)[:CCdr] = CCdr - _PMs.var(pm, nw)[:CCdi] = CCdi + var(pm, nw)[:CCdr] = CCdr + var(pm, nw)[:CCdi] = CCdi - report && _PMs.sol_component_value(pm, nw, :load, :CCdr, load_ids, CCdr) - report && _PMs.sol_component_value(pm, nw, :load, :CCdi, load_ids, CCdi) + report && _IM.sol_component_value(pm, nw, :load, :CCdr, load_ids, CCdr) + report && _IM.sol_component_value(pm, nw, :load, :CCdi, load_ids, CCdi) end @@ -484,13 +484,13 @@ Link the current and power withdrawn by a generator at the bus through a PSD constraint. The rank-1 constraint is dropped in this formulation. """ function constraint_mc_generation(pm::SDPUBFKCLMXModel, gen_id::Int; nw::Int=pm.cnw) - Pg = _PMs.var(pm, nw, :Pg, gen_id) - Qg = _PMs.var(pm, nw, :Qg, gen_id) - bus_id = _PMs.ref(pm, nw, :gen, gen_id)["gen_bus"] - Wr = _PMs.var(pm, nw, :Wr, bus_id) - Wi = _PMs.var(pm, nw, :Wi, bus_id) - CCgr = _PMs.var(pm, nw, :CCgr, gen_id) - CCgi = _PMs.var(pm, nw, :CCgi, gen_id) + Pg = var(pm, nw, :Pg, gen_id) + Qg = var(pm, nw, :Qg, gen_id) + bus_id = ref(pm, nw, :gen, gen_id)["gen_bus"] + Wr = var(pm, nw, :Wr, bus_id) + Wi = var(pm, nw, :Wi, bus_id) + CCgr = var(pm, nw, :CCgr, gen_id) + CCgi = var(pm, nw, :CCgi, gen_id) constraint_SWL_psd(pm.model, Pg, Qg, Wr, Wi, CCgr, CCgi) end @@ -563,11 +563,11 @@ Creates the constraints modelling the (relaxed) voltage-dependent loads. """ function constraint_mc_load(pm::AbstractUBFModels, load_id::Int; nw::Int=pm.cnw) # shared variables and parameters - load = _PMs.ref(pm, nw, :load, load_id) + load = ref(pm, nw, :load, load_id) pd0 = load["pd"] qd0 = load["qd"] bus_id = load["load_bus"] - bus = _PMs.ref(pm, nw, :bus, bus_id) + bus = ref(pm, nw, :bus, bus_id) ncnds = length(pd0) # calculate load params @@ -580,32 +580,32 @@ function constraint_mc_load(pm::AbstractUBFModels, load_id::Int; nw::Int=pm.cnw) # take care of connections if load["configuration"]=="wye" if load["model"]=="constant_power" - _PMs.var(pm, nw, :pl)[load_id] = pd0 - _PMs.var(pm, nw, :ql)[load_id] = qd0 + var(pm, nw, :pl)[load_id] = pd0 + var(pm, nw, :ql)[load_id] = qd0 elseif load["model"]=="constant_impedance" - w = _PMs.var(pm, nw, :w)[bus_id] - _PMs.var(pm, nw, :pl)[load_id] = a.*w - _PMs.var(pm, nw, :ql)[load_id] = b.*w + w = var(pm, nw, :w)[bus_id] + var(pm, nw, :pl)[load_id] = a.*w + var(pm, nw, :ql)[load_id] = b.*w # in this case, :pl has a JuMP variable else - pl = _PMs.var(pm, nw, :pl)[load_id] - ql = _PMs.var(pm, nw, :ql)[load_id] + pl = var(pm, nw, :pl)[load_id] + ql = var(pm, nw, :ql)[load_id] for c in 1:ncnds constraint_pqw(pm.model, Wr[c,c], pl[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) constraint_pqw(pm.model, Wr[c,c], ql[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) end end # :pd is identical to :pl now - _PMs.var(pm, nw, :pd)[load_id] = _PMs.var(pm, nw, :pl)[load_id] - _PMs.var(pm, nw, :qd)[load_id] = _PMs.var(pm, nw, :ql)[load_id] + var(pm, nw, :pd)[load_id] = var(pm, nw, :pl)[load_id] + var(pm, nw, :qd)[load_id] = var(pm, nw, :ql)[load_id] elseif load["configuration"]=="delta" # link Wy, CCd and X - Wr = _PMs.var(pm, nw, :Wr, bus_id) - Wi = _PMs.var(pm, nw, :Wi, bus_id) - CCdr = _PMs.var(pm, nw, :CCdr, load_id) - CCdi = _PMs.var(pm, nw, :CCdi, load_id) - Xdr = _PMs.var(pm, nw, :Xdr, load_id) - Xdi = _PMs.var(pm, nw, :Xdi, load_id) + Wr = var(pm, nw, :Wr, bus_id) + Wi = var(pm, nw, :Wi, bus_id) + CCdr = var(pm, nw, :CCdr, load_id) + CCdi = var(pm, nw, :CCdi, load_id) + Xdr = var(pm, nw, :Xdr, load_id) + Xdi = var(pm, nw, :Xdi, load_id) Td = [1 -1 0; 0 1 -1; -1 0 1] constraint_SWL_psd(pm.model, Xdr, Xdi, Wr, Wi, CCdr, CCdi) # define pd/qd and pl/ql as affine transformations of X @@ -614,10 +614,10 @@ function constraint_mc_load(pm::AbstractUBFModels, load_id::Int; nw::Int=pm.cnw) pl = LinearAlgebra.diag(Td*Xdr) ql = LinearAlgebra.diag(Td*Xdi) - _PMs.var(pm, nw, :pd)[load_id] = pd - _PMs.var(pm, nw, :qd)[load_id] = qd - _PMs.var(pm, nw, :pl)[load_id] = pl - _PMs.var(pm, nw, :ql)[load_id] = ql + var(pm, nw, :pd)[load_id] = pd + var(pm, nw, :qd)[load_id] = qd + var(pm, nw, :pl)[load_id] = pl + var(pm, nw, :ql)[load_id] = ql # |Vd|^2 is a linear transformation of Wr wd = LinearAlgebra.diag(Td*Wr*Td') @@ -647,11 +647,11 @@ the matrix KCL formulation. """ function constraint_mc_load(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int=pm.cnw) # shared variables and parameters - load = _PMs.ref(pm, nw, :load, load_id) + load = ref(pm, nw, :load, load_id) pd0 = load["pd"] qd0 = load["qd"] bus_id = load["load_bus"] - bus = _PMs.ref(pm, nw, :bus, bus_id) + bus = ref(pm, nw, :bus, bus_id) ncnds = length(pd0) # calculate load params @@ -662,42 +662,42 @@ function constraint_mc_load(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int=pm.cnw) pmin, pmax, qmin, qmax = _calc_load_pq_bounds(load, bus) # take care of connections - Wr = _PMs.var(pm, nw, :Wr, bus_id) - Wi = _PMs.var(pm, nw, :Wi, bus_id) - CCdr = _PMs.var(pm, nw, :CCdr, load_id) - CCdi = _PMs.var(pm, nw, :CCdi, load_id) + Wr = var(pm, nw, :Wr, bus_id) + Wi = var(pm, nw, :Wi, bus_id) + CCdr = var(pm, nw, :CCdr, load_id) + CCdi = var(pm, nw, :CCdi, load_id) if load["configuration"]=="wye" if load["model"]=="constant_power" - _PMs.var(pm, nw, :pl)[load_id] = pd0 - _PMs.var(pm, nw, :ql)[load_id] = qd0 + var(pm, nw, :pl)[load_id] = pd0 + var(pm, nw, :ql)[load_id] = qd0 elseif load["model"]=="constant_impedance" - w = _PMs.var(pm, nw, :w, bus_id) + w = var(pm, nw, :w, bus_id) # for c in 1:ncnds - _PMs.var(pm, nw, :pl)[load_id] = a.*w - _PMs.var(pm, nw, :ql)[load_id] = b.*w + var(pm, nw, :pl)[load_id] = a.*w + var(pm, nw, :ql)[load_id] = b.*w # end # in this case, :pl has a JuMP variable else - pl = _PMs.var(pm, nw, :pl)[load_id] - ql = _PMs.var(pm, nw, :ql)[load_id] + pl = var(pm, nw, :pl)[load_id] + ql = var(pm, nw, :ql)[load_id] for c in 1:ncnds constraint_pqw(pm.model, Wr[c,c], pl[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) constraint_pqw(pm.model, Wr[c,c], ql[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) end end # diagonal of :Pd is identical to :pl now - Pd = _PMs.var(pm, nw, :Pd)[load_id] - Qd = _PMs.var(pm, nw, :Qd)[load_id] + Pd = var(pm, nw, :Pd)[load_id] + Qd = var(pm, nw, :Qd)[load_id] for c in 1:ncnds - Pd[c,c] = _PMs.var(pm, nw, :pl)[load_id][c] - Qd[c,c] = _PMs.var(pm, nw, :ql)[load_id][c] + Pd[c,c] = var(pm, nw, :pl)[load_id][c] + Qd[c,c] = var(pm, nw, :ql)[load_id][c] end elseif load["configuration"]=="delta" # link Wy, CCd and X - Xdr = _PMs.var(pm, nw, :Xdr, load_id) - Xdi = _PMs.var(pm, nw, :Xdi, load_id) + Xdr = var(pm, nw, :Xdr, load_id) + Xdi = var(pm, nw, :Xdi, load_id) Td = [1 -1 0; 0 1 -1; -1 0 1] constraint_SWL_psd(pm.model, Xdr, Xdi, Wr, Wi, CCdr, CCdi) # define pd/qd and pl/ql as affine transformations of X @@ -706,10 +706,10 @@ function constraint_mc_load(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int=pm.cnw) pl = LinearAlgebra.diag(Td*Xdr) ql = LinearAlgebra.diag(Td*Xdi) - _PMs.var(pm, nw, :Pd)[load_id] = Pd - _PMs.var(pm, nw, :Qd)[load_id] = Qd - _PMs.var(pm, nw, :pl)[load_id] = pl - _PMs.var(pm, nw, :ql)[load_id] = ql + var(pm, nw, :Pd)[load_id] = Pd + var(pm, nw, :Qd)[load_id] = Qd + var(pm, nw, :pl)[load_id] = pl + var(pm, nw, :ql)[load_id] = ql # |Vd|^2 is a linear transformation of Wr wd = LinearAlgebra.diag(Td*Wr*Td') @@ -763,15 +763,15 @@ end For KCLMXModels, a new power balance constraint is required. """ function constraint_mc_power_balance(pm::KCLMXModels, i::Int; nw::Int=pm.cnw) - bus = _PMs.ref(pm, nw, :bus, i) - bus_arcs = _PMs.ref(pm, nw, :bus_arcs, i) - bus_arcs_dc = _PMs.ref(pm, nw, :bus_arcs_dc, i) - bus_gens = _PMs.ref(pm, nw, :bus_gens, i) - bus_loads = _PMs.ref(pm, nw, :bus_loads, i) - bus_shunts = _PMs.ref(pm, nw, :bus_shunts, i) + bus = ref(pm, nw, :bus, i) + bus_arcs = ref(pm, nw, :bus_arcs, i) + bus_arcs_dc = ref(pm, nw, :bus_arcs_dc, i) + bus_gens = ref(pm, nw, :bus_gens, i) + bus_loads = ref(pm, nw, :bus_loads, i) + bus_shunts = ref(pm, nw, :bus_shunts, i) - bus_Gs = Dict(k => LinearAlgebra.diagm(0=>_PMs.ref(pm, nw, :shunt, k, "gs")) for k in bus_shunts) - bus_Bs = Dict(k => LinearAlgebra.diagm(0=>_PMs.ref(pm, nw, :shunt, k, "bs")) for k in bus_shunts) + bus_Gs = Dict(k => LinearAlgebra.diagm(0=>ref(pm, nw, :shunt, k, "gs")) for k in bus_shunts) + bus_Bs = Dict(k => LinearAlgebra.diagm(0=>ref(pm, nw, :shunt, k, "bs")) for k in bus_shunts) constraint_mc_power_balance(pm, nw, i, bus_arcs, bus_arcs_dc, bus_gens, bus_loads, bus_Gs, bus_Bs) end @@ -786,15 +786,15 @@ P = Wr.G'+Wi.B' Q = -Wr.B'+Wi.G' """ function constraint_mc_power_balance(pm::KCLMXModels, n::Int, i::Int, bus_arcs, bus_arcs_dc, bus_gens, bus_loads, bus_Gs, bus_Bs) - Wr = _PMs.var(pm, n, :Wr, i) - Wi = _PMs.var(pm, n, :Wi, i) + Wr = var(pm, n, :Wr, i) + Wi = var(pm, n, :Wi, i) - P = get(_PMs.var(pm, n), :P, Dict()); _PMs._check_var_keys(P, bus_arcs, "active power", "branch") - Q = get(_PMs.var(pm, n), :Q, Dict()); _PMs._check_var_keys(Q, bus_arcs, "reactive power", "branch") - Pg = get(_PMs.var(pm, n), :Pg, Dict()); _PMs._check_var_keys(Pg, bus_gens, "active power", "generator") - Qg = get(_PMs.var(pm, n), :Qg, Dict()); _PMs._check_var_keys(Qg, bus_gens, "reactive power", "generator") - Pd = get(_PMs.var(pm, n), :Pd, Dict()); _PMs._check_var_keys(Pd, bus_loads, "active power", "load") - Qd = get(_PMs.var(pm, n), :Qd, Dict()); _PMs._check_var_keys(Qd, bus_loads, "reactive power", "load") + P = get(var(pm, n), :P, Dict()); _PM._check_var_keys(P, bus_arcs, "active power", "branch") + Q = get(var(pm, n), :Q, Dict()); _PM._check_var_keys(Q, bus_arcs, "reactive power", "branch") + Pg = get(var(pm, n), :Pg, Dict()); _PM._check_var_keys(Pg, bus_gens, "active power", "generator") + Qg = get(var(pm, n), :Qg, Dict()); _PM._check_var_keys(Qg, bus_gens, "reactive power", "generator") + Pd = get(var(pm, n), :Pd, Dict()); _PM._check_var_keys(Pd, bus_loads, "active power", "load") + Qd = get(var(pm, n), :Qd, Dict()); _PM._check_var_keys(Qd, bus_loads, "reactive power", "load") # ignore dc for now #TODO add DC in matrix version? @@ -805,13 +805,13 @@ function constraint_mc_power_balance(pm::KCLMXModels, n::Int, i::Int, bus_arcs, # changed the ordering # LHS: all variables with generator sign convention # RHS: all variables with load sign convention - # _PMs.con(pm, n, :kcl_P)[i] = + # con(pm, n, :kcl_P)[i] = cp = JuMP.@constraint(pm.model, sum(Pg[g] for g in bus_gens) .== sum(P[a] for a in bus_arcs) + sum(Pd[d] for d in bus_loads) + ( Wr*Gt'+Wi*Bt')) - # _PMs.con(pm, n, :kcl_Q)[i] = + # con(pm, n, :kcl_Q)[i] = cq = JuMP.@constraint(pm.model, sum(Qg[g] for g in bus_gens) .== sum(Q[a] for a in bus_arcs) + sum(Qd[d] for d in bus_loads) + (-Wr*Bt'+Wi*Gt')) - if _PMs.report_duals(pm) - _PMs.sol(pm, n, :bus, i)[:lam_kcl_r] = cp - _PMs.sol(pm, n, :bus, i)[:lam_kcl_i] = cq + if _PM.report_duals(pm) + sol(pm, n, :bus, i)[:lam_kcl_r] = cp + sol(pm, n, :bus, i)[:lam_kcl_i] = cq end end diff --git a/src/form/bf_mx_lin.jl b/src/form/bf_mx_lin.jl index 134545b9b..ebe34d64d 100644 --- a/src/form/bf_mx_lin.jl +++ b/src/form/bf_mx_lin.jl @@ -27,14 +27,14 @@ end "Defines branch flow model power flow equations" function constraint_mc_flow_losses(pm::LPUBFDiagModel, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm) - p_fr = _PMs.var(pm, n, :p)[f_idx] - q_fr = _PMs.var(pm, n, :q)[f_idx] + p_fr = var(pm, n, :p)[f_idx] + q_fr = var(pm, n, :q)[f_idx] - p_to = _PMs.var(pm, n, :p)[t_idx] - q_to = _PMs.var(pm, n, :q)[t_idx] + p_to = var(pm, n, :p)[t_idx] + q_to = var(pm, n, :q)[t_idx] - w_fr = _PMs.var(pm, n, :w)[f_bus] - w_to = _PMs.var(pm, n, :w)[t_bus] + w_fr = var(pm, n, :w)[f_bus] + w_to = var(pm, n, :w)[t_bus] JuMP.@constraint(pm.model, p_fr + p_to .== diag( g_sh_fr).*w_fr + diag( g_sh_to).*w_to) JuMP.@constraint(pm.model, q_fr + q_to .== diag(-b_sh_fr).*w_fr + diag(-b_sh_to).*w_to) @@ -43,11 +43,11 @@ end "Defines voltage drop over a branch, linking from and to side voltage" function constraint_mc_model_voltage_magnitude_difference(pm::LPUBFDiagModel, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, b_sh_fr, tm) - w_fr = _PMs.var(pm, n, :w)[f_bus] - w_to = _PMs.var(pm, n, :w)[t_bus] + w_fr = var(pm, n, :w)[f_bus] + w_to = var(pm, n, :w)[t_bus] - p_fr = _PMs.var(pm, n, :p)[f_idx] - q_fr = _PMs.var(pm, n, :q)[f_idx] + p_fr = var(pm, n, :p)[f_idx] + q_fr = var(pm, n, :q)[f_idx] p_s_fr = p_fr - diag(g_sh_fr).*w_fr q_s_fr = q_fr + diag(b_sh_fr).*w_to @@ -65,30 +65,30 @@ end "balanced three-phase phasor" function constraint_mc_theta_ref(pm::LPUBFDiagModel, n::Int, i::Int, va_ref) - ncnds = length(_PMs.conductor_ids(pm)) + ncnds = length(conductor_ids(pm)) @assert(ncnds >= 2) - w = _PMs.var(pm, n, :w)[i] + w = var(pm, n, :w)[i] JuMP.@constraint(pm.model, w[2:ncnds] .== w[1]) end "" function constraint_mc_power_balance(pm::LPUBFDiagModel, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) - w = _PMs.var(pm, nw, :w, i) + w = var(pm, nw, :w, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - pg = get(_PMs.var(pm, nw), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") + pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") cstr_p = [] cstr_q = [] @@ -115,8 +115,8 @@ function constraint_mc_power_balance(pm::LPUBFDiagModel, nw::Int, i, bus_arcs, b + sum(bs.*w for bs in values(bus_bs)) ) - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end diff --git a/src/form/bf_mx_sdp.jl b/src/form/bf_mx_sdp.jl index 61d0c2737..7cde2c6ab 100644 --- a/src/form/bf_mx_sdp.jl +++ b/src/form/bf_mx_sdp.jl @@ -1,13 +1,13 @@ "Defines relationship between branch (series) power flow, branch (series) current and node voltage magnitude" function constraint_mc_model_current(pm::SDPUBFModel, n::Int, i, f_bus, f_idx, g_sh_fr, b_sh_fr) - p_fr = _PMs.var(pm, n, :P)[f_idx] - q_fr = _PMs.var(pm, n, :Q)[f_idx] + p_fr = var(pm, n, :P)[f_idx] + q_fr = var(pm, n, :Q)[f_idx] - w_fr_re = _PMs.var(pm, n, :Wr)[f_bus] - w_fr_im = _PMs.var(pm, n, :Wi)[f_bus] + w_fr_re = var(pm, n, :Wr)[f_bus] + w_fr_im = var(pm, n, :Wi)[f_bus] - ccm_re = _PMs.var(pm, n, :CCr)[i] - ccm_im = _PMs.var(pm, n, :CCi)[i] + ccm_re = var(pm, n, :CCr)[i] + ccm_im = var(pm, n, :CCi)[i] p_s_fr = p_fr - (w_fr_re*(g_sh_fr)' + w_fr_im*(b_sh_fr)') q_s_fr = q_fr - (w_fr_im*(g_sh_fr)' - w_fr_re*(b_sh_fr)') diff --git a/src/form/bf_mx_soc.jl b/src/form/bf_mx_soc.jl index a7c300242..4e3a39ce4 100644 --- a/src/form/bf_mx_soc.jl +++ b/src/form/bf_mx_soc.jl @@ -1,13 +1,13 @@ "Defines relationship between branch (series) power flow, branch (series) current and node voltage magnitude" function constraint_mc_model_current(pm::SOCUBFModels, n::Int, i, f_bus, f_idx, g_sh_fr, b_sh_fr) - p_fr = _PMs.var(pm, n, :P)[f_idx] - q_fr = _PMs.var(pm, n, :Q)[f_idx] + p_fr = var(pm, n, :P)[f_idx] + q_fr = var(pm, n, :Q)[f_idx] - w_fr_re = _PMs.var(pm, n, :Wr)[f_bus] - w_fr_im = _PMs.var(pm, n, :Wi)[f_bus] + w_fr_re = var(pm, n, :Wr)[f_bus] + w_fr_im = var(pm, n, :Wi)[f_bus] - ccm_re = _PMs.var(pm, n, :CCr)[i] - ccm_im = _PMs.var(pm, n, :CCi)[i] + ccm_re = var(pm, n, :CCr)[i] + ccm_im = var(pm, n, :CCi)[i] p_s_fr = p_fr - g_sh_fr*w_fr_re q_s_fr = q_fr + b_sh_fr*w_fr_re @@ -27,7 +27,7 @@ function constraint_mc_model_current(pm::SOCUBFModels, n::Int, i, f_bus, f_idx, # code below useful for debugging: valid inequality equired to make the SOC-NLP formulation more accurate # (l,i,j) = f_idx # t_idx = (l,j,i) - # p_to = _PMs.var(pm, n, :P)[t_idx] + # p_to = var(pm, n, :P)[t_idx] # total losses are positive when g_fr, g_to and r are positive # not guaranteed for individual phases though when matrix obtained through Kron's reduction # JuMP.@constraint(pm.model, tr(p_fr) + tr(p_to) >= 0) @@ -36,14 +36,14 @@ end "Defines relationship between branch (series) power flow, branch (series) current and node voltage magnitude" function constraint_mc_model_current(pm::SOCConicUBFModel, n::Int, i, f_bus, f_idx, g_sh_fr, b_sh_fr) - p_fr = _PMs.var(pm, n, :P)[f_idx] - q_fr = _PMs.var(pm, n, :Q)[f_idx] + p_fr = var(pm, n, :P)[f_idx] + q_fr = var(pm, n, :Q)[f_idx] - w_fr_re = _PMs.var(pm, n, :Wr)[f_bus] - w_fr_im = _PMs.var(pm, n, :Wi)[f_bus] + w_fr_re = var(pm, n, :Wr)[f_bus] + w_fr_im = var(pm, n, :Wi)[f_bus] - ccm_re = _PMs.var(pm, n, :CCr)[i] - ccm_im = _PMs.var(pm, n, :CCi)[i] + ccm_re = var(pm, n, :CCr)[i] + ccm_im = var(pm, n, :CCi)[i] p_s_fr = p_fr - g_sh_fr*w_fr_re q_s_fr = q_fr + b_sh_fr*w_fr_re diff --git a/src/form/dcp.jl b/src/form/dcp.jl index 7073aa7ac..0dae6a479 100644 --- a/src/form/dcp.jl +++ b/src/form/dcp.jl @@ -2,19 +2,19 @@ "" -function variable_mc_voltage(pm::_PMs.AbstractDCPModel; nw=pm.cnw, kwargs...) +function variable_mc_voltage(pm::_PM.AbstractDCPModel; nw=pm.cnw, kwargs...) variable_mc_voltage_angle(pm; nw=nw, kwargs...) end ######## AbstractDCPForm Models (has va but assumes vm is 1.0) ######## "nothing to do, these models do not have complex voltage constraints" -function constraint_mc_model_voltage(pm::_PMs.AbstractDCPModel, n::Int, c::Int) +function constraint_mc_model_voltage(pm::_PM.AbstractDCPModel, n::Int, c::Int) end "" -function variable_mc_bus_voltage_on_off(pm::_PMs.AbstractDCPModel; kwargs...) +function variable_mc_bus_voltage_on_off(pm::_PM.AbstractDCPModel; kwargs...) variable_mc_voltage_angle(pm; kwargs...) end @@ -27,26 +27,26 @@ Creates Ohms constraints (yt post fix indicates that Y and T values are in recta p[f_idx] == -b*(t[f_bus] - t[t_bus]) ``` """ -function constraint_mc_ohms_yt_from(pm::_PMs.AbstractDCPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, tm) - p_fr = _PMs.var(pm, n, :p, f_idx) - va_fr = _PMs.var(pm, n, :va, f_bus) - va_to = _PMs.var(pm, n, :va, t_bus) +function constraint_mc_ohms_yt_from(pm::_PM.AbstractDCPModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, 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) - for c in _PMs.conductor_ids(pm, n) - JuMP.@constraint(pm.model, p_fr[c] == -sum(b[c,d]*(va_fr[c] - va_to[d]) for d in _PMs.conductor_ids(pm))) + for c in conductor_ids(pm, n) + JuMP.@constraint(pm.model, p_fr[c] == -sum(b[c,d]*(va_fr[c] - va_to[d]) for d in conductor_ids(pm))) end end "power balance constraint with line shunts and transformers for load shed problem, DCP formulation" -function constraint_mc_power_balance_shed(pm::_PMs.AbstractDCPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - pg = get(_PMs.var(pm, nw), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - z_demand = _PMs.var(pm, nw, :z_demand) - z_shunt = _PMs.var(pm, nw, :z_shunt) +function constraint_mc_power_balance_shed(pm::_PM.AbstractDCPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + z_demand = var(pm, nw, :z_demand) + z_shunt = var(pm, nw, :z_shunt) cp = JuMP.@constraint(pm.model, sum(p[a] for a in bus_arcs) @@ -59,30 +59,30 @@ function constraint_mc_power_balance_shed(pm::_PMs.AbstractDCPModel, nw::Int, i: - sum(diag(gs)*1.0^2 .*z_shunt[n] for (n,gs) in bus_gs) ) - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cp + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cp end end "on/off bus voltage constraint for DCP formulation, nothing to do" -function constraint_mc_bus_voltage_on_off(pm::_PMs.AbstractDCPModel; nw::Int=pm.cnw, kwargs...) +function constraint_mc_bus_voltage_on_off(pm::_PM.AbstractDCPModel; nw::Int=pm.cnw, kwargs...) end "" -function variable_mc_branch_flow_active(pm::_PMs.AbstractAPLossLessModels; nw::Int=pm.cnw, bounded::Bool = true, report::Bool = true) - cnds = _PMs.conductor_ids(pm) +function variable_mc_branch_flow_active(pm::_PM.AbstractAPLossLessModels; nw::Int=pm.cnw, bounded::Bool = true, report::Bool = true) + cnds = conductor_ids(pm) ncnds = length(cnds) p = Dict((l,i,j) => JuMP.@variable(pm.model, [c in 1:ncnds], base_name="$(nw)_($l,$i,$j)_p", - start = comp_start_value(_PMs.ref(pm, nw, :branch, l), "p_start", c, 0.0) - ) for (l,i,j) in _PMs.ref(pm, nw, :arcs_from)) + start = comp_start_value(ref(pm, nw, :branch, l), "p_start", c, 0.0) + ) for (l,i,j) in ref(pm, nw, :arcs_from)) if bounded - for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) - smax = _calc_branch_power_max(_PMs.ref(pm, nw, :branch, l), _PMs.ref(pm, nw, :bus, i)) + for (l,i,j) in ref(pm, nw, :arcs_from) + smax = _calc_branch_power_max(ref(pm, nw, :branch, l), ref(pm, nw, :bus, i)) if !ismissing(smax) JuMP.set_upper_bound.(p[(l,i,j)], smax) JuMP.set_lower_bound.(p[(l,i,j)], -smax) @@ -90,7 +90,7 @@ function variable_mc_branch_flow_active(pm::_PMs.AbstractAPLossLessModels; nw::I end end - for (l,branch) in _PMs.ref(pm, nw, :branch) + for (l,branch) in ref(pm, nw, :branch) if haskey(branch, "pf_start") f_idx = (l, branch["f_bus"], branch["t_bus"]) JuMP.set_start_value(p[f_idx], branch["pf_start"]) @@ -98,7 +98,7 @@ function variable_mc_branch_flow_active(pm::_PMs.AbstractAPLossLessModels; nw::I end # this explicit type erasure is necessary - p_expr = Dict{Any,Any}( ((l,i,j), p[(l,i,j)]) for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) ) - p_expr = merge(p_expr, Dict( ((l,j,i), -1.0*p[(l,i,j)]) for (l,i,j) in _PMs.ref(pm, nw, :arcs_from))) - _PMs.var(pm, nw)[:p] = p_expr + p_expr = Dict{Any,Any}( ((l,i,j), p[(l,i,j)]) for (l,i,j) in ref(pm, nw, :arcs_from) ) + p_expr = merge(p_expr, Dict( ((l,j,i), -1.0*p[(l,i,j)]) for (l,i,j) in ref(pm, nw, :arcs_from))) + var(pm, nw)[:p] = p_expr end diff --git a/src/form/ivr.jl b/src/form/ivr.jl index 021a44de1..fb906cc91 100644 --- a/src/form/ivr.jl +++ b/src/form/ivr.jl @@ -3,7 +3,7 @@ # in the context of constant-power loads or generators "" -function variable_mc_branch_current(pm::_PMs.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) +function variable_mc_branch_current(pm::_PM.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) variable_mc_branch_current_real(pm, nw=nw, bounded=bounded, report=report; kwargs...) variable_mc_branch_current_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...) @@ -11,26 +11,26 @@ function variable_mc_branch_current(pm::_PMs.AbstractIVRModel; nw::Int=pm.cnw, b p = Dict() q = Dict() - for (l,i,j) in _PMs.ref(pm, nw, :arcs_from) - vr_fr = _PMs.var(pm, nw, :vr, i) - vi_fr = _PMs.var(pm, nw, :vi, i) - cr_fr = _PMs.var(pm, nw, :cr, (l,i,j)) - ci_fr = _PMs.var(pm, nw, :ci, (l,i,j)) + for (l,i,j) in ref(pm, nw, :arcs_from) + vr_fr = var(pm, nw, :vr, i) + vi_fr = var(pm, nw, :vi, i) + cr_fr = var(pm, nw, :cr, (l,i,j)) + ci_fr = var(pm, nw, :ci, (l,i,j)) - vr_to = _PMs.var(pm, nw, :vr, j) - vi_to = _PMs.var(pm, nw, :vi, j) - cr_to = _PMs.var(pm, nw, :cr, (l,j,i)) - ci_to = _PMs.var(pm, nw, :ci, (l,j,i)) + vr_to = var(pm, nw, :vr, j) + vi_to = var(pm, nw, :vi, j) + cr_to = var(pm, nw, :cr, (l,j,i)) + ci_to = var(pm, nw, :ci, (l,j,i)) p[(l,i,j)] = vr_fr.*cr_fr + vi_fr.*ci_fr q[(l,i,j)] = vi_fr.*cr_fr - vr_fr.*ci_fr p[(l,j,i)] = vr_to.*cr_to + vi_to.*ci_to q[(l,j,i)] = vi_to.*cr_to - vr_to.*ci_to end - _PMs.var(pm, nw)[:p] = p - _PMs.var(pm, nw)[:q] = q - report && _PMs.sol_component_value_edge(pm, nw, :branch, :pf, :pt, _PMs.ref(pm, nw, :arcs_from), _PMs.ref(pm, nw, :arcs_to), p) - report && _PMs.sol_component_value_edge(pm, nw, :branch, :qf, :qt, _PMs.ref(pm, nw, :arcs_from), _PMs.ref(pm, nw, :arcs_to), q) + var(pm, nw)[:p] = p + var(pm, nw)[:q] = q + report && _IM.sol_component_value_edge(pm, nw, :branch, :pf, :pt, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), p) + report && _IM.sol_component_value_edge(pm, nw, :branch, :qf, :qt, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), q) variable_mc_branch_series_current_real(pm, nw=nw, bounded=bounded, report=report; kwargs...) variable_mc_branch_series_current_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...) @@ -38,7 +38,7 @@ end "" -function variable_mc_transformer_current(pm::_PMs.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) +function variable_mc_transformer_current(pm::_PM.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) variable_mc_transformer_current_real(pm, nw=nw, bounded=bounded, report=report; kwargs...) variable_mc_transformer_current_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...) @@ -49,84 +49,84 @@ function variable_mc_transformer_current(pm::_PMs.AbstractIVRModel; nw::Int=pm.c f_cnd = [1, 2, 3] #TODO extend to this being passed in constraint template t_cnd = [1, 2, 3] #TODO extend to this being passed in constraint template - for (l,i,j) in _PMs.ref(pm, nw, :arcs_from_trans) - vr_fr = _PMs.var(pm, nw, :vr, i)[f_cnd] - vi_fr = _PMs.var(pm, nw, :vi, i)[f_cnd] - cr_fr = _PMs.var(pm, nw, :crt, (l,i,j)) - ci_fr = _PMs.var(pm, nw, :cit, (l,i,j)) + for (l,i,j) in ref(pm, nw, :arcs_from_trans) + vr_fr = var(pm, nw, :vr, i)[f_cnd] + vi_fr = var(pm, nw, :vi, i)[f_cnd] + cr_fr = var(pm, nw, :crt, (l,i,j)) + ci_fr = var(pm, nw, :cit, (l,i,j)) - vr_to = _PMs.var(pm, nw, :vr, j)[t_cnd] - vi_to = _PMs.var(pm, nw, :vi, j)[t_cnd] - cr_to = _PMs.var(pm, nw, :crt, (l,j,i)) - ci_to = _PMs.var(pm, nw, :cit, (l,j,i)) + vr_to = var(pm, nw, :vr, j)[t_cnd] + vi_to = var(pm, nw, :vi, j)[t_cnd] + cr_to = var(pm, nw, :crt, (l,j,i)) + ci_to = var(pm, nw, :cit, (l,j,i)) p[(l,i,j)] = vr_fr.*cr_fr + vi_fr.*ci_fr q[(l,i,j)] = vi_fr.*cr_fr - vr_fr.*ci_fr p[(l,j,i)] = vr_to.*cr_to + vi_to.*ci_to q[(l,j,i)] = vi_to.*cr_to - vr_to.*ci_to end - _PMs.var(pm, nw)[:p] = p - _PMs.var(pm, nw)[:q] = q - report && _PMs.sol_component_value_edge(pm, nw, :transformer, :pf, :pt, _PMs.ref(pm, nw, :arcs_from_trans), _PMs.ref(pm, nw, :arcs_to_trans), p) - report && _PMs.sol_component_value_edge(pm, nw, :transformer, :qf, :qt, _PMs.ref(pm, nw, :arcs_from_trans), _PMs.ref(pm, nw, :arcs_to_trans), q) + var(pm, nw)[:p] = p + var(pm, nw)[:q] = q + report && _IM.sol_component_value_edge(pm, nw, :transformer, :pf, :pt, ref(pm, nw, :arcs_from_trans), ref(pm, nw, :arcs_to_trans), p) + report && _IM.sol_component_value_edge(pm, nw, :transformer, :qf, :qt, ref(pm, nw, :arcs_from_trans), ref(pm, nw, :arcs_to_trans), q) end "" -function variable_mc_load(pm::_PMs.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) - _PMs.var(pm, nw)[:crd] = Dict{Int, Any}() - _PMs.var(pm, nw)[:cid] = Dict{Int, Any}() - _PMs.var(pm, nw)[:crd_bus] = Dict{Int, Any}() - _PMs.var(pm, nw)[:cid_bus] = Dict{Int, Any}() +function variable_mc_load(pm::_PM.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) + var(pm, nw)[:crd] = Dict{Int, Any}() + var(pm, nw)[:cid] = Dict{Int, Any}() + var(pm, nw)[:crd_bus] = Dict{Int, Any}() + var(pm, nw)[:cid_bus] = Dict{Int, Any}() end "" -function variable_mc_generation(pm::_PMs.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) +function variable_mc_generation(pm::_PM.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) variable_mc_generation_current_real(pm, nw=nw, bounded=bounded, report=report; kwargs...) variable_mc_generation_current_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...) - cnds = _PMs.conductor_ids(pm; nw=nw) + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) - _PMs.var(pm, nw)[:crg_bus] = Dict{Int, Any}() - _PMs.var(pm, nw)[:cig_bus] = Dict{Int, Any}() + var(pm, nw)[:crg_bus] = Dict{Int, Any}() + var(pm, nw)[:cig_bus] = Dict{Int, Any}() # store active and reactive power expressions for use in objective + post processing - _PMs.var(pm, nw)[:pg] = Dict{Int, Any}() - _PMs.var(pm, nw)[:qg] = Dict{Int, Any}() + var(pm, nw)[:pg] = Dict{Int, Any}() + var(pm, nw)[:qg] = Dict{Int, Any}() end "" -function variable_mc_voltage(pm::_PMs.AbstractIVRModel; nw=pm.cnw, bounded::Bool=true, kwargs...) +function variable_mc_voltage(pm::_PM.AbstractIVRModel; nw=pm.cnw, bounded::Bool=true, kwargs...) variable_mc_voltage_real(pm; nw=nw, bounded=bounded, kwargs...) variable_mc_voltage_imaginary(pm; nw=nw, bounded=bounded, kwargs...) # local infeasbility issues without proper initialization; # convergence issues start when the equivalent angles of the starting point # are further away than 90 degrees from the solution (as given by ACP) - # this is the default behaviour of _PMs, initialize all phases as (1,0) + # this is the default behaviour of _PM, initialize all phases as (1,0) # the magnitude seems to have little effect on the convergence (>0.05) # updating the starting point to a balanced phasor does the job - ncnd = length(_PMs.conductor_ids(pm)) + ncnd = length(conductor_ids(pm)) theta = [_wrap_to_pi(2 * pi / ncnd * (1-c)) for c in 1:ncnd] vm = 1 - for id in _PMs.ids(pm, nw, :bus) - busref = _PMs.ref(pm, nw, :bus, id) + for id in ids(pm, nw, :bus) + busref = ref(pm, nw, :bus, id) if !haskey(busref, "va_start") for c in 1:ncnd vr = vm*cos(theta[c]) vi = vm*sin(theta[c]) - JuMP.set_start_value(_PMs.var(pm, nw, :vr, id)[c], vr) - JuMP.set_start_value(_PMs.var(pm, nw, :vi, id)[c], vi) + JuMP.set_start_value(var(pm, nw, :vr, id)[c], vr) + JuMP.set_start_value(var(pm, nw, :vi, id)[c], vi) end end end # apply bounds if bounded if bounded - for i in _PMs.ids(pm, nw, :bus) + for i in ids(pm, nw, :bus) constraint_mc_voltage_magnitude_bounds(pm, i, nw=nw) end end @@ -135,15 +135,15 @@ end """ Defines how current distributes over series and shunt impedances of a pi-model branch """ -function constraint_mc_current_from(pm::_PMs.AbstractIVRModel, n::Int, f_bus, f_idx, g_sh_fr, b_sh_fr, tr, ti, tm) - vr_fr = _PMs.var(pm, n, :vr, f_bus) - vi_fr = _PMs.var(pm, n, :vi, f_bus) +function constraint_mc_current_from(pm::_PM.AbstractIVRModel, n::Int, f_bus, f_idx, g_sh_fr, b_sh_fr, tr, ti, tm) + vr_fr = var(pm, n, :vr, f_bus) + vi_fr = var(pm, n, :vi, f_bus) - csr_fr = _PMs.var(pm, n, :csr, f_idx[1]) - csi_fr = _PMs.var(pm, n, :csi, f_idx[1]) + csr_fr = var(pm, n, :csr, f_idx[1]) + csi_fr = var(pm, n, :csi, f_idx[1]) - cr_fr = _PMs.var(pm, n, :cr, f_idx) - ci_fr = _PMs.var(pm, n, :ci, f_idx) + cr_fr = var(pm, n, :cr, f_idx) + ci_fr = var(pm, n, :ci, f_idx) tr = tr ti = ti @@ -158,15 +158,15 @@ end """ Defines how current distributes over series and shunt impedances of a pi-model branch """ -function constraint_mc_current_to(pm::_PMs.AbstractIVRModel, n::Int, t_bus, f_idx, t_idx, g_sh_to, b_sh_to) - vr_to = _PMs.var(pm, n, :vr, t_bus) - vi_to = _PMs.var(pm, n, :vi, t_bus) +function constraint_mc_current_to(pm::_PM.AbstractIVRModel, n::Int, t_bus, f_idx, t_idx, g_sh_to, b_sh_to) + vr_to = var(pm, n, :vr, t_bus) + vi_to = var(pm, n, :vi, t_bus) - csr_to = -_PMs.var(pm, n, :csr, f_idx[1]) - csi_to = -_PMs.var(pm, n, :csi, f_idx[1]) + csr_to = -var(pm, n, :csr, f_idx[1]) + csi_to = -var(pm, n, :csi, f_idx[1]) - cr_to = _PMs.var(pm, n, :cr, t_idx) - ci_to = _PMs.var(pm, n, :ci, t_idx) + cr_to = var(pm, n, :cr, t_idx) + ci_to = var(pm, n, :ci, t_idx) g_sh_to = g_sh_to b_sh_to = b_sh_to @@ -179,15 +179,15 @@ end """ Defines voltage drop over a branch, linking from and to side complex voltage """ -function constraint_mc_voltage_drop(pm::_PMs.AbstractIVRModel, n::Int, i, f_bus, t_bus, f_idx, r, x, tr, ti, tm) - vr_fr = _PMs.var(pm, n, :vr, f_bus) - vi_fr = _PMs.var(pm, n, :vi, f_bus) +function constraint_mc_voltage_drop(pm::_PM.AbstractIVRModel, n::Int, i, f_bus, t_bus, f_idx, r, x, tr, ti, tm) + vr_fr = var(pm, n, :vr, f_bus) + vi_fr = var(pm, n, :vi, f_bus) - vr_to = _PMs.var(pm, n, :vr, t_bus) - vi_to = _PMs.var(pm, n, :vi, t_bus) + vr_to = var(pm, n, :vr, t_bus) + vi_to = var(pm, n, :vi, t_bus) - csr_fr = _PMs.var(pm, n, :csr, f_idx[1]) - csi_fr = _PMs.var(pm, n, :csi, f_idx[1]) + csr_fr = var(pm, n, :csr, f_idx[1]) + csi_fr = var(pm, n, :csi, f_idx[1]) r = r x = x @@ -199,13 +199,13 @@ end """ Bounds the voltage angle difference between bus pairs """ -function constraint_mc_voltage_angle_difference(pm::_PMs.AbstractIVRModel, n::Int, f_idx, angmin, angmax) +function constraint_mc_voltage_angle_difference(pm::_PM.AbstractIVRModel, n::Int, f_idx, angmin, angmax) i, f_bus, t_bus = f_idx - vr_fr = _PMs.var(pm, n, :vr, f_bus) - vi_fr = _PMs.var(pm, n, :vi, f_bus) - vr_to = _PMs.var(pm, n, :vr, t_bus) - vi_to = _PMs.var(pm, n, :vi, t_bus) + vr_fr = var(pm, n, :vr, f_bus) + vi_fr = var(pm, n, :vi, f_bus) + vr_to = var(pm, n, :vr, t_bus) + vi_to = var(pm, n, :vi, t_bus) vvr = vr_fr.*vr_to + vi_fr.*vi_to vvi = vi_fr.*vr_to - vr_fr.*vi_to @@ -217,24 +217,24 @@ end Kirchhoff's current law applied to buses `sum(cr + im*ci) = 0` """ -function constraint_mc_current_balance_load(pm::_PMs.AbstractIVRModel, n::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) - vr = _PMs.var(pm, n, :vr, i) - vi = _PMs.var(pm, n, :vi, i) - - cr = get(_PMs.var(pm, n), :cr, Dict()); _PMs._check_var_keys(cr, bus_arcs, "real current", "branch") - ci = get(_PMs.var(pm, n), :ci, Dict()); _PMs._check_var_keys(ci, bus_arcs, "imaginary current", "branch") - crd = get(_PMs.var(pm, n), :crd_bus, Dict()); _PMs._check_var_keys(crd, bus_loads, "real current", "load") - cid = get(_PMs.var(pm, n), :cid_bus, Dict()); _PMs._check_var_keys(cid, bus_loads, "imaginary current", "load") - crg = get(_PMs.var(pm, n), :crg_bus, Dict()); _PMs._check_var_keys(crg, bus_gens, "real current", "generator") - cig = get(_PMs.var(pm, n), :cig_bus, Dict()); _PMs._check_var_keys(cig, bus_gens, "imaginary current", "generator") - crs = get(_PMs.var(pm, n), :crs, Dict()); _PMs._check_var_keys(crs, bus_storage, "real currentr", "storage") - cis = get(_PMs.var(pm, n), :cis, Dict()); _PMs._check_var_keys(cis, bus_storage, "imaginary current", "storage") - crsw = get(_PMs.var(pm, n), :crsw, Dict()); _PMs._check_var_keys(crsw, bus_arcs_sw, "real current", "switch") - cisw = get(_PMs.var(pm, n), :cisw, Dict()); _PMs._check_var_keys(cisw, bus_arcs_sw, "imaginary current", "switch") - crt = get(_PMs.var(pm, n), :crt, Dict()); _PMs._check_var_keys(crt, bus_arcs_trans, "real current", "transformer") - cit = get(_PMs.var(pm, n), :cit, Dict()); _PMs._check_var_keys(cit, bus_arcs_trans, "imaginary current", "transformer") - - cnds = _PMs.conductor_ids(pm; nw=n) +function constraint_mc_current_balance_load(pm::_PM.AbstractIVRModel, n::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) + vr = var(pm, n, :vr, i) + vi = var(pm, n, :vi, i) + + cr = get(var(pm, n), :cr, Dict()); _PM._check_var_keys(cr, bus_arcs, "real current", "branch") + ci = get(var(pm, n), :ci, Dict()); _PM._check_var_keys(ci, bus_arcs, "imaginary current", "branch") + crd = get(var(pm, n), :crd_bus, Dict()); _PM._check_var_keys(crd, bus_loads, "real current", "load") + cid = get(var(pm, n), :cid_bus, Dict()); _PM._check_var_keys(cid, bus_loads, "imaginary current", "load") + crg = get(var(pm, n), :crg_bus, Dict()); _PM._check_var_keys(crg, bus_gens, "real current", "generator") + cig = get(var(pm, n), :cig_bus, Dict()); _PM._check_var_keys(cig, bus_gens, "imaginary current", "generator") + crs = get(var(pm, n), :crs, Dict()); _PM._check_var_keys(crs, bus_storage, "real currentr", "storage") + cis = get(var(pm, n), :cis, Dict()); _PM._check_var_keys(cis, bus_storage, "imaginary current", "storage") + crsw = get(var(pm, n), :crsw, Dict()); _PM._check_var_keys(crsw, bus_arcs_sw, "real current", "switch") + cisw = get(var(pm, n), :cisw, Dict()); _PM._check_var_keys(cisw, bus_arcs_sw, "imaginary current", "switch") + crt = get(var(pm, n), :crt, Dict()); _PM._check_var_keys(crt, bus_arcs_trans, "real current", "transformer") + cit = get(var(pm, n), :cit, Dict()); _PM._check_var_keys(cit, bus_arcs_trans, "imaginary current", "transformer") + + cnds = conductor_ids(pm; nw=n) ncnds = length(cnds) Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) @@ -263,15 +263,15 @@ function constraint_mc_current_balance_load(pm::_PMs.AbstractIVRModel, n::Int, i end "`p[f_idx]^2 + q[f_idx]^2 <= rate_a^2`" -function constraint_mc_thermal_limit_from(pm::_PMs.AbstractIVRModel, n::Int, f_idx, rate_a) +function constraint_mc_thermal_limit_from(pm::_PM.AbstractIVRModel, n::Int, f_idx, rate_a) (l, f_bus, t_bus) = f_idx - vr = _PMs.var(pm, n, :vr, f_bus) - vi = _PMs.var(pm, n, :vi, f_bus) - crf = _PMs.var(pm, n, :cr, f_idx) - cif = _PMs.var(pm, n, :ci, f_idx) + vr = var(pm, n, :vr, f_bus) + vi = var(pm, n, :vi, f_bus) + crf = var(pm, n, :cr, f_idx) + cif = var(pm, n, :ci, f_idx) - cnds = _PMs.conductor_ids(pm; nw=n) + cnds = conductor_ids(pm; nw=n) ncnds = length(cnds) for c in cnds @@ -280,15 +280,15 @@ function constraint_mc_thermal_limit_from(pm::_PMs.AbstractIVRModel, n::Int, f_i end "`p[t_idx]^2 + q[t_idx]^2 <= rate_a^2`" -function constraint_mc_thermal_limit_to(pm::_PMs.AbstractIVRModel, n::Int, t_idx, rate_a) +function constraint_mc_thermal_limit_to(pm::_PM.AbstractIVRModel, n::Int, t_idx, rate_a) (l, t_bus, f_bus) = t_idx - vr = _PMs.var(pm, n, :vr, t_bus) - vi = _PMs.var(pm, n, :vi, t_bus) - crt = _PMs.var(pm, n, :cr, t_idx) - cit = _PMs.var(pm, n, :ci, t_idx) + vr = var(pm, n, :vr, t_bus) + vi = var(pm, n, :vi, t_bus) + crt = var(pm, n, :cr, t_idx) + cit = var(pm, n, :ci, t_idx) - cnds = _PMs.conductor_ids(pm; nw=n) + cnds = conductor_ids(pm; nw=n) ncnds = length(cnds) for c in cnds @@ -301,15 +301,15 @@ Bounds the current magnitude at both from and to side of a branch `cr[f_idx]^2 + ci[f_idx]^2 <= c_rating_a^2` `cr[t_idx]^2 + ci[t_idx]^2 <= c_rating_a^2` """ -function constraint_mc_current_limit(pm::_PMs.AbstractIVRModel, n::Int, f_idx, c_rating_a) +function constraint_mc_current_limit(pm::_PM.AbstractIVRModel, n::Int, f_idx, c_rating_a) (l, f_bus, t_bus) = f_idx t_idx = (l, t_bus, f_bus) - crf = _PMs.var(pm, n, :cr, f_idx) - cif = _PMs.var(pm, n, :ci, f_idx) + crf = var(pm, n, :cr, f_idx) + cif = var(pm, n, :ci, f_idx) - crt = _PMs.var(pm, n, :cr, t_idx) - cit = _PMs.var(pm, n, :ci, t_idx) + crt = var(pm, n, :cr, t_idx) + cit = var(pm, n, :ci, t_idx) JuMP.@constraint(pm.model, crf.^2 + cif.^2 .<= c_rating_a.^2) JuMP.@constraint(pm.model, crt.^2 + cit.^2 .<= c_rating_a.^2) @@ -318,13 +318,13 @@ end """ `pmin <= Re(v*cg') <= pmax` """ -function constraint_mc_generation_active_power_limits(pm::_PMs.AbstractIVRModel, n::Int, i, bus, pmax, pmin) +function constraint_mc_generation_active_power_limits(pm::_PM.AbstractIVRModel, n::Int, i, bus, pmax, pmin) @assert pmin <= pmax - vr = _PMs.var(pm, n, :vr, bus) - vi = _PMs.var(pm, n, :vi, bus) - cr = _PMs.var(pm, n, :crg, i) - ci = _PMs.var(pm, n, :cig, i) + vr = var(pm, n, :vr, bus) + vi = var(pm, n, :vi, bus) + cr = var(pm, n, :crg, i) + ci = var(pm, n, :cig, i) JuMP.@constraint(pm.model, pmin .<= vr.*cr + vi.*ci) JuMP.@constraint(pm.model, pmax .>= vr.*cr + vi.*ci) @@ -333,54 +333,54 @@ end """ `qmin <= Im(v*cg') <= qmax` """ -function constraint_mc_generation_reactive_power_limits(pm::_PMs.AbstractIVRModel, n::Int, i, bus, qmax, qmin) +function constraint_mc_generation_reactive_power_limits(pm::_PM.AbstractIVRModel, n::Int, i, bus, qmax, qmin) @assert qmin <= qmax - vr = _PMs.var(pm, n, :vr, bus) - vi = _PMs.var(pm, n, :vi, bus) - cr = _PMs.var(pm, n, :crg, i) - ci = _PMs.var(pm, n, :cig, i) + vr = var(pm, n, :vr, bus) + vi = var(pm, n, :vi, bus) + cr = var(pm, n, :crg, i) + ci = var(pm, n, :cig, i) JuMP.@constraint(pm.model, qmin .<= vi.*cr - vr.*ci) JuMP.@constraint(pm.model, qmax .>= vi.*cr - vr.*ci) end "`pg[i] == pg`" -function constraint_mc_active_gen_setpoint(pm::_PMs.AbstractIVRModel, n::Int, i, pgref) - gen = _PMs.ref(pm, n, :gen, i) +function constraint_mc_active_gen_setpoint(pm::_PM.AbstractIVRModel, n::Int, i, pgref) + gen = ref(pm, n, :gen, i) bus = gen["gen_bus"] - vr = _PMs.var(pm, n, :vr, bus) - vi = _PMs.var(pm, n, :vi, bus) - cr = _PMs.var(pm, n, :crg, i) - ci = _PMs.var(pm, n, :cig, i) + vr = var(pm, n, :vr, bus) + vi = var(pm, n, :vi, bus) + cr = var(pm, n, :crg, i) + ci = var(pm, n, :cig, i) JuMP.@constraint(pm.model, pgref .== vr.*cr + vi.*ci) end "`qq[i] == qq`" -function constraint_mc_reactive_gen_setpoint(pm::_PMs.AbstractIVRModel, n::Int, i, qgref) - gen = _PMs.ref(pm, n, :gen, i) +function constraint_mc_reactive_gen_setpoint(pm::_PM.AbstractIVRModel, n::Int, i, qgref) + gen = ref(pm, n, :gen, i) bus = gen["gen_bus"] - vr = _PMs.var(pm, n, :vr, bus) - vi = _PMs.var(pm, n, :vi, bus) - cr = _PMs.var(pm, n, :crg, i) - ci = _PMs.var(pm, n, :cig, i) + vr = var(pm, n, :vr, bus) + vi = var(pm, n, :vi, bus) + cr = var(pm, n, :crg, i) + ci = var(pm, n, :cig, i) JuMP.@constraint(pm.model, qgref .== vi.*cr - vr.*ci) end -function _PMs._objective_min_fuel_cost_polynomial_linquad(pm::_PMs.AbstractIVRModel; report::Bool=true) +function _PM._objective_min_fuel_cost_polynomial_linquad(pm::_PM.AbstractIVRModel; report::Bool=true) gen_cost = Dict() dcline_cost = Dict() - for (n, nw_ref) in _PMs.nws(pm) + for (n, nw_ref) in nws(pm) for (i,gen) in nw_ref[:gen] bus = gen["gen_bus"] #to avoid function calls inside of @NLconstraint: - pg = _PMs.var(pm, n, :pg, i) - nc = length(_PMs.conductor_ids(pm, n)) + pg = var(pm, n, :pg, i) + nc = length(conductor_ids(pm, n)) if length(gen["cost"]) == 1 gen_cost[(n,i)] = gen["cost"][1] elseif length(gen["cost"]) == 2 @@ -397,21 +397,21 @@ function _PMs._objective_min_fuel_cost_polynomial_linquad(pm::_PMs.AbstractIVRMo sum( sum( gen_cost[(n,i)] for (i,gen) in nw_ref[:gen] ) + sum( dcline_cost[(n,i)] for (i,dcline) in nw_ref[:dcline] ) - for (n, nw_ref) in _PMs.nws(pm)) + for (n, nw_ref) in nws(pm)) ) end "adds pg_cost variables and constraints" -function objective_variable_pg_cost(pm::_PMs.AbstractIVRModel; report::Bool=true) - for (n, nw_ref) in _PMs.nws(pm) +function objective_variable_pg_cost(pm::_PM.AbstractIVRModel; report::Bool=true) + for (n, nw_ref) in nws(pm) gen_lines = calc_cost_pwl_lines(nw_ref[:gen]) #to avoid function calls inside of @NLconstraint - pg_cost = _PMs.var(pm, n)[:pg_cost] = JuMP.@variable(pm.model, - [i in _PMs.ids(pm, n, :gen)], base_name="$(n)_pg_cost", + pg_cost = var(pm, n)[:pg_cost] = JuMP.@variable(pm.model, + [i in ids(pm, n, :gen)], base_name="$(n)_pg_cost", ) - report && _PMs.sol_component_value(pm, n, :gen, :pg_cost, _PMs.ids(pm, n, :gen), pg_cost) + report && _IM.sol_component_value(pm, n, :gen, :pg_cost, ids(pm, n, :gen), pg_cost) nc = length(conductor_ids(pm, n)) @@ -426,23 +426,23 @@ function objective_variable_pg_cost(pm::_PMs.AbstractIVRModel; report::Bool=true end -function constraint_mc_trans_yy(pm::_PMs.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) - vr_fr_P = _PMs.var(pm, nw, :vr, f_bus)[f_cnd] - vi_fr_P = _PMs.var(pm, nw, :vi, f_bus)[f_cnd] +function constraint_mc_trans_yy(pm::_PM.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) + vr_fr_P = var(pm, nw, :vr, f_bus)[f_cnd] + vi_fr_P = var(pm, nw, :vi, f_bus)[f_cnd] vr_fr_n = 0 vi_fr_n = 0 - vr_to_P = _PMs.var(pm, nw, :vr, t_bus)[t_cnd] - vi_to_P = _PMs.var(pm, nw, :vi, t_bus)[t_cnd] + vr_to_P = var(pm, nw, :vr, t_bus)[t_cnd] + vi_to_P = var(pm, nw, :vi, t_bus)[t_cnd] vr_to_n = 0 vi_to_n = 0 - cr_fr_P = _PMs.var(pm, nw, :crt, f_idx)[f_cnd] - ci_fr_P = _PMs.var(pm, nw, :cit, f_idx)[f_cnd] - cr_to_P = _PMs.var(pm, nw, :crt, t_idx)[t_cnd] - ci_to_P = _PMs.var(pm, nw, :cit, t_idx)[t_cnd] + cr_fr_P = var(pm, nw, :crt, f_idx)[f_cnd] + ci_fr_P = var(pm, nw, :cit, f_idx)[f_cnd] + cr_to_P = var(pm, nw, :crt, t_idx)[t_cnd] + ci_to_P = var(pm, nw, :cit, t_idx)[t_cnd] # construct tm as a parameter or scaled variable depending on whether it is fixed or not - tm = [tm_fixed[p] ? tm_set[p] : _PMs.var(pm, nw, :tap, trans_id)[p] for p in _PMs.conductor_ids(pm)] + tm = [tm_fixed[p] ? tm_set[p] : var(pm, nw, :tap, trans_id)[p] for p in conductor_ids(pm)] scale = (tm_scale*pol).*tm_set JuMP.@constraint(pm.model, (vr_fr_P.-vr_fr_n) .== scale.*(vr_to_P.-vr_to_n)) @@ -452,21 +452,21 @@ function constraint_mc_trans_yy(pm::_PMs.AbstractIVRModel, nw::Int, trans_id::In end -function constraint_mc_trans_dy(pm::_PMs.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) - vr_fr_P = _PMs.var(pm, nw, :vr, f_bus)[f_cnd] - vi_fr_P = _PMs.var(pm, nw, :vi, f_bus)[f_cnd] - vr_to_P = _PMs.var(pm, nw, :vr, t_bus)[t_cnd] - vi_to_P = _PMs.var(pm, nw, :vi, t_bus)[t_cnd] +function constraint_mc_trans_dy(pm::_PM.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) + vr_fr_P = var(pm, nw, :vr, f_bus)[f_cnd] + vi_fr_P = var(pm, nw, :vi, f_bus)[f_cnd] + vr_to_P = var(pm, nw, :vr, t_bus)[t_cnd] + vi_to_P = var(pm, nw, :vi, t_bus)[t_cnd] vr_to_n = 0 vi_to_n = 0 - cr_fr_P = _PMs.var(pm, nw, :crt, f_idx)[f_cnd] - ci_fr_P = _PMs.var(pm, nw, :cit, f_idx)[f_cnd] - cr_to_P = _PMs.var(pm, nw, :crt, t_idx)[t_cnd] - ci_to_P = _PMs.var(pm, nw, :cit, t_idx)[t_cnd] + cr_fr_P = var(pm, nw, :crt, f_idx)[f_cnd] + ci_fr_P = var(pm, nw, :cit, f_idx)[f_cnd] + cr_to_P = var(pm, nw, :crt, t_idx)[t_cnd] + ci_to_P = var(pm, nw, :cit, t_idx)[t_cnd] # construct tm as a parameter or scaled variable depending on whether it is fixed or not - tm = [tm_fixed[p] ? tm_set[p] : _PMs.var(pm, nw, :tap, trans_id)[p] for p in _PMs.conductor_ids(pm)] + tm = [tm_fixed[p] ? tm_set[p] : var(pm, nw, :tap, trans_id)[p] for p in conductor_ids(pm)] scale = (tm_scale*pol).*tm_set n_phases = length(tm) @@ -481,9 +481,9 @@ end "" -function constraint_mc_load_wye(pm::_PMs.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) - vr = _PMs.var(pm, nw, :vr, bus_id) - vi = _PMs.var(pm, nw, :vi, bus_id) +function constraint_mc_load_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) + vr = var(pm, nw, :vr, bus_id) + vi = var(pm, nw, :vi, bus_id) nph = 3 @@ -496,31 +496,31 @@ function constraint_mc_load_wye(pm::_PMs.IVRPowerModel, nw::Int, id::Int, bus_id -b[i]*vr[i]*(vr[i]^2+vi[i]^2)^(beta[i]/2 -1) ) - _PMs.var(pm, nw, :crd_bus)[id] = crd - _PMs.var(pm, nw, :cid_bus)[id] = cid + var(pm, nw, :crd_bus)[id] = crd + var(pm, nw, :cid_bus)[id] = cid if report pd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], vr[i]*crd[i]+vi[i]*cid[i]) qd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], -vr[i]*cid[i]+vi[i]*crd[i]) - _PMs.sol(pm, nw, :load, id)[:pd_bus] = pd_bus - _PMs.sol(pm, nw, :load, id)[:qd_bus] = qd_bus + sol(pm, nw, :load, id)[:pd_bus] = pd_bus + sol(pm, nw, :load, id)[:qd_bus] = qd_bus - _PMs.sol(pm, nw, :load, id)[:crd_bus] = crd - _PMs.sol(pm, nw, :load, id)[:cid_bus] = cid + sol(pm, nw, :load, id)[:crd_bus] = crd + sol(pm, nw, :load, id)[:cid_bus] = cid pd = JuMP.@NLexpression(pm.model, [i in 1:nph], a[i]*(vr[i]^2+vi[i]^2)^(alpha[i]/2) ) qd = JuMP.@NLexpression(pm.model, [i in 1:nph], b[i]*(vr[i]^2+vi[i]^2)^(beta[i]/2) ) - _PMs.sol(pm, nw, :load, id)[:pd] = pd - _PMs.sol(pm, nw, :load, id)[:qd] = qd + sol(pm, nw, :load, id)[:pd] = pd + sol(pm, nw, :load, id)[:qd] = qd end end "" -function constraint_mc_load_delta(pm::_PMs.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) - vr = _PMs.var(pm, nw, :vr, bus_id) - vi = _PMs.var(pm, nw, :vi, bus_id) +function constraint_mc_load_delta(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) + vr = var(pm, nw, :vr, bus_id) + vi = var(pm, nw, :vi, bus_id) nph = 3 prev = Dict(i=>(i+nph-2)%nph+1 for i in 1:nph) @@ -541,30 +541,30 @@ function constraint_mc_load_delta(pm::_PMs.IVRPowerModel, nw::Int, id::Int, bus_ crd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], crd[i]-crd[prev[i]]) cid_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], cid[i]-cid[prev[i]]) - _PMs.var(pm, nw, :crd_bus)[id] = crd_bus - _PMs.var(pm, nw, :cid_bus)[id] = cid_bus + var(pm, nw, :crd_bus)[id] = crd_bus + var(pm, nw, :cid_bus)[id] = cid_bus if report pd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], vr[i]*crd_bus[i]+vi[i]*cid_bus[i]) qd_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], -vr[i]*cid_bus[i]+vi[i]*crd_bus[i]) - _PMs.sol(pm, nw, :load, id)[:pd_bus] = pd_bus - _PMs.sol(pm, nw, :load, id)[:qd_bus] = qd_bus + sol(pm, nw, :load, id)[:pd_bus] = pd_bus + sol(pm, nw, :load, id)[:qd_bus] = qd_bus pd = JuMP.@NLexpression(pm.model, [i in 1:nph], a[i]*(vrd[i]^2+vid[i]^2)^(alpha[i]/2) ) qd = JuMP.@NLexpression(pm.model, [i in 1:nph], b[i]*(vrd[i]^2+vid[i]^2)^(beta[i]/2) ) - _PMs.sol(pm, nw, :load, id)[:pd] = pd - _PMs.sol(pm, nw, :load, id)[:qd] = qd + sol(pm, nw, :load, id)[:pd] = pd + sol(pm, nw, :load, id)[:qd] = qd end end "" -function constraint_mc_generation_wye(pm::_PMs.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) - vr = _PMs.var(pm, nw, :vr, bus_id) - vi = _PMs.var(pm, nw, :vi, bus_id) - crg = _PMs.var(pm, nw, :crg, id) - cig = _PMs.var(pm, nw, :cig, id) +function constraint_mc_generation_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) + vr = var(pm, nw, :vr, bus_id) + vi = var(pm, nw, :vi, bus_id) + crg = var(pm, nw, :crg, id) + cig = var(pm, nw, :cig, id) nph = 3 @@ -588,27 +588,27 @@ function constraint_mc_generation_wye(pm::_PMs.IVRPowerModel, nw::Int, id::Int, end end - _PMs.var(pm, nw, :crg_bus)[id] = crg - _PMs.var(pm, nw, :cig_bus)[id] = cig - _PMs.var(pm, nw, :pg)[id] = pg - _PMs.var(pm, nw, :qg)[id] = qg + var(pm, nw, :crg_bus)[id] = crg + var(pm, nw, :cig_bus)[id] = cig + var(pm, nw, :pg)[id] = pg + var(pm, nw, :qg)[id] = qg if report - _PMs.sol(pm, nw, :gen, id)[:crg_bus] = _PMs.var(pm, nw, :crg_bus, id) - _PMs.sol(pm, nw, :gen, id)[:cig_bus] = _PMs.var(pm, nw, :crg_bus, id) + sol(pm, nw, :gen, id)[:crg_bus] = var(pm, nw, :crg_bus, id) + sol(pm, nw, :gen, id)[:cig_bus] = var(pm, nw, :crg_bus, id) - _PMs.sol(pm, nw, :gen, id)[:pg] = pg - _PMs.sol(pm, nw, :gen, id)[:qg] = qg + sol(pm, nw, :gen, id)[:pg] = pg + sol(pm, nw, :gen, id)[:qg] = qg end end "" -function constraint_mc_generation_delta(pm::_PMs.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) - vr = _PMs.var(pm, nw, :vr, bus_id) - vi = _PMs.var(pm, nw, :vi, bus_id) - crg = _PMs.var(pm, nw, :crg, id) - cig = _PMs.var(pm, nw, :cig, id) +function constraint_mc_generation_delta(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) + vr = var(pm, nw, :vr, bus_id) + vi = var(pm, nw, :vi, bus_id) + crg = var(pm, nw, :crg, id) + cig = var(pm, nw, :cig, id) nph = 3 prev = Dict(i=>(i+nph-2)%nph+1 for i in 1:nph) @@ -630,15 +630,15 @@ function constraint_mc_generation_delta(pm::_PMs.IVRPowerModel, nw::Int, id::Int crg_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], crg[i]-crg[prev[i]]) cig_bus = JuMP.@NLexpression(pm.model, [i in 1:nph], cig[i]-cig[prev[i]]) - _PMs.var(pm, nw, :crg_bus)[id] = crg_bus - _PMs.var(pm, nw, :cig_bus)[id] = cig_bus - _PMs.var(pm, nw, :pg)[id] = pg - _PMs.var(pm, nw, :qg)[id] = qg + var(pm, nw, :crg_bus)[id] = crg_bus + var(pm, nw, :cig_bus)[id] = cig_bus + var(pm, nw, :pg)[id] = pg + var(pm, nw, :qg)[id] = qg if report - _PMs.sol(pm, nw, :gen, id)[:crg_bus] = crg_bus - _PMs.sol(pm, nw, :gen, id)[:cig_bus] = cig_bus - _PMs.sol(pm, nw, :gen, id)[:pg] = pg - _PMs.sol(pm, nw, :gen, id)[:qg] = qg + sol(pm, nw, :gen, id)[:crg_bus] = crg_bus + sol(pm, nw, :gen, id)[:cig_bus] = cig_bus + sol(pm, nw, :gen, id)[:pg] = pg + sol(pm, nw, :gen, id)[:qg] = qg end end diff --git a/src/form/shared.jl b/src/form/shared.jl index ea7aa8b7b..3697a6f22 100644 --- a/src/form/shared.jl +++ b/src/form/shared.jl @@ -1,26 +1,26 @@ import LinearAlgebra: diag "`vm[i] == vmref`" -function constraint_mc_voltage_magnitude_setpoint(pm::_PMs.AbstractWModels, n::Int, i::Int, vmref) - w = _PMs.var(pm, n, :w, i) +function constraint_mc_voltage_magnitude_setpoint(pm::_PM.AbstractWModels, n::Int, i::Int, vmref) + w = var(pm, n, :w, i) JuMP.@constraint(pm.model, w .== vmref.^2) end "" -function constraint_mc_power_balance_slack(pm::_PMs.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) - w = _PMs.var(pm, nw, :w, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(_PMs.var(pm, nw), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - p_slack = _PMs.var(pm, nw, :p_slack, i) - q_slack = _PMs.var(pm, nw, :q_slack, i) +function constraint_mc_power_balance_slack(pm::_PM.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + w = var(pm, nw, :w, i) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + p_slack = var(pm, nw, :p_slack, i) + q_slack = var(pm, nw, :q_slack, i) Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) Bt = isempty(bus_bs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_bs)) @@ -49,24 +49,24 @@ function constraint_mc_power_balance_slack(pm::_PMs.AbstractWModels, nw::Int, i, + q_slack ) - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end "do nothing, no way to represent this in these variables" -function constraint_mc_theta_ref(pm::_PMs.AbstractWModels, n::Int, d::Int, va_ref) +function constraint_mc_theta_ref(pm::_PM.AbstractWModels, n::Int, d::Int, va_ref) end "Creates phase angle constraints at reference buses" -function constraint_mc_theta_ref(pm::_PMs.AbstractPolarModels, n::Int, d::Int, va_ref) - cnds = _PMs.conductor_ids(pm; nw=n) +function constraint_mc_theta_ref(pm::_PM.AbstractPolarModels, n::Int, d::Int, va_ref) + cnds = conductor_ids(pm; nw=n) nconductors = length(cnds) - va = _PMs.var(pm, n, :va, d) + va = var(pm, n, :va, d) JuMP.@constraint(pm.model, va .== va_ref) end @@ -77,30 +77,30 @@ For a variable tap transformer, fix the tap variables which are fixed. For example, an OLTC where the third phase is fixed, will have tap variables for all phases, but the third tap variable should be fixed. """ -function constraint_mc_oltc_tap_fix(pm::_PMs.AbstractPowerModel, i::Int, fixed::Vector, tm::Vector; nw=pm.cnw) +function constraint_mc_oltc_tap_fix(pm::_PM.AbstractPowerModel, i::Int, fixed::Vector, tm::Vector; nw=pm.cnw) for (c,fixed) in enumerate(fixed) if fixed - JuMP.@constraint(pm.model, _PMs.var(pm, nw, c, :tap)[i]==tm[c]) + JuMP.@constraint(pm.model, var(pm, nw, c, :tap)[i]==tm[c]) end end end "KCL for load shed problem with transformers (AbstractWForms)" -function constraint_mc_power_balance_shed(pm::_PMs.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) - w = _PMs.var(pm, nw, :w, i) - p = get(_PMs.var(pm, nw), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") - q = get(_PMs.var(pm, nw), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") - pg = get(_PMs.var(pm, nw), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - psw = get(_PMs.var(pm, nw), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") - qsw = get(_PMs.var(pm, nw), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") - pt = get(_PMs.var(pm, nw), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") - qt = get(_PMs.var(pm, nw), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") - z_demand = _PMs.var(pm, nw, :z_demand) - z_shunt = _PMs.var(pm, nw, :z_shunt) +function constraint_mc_power_balance_shed(pm::_PM.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + w = var(pm, nw, :w, i) + p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") + q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") + pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + psw = get(var(pm, nw), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") + qsw = get(var(pm, nw), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") + pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") + qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + z_demand = var(pm, nw, :z_demand) + z_shunt = var(pm, nw, :z_shunt) bus_GsBs = [(n,bus_gs[n], bus_bs[n]) for n in keys(bus_gs)] @@ -125,32 +125,32 @@ function constraint_mc_power_balance_shed(pm::_PMs.AbstractWModels, nw::Int, i, - sum(z_shunt[n].*(-w.*diag(Bt')) for (n,Gs,Bs) in bus_GsBs) ) - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end "" -function constraint_mc_power_balance_load(pm::_PMs.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) - Wr = _PMs.var(pm, nw, :Wr, i) - Wi = _PMs.var(pm, nw, :Wi, i) - P = get(_PMs.var(pm, nw), :P, Dict()); _PMs._check_var_keys(P, bus_arcs, "active power", "branch") - Q = get(_PMs.var(pm, nw), :Q, Dict()); _PMs._check_var_keys(Q, bus_arcs, "reactive power", "branch") - Psw = get(_PMs.var(pm, nw), :Psw, Dict()); _PMs._check_var_keys(Psw, bus_arcs_sw, "active power", "switch") - Qsw = get(_PMs.var(pm, nw), :Qsw, Dict()); _PMs._check_var_keys(Qsw, bus_arcs_sw, "reactive power", "switch") - Pt = get(_PMs.var(pm, nw), :Pt, Dict()); _PMs._check_var_keys(Pt, bus_arcs_trans, "active power", "transformer") - Qt = get(_PMs.var(pm, nw), :Qt, Dict()); _PMs._check_var_keys(Qt, bus_arcs_trans, "reactive power", "transformer") - - pd = get(_PMs.var(pm, nw), :pd_bus, Dict()); _PMs._check_var_keys(pg, bus_loads, "active power", "load") - qd = get(_PMs.var(pm, nw), :qd_bus, Dict()); _PMs._check_var_keys(qg, bus_loads, "reactive power", "load") - pg = get(_PMs.var(pm, nw), :pg_bus, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") - qg = get(_PMs.var(pm, nw), :qg_bus, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") - ps = get(_PMs.var(pm, nw), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") - qs = get(_PMs.var(pm, nw), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") - - cnds = _PMs.conductor_ids(pm; nw=nw) +function constraint_mc_power_balance_load(pm::_PM.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) + Wr = var(pm, nw, :Wr, i) + Wi = var(pm, nw, :Wi, i) + P = get(var(pm, nw), :P, Dict()); _PM._check_var_keys(P, bus_arcs, "active power", "branch") + Q = get(var(pm, nw), :Q, Dict()); _PM._check_var_keys(Q, bus_arcs, "reactive power", "branch") + Psw = get(var(pm, nw), :Psw, Dict()); _PM._check_var_keys(Psw, bus_arcs_sw, "active power", "switch") + Qsw = get(var(pm, nw), :Qsw, Dict()); _PM._check_var_keys(Qsw, bus_arcs_sw, "reactive power", "switch") + Pt = get(var(pm, nw), :Pt, Dict()); _PM._check_var_keys(Pt, bus_arcs_trans, "active power", "transformer") + Qt = get(var(pm, nw), :Qt, Dict()); _PM._check_var_keys(Qt, bus_arcs_trans, "reactive power", "transformer") + + pd = get(var(pm, nw), :pd_bus, Dict()); _PM._check_var_keys(pg, bus_loads, "active power", "load") + qd = get(var(pm, nw), :qd_bus, Dict()); _PM._check_var_keys(qg, bus_loads, "reactive power", "load") + pg = get(var(pm, nw), :pg_bus, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") + qg = get(var(pm, nw), :qg_bus, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") + ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") + qs = get(var(pm, nw), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") + + cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) @@ -178,40 +178,40 @@ function constraint_mc_power_balance_load(pm::_PMs.AbstractWModels, nw::Int, i, - diag(-Wr*Bt'+Wi*Gt') ) - if _PMs.report_duals(pm) - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p - _PMs.sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q + if _PM.report_duals(pm) + sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end end "delegate back to PowerModels" -function constraint_mc_ohms_yt_from(pm::_PMs.AbstractWModels, n::Int, c::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, tm) - _PMs.constraint_ohms_yt_from(pm, n, c, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, tm) +function constraint_mc_ohms_yt_from(pm::_PM.AbstractWModels, n::Int, c::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, tm) + _PM.constraint_ohms_yt_from(pm, n, c, f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, tm) end "delegate back to PowerModels" -function constraint_mc_ohms_yt_to(pm::_PMs.AbstractWModels, n::Int, c::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) - _PMs.constraint_ohms_yt_to(pm, n, c, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) +function constraint_mc_ohms_yt_to(pm::_PM.AbstractWModels, n::Int, c::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) + _PM.constraint_ohms_yt_to(pm, n, c, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) end "on/off bus voltage constraint for relaxed forms" -function constraint_mc_bus_voltage_on_off(pm::_PMs.AbstractWModels, n::Int; kwargs...) - for (i, bus) in _PMs.ref(pm, n, :bus) +function constraint_mc_bus_voltage_on_off(pm::_PM.AbstractWModels, n::Int; kwargs...) + for (i, bus) in ref(pm, n, :bus) constraint_mc_voltage_magnitude_sqr_on_off(pm, i, nw=n) end end -function constraint_mc_voltage_angle_difference(pm::_PMs.AbstractPolarModels, n::Int, f_idx, angmin, angmax) +function constraint_mc_voltage_angle_difference(pm::_PM.AbstractPolarModels, n::Int, f_idx, angmin, angmax) i, f_bus, t_bus = f_idx - va_fr = _PMs.var(pm, n, :va, f_bus) - va_to = _PMs.var(pm, n, :va, t_bus) + va_fr = var(pm, n, :va, f_bus) + va_to = var(pm, n, :va, t_bus) - for c in _PMs.conductor_ids(pm; nw=n) + for c in conductor_ids(pm; nw=n) JuMP.@constraint(pm.model, va_fr[c] - va_to[c] <= angmax[c]) JuMP.@constraint(pm.model, va_fr[c] - va_to[c] >= angmin[c]) end @@ -219,29 +219,29 @@ end "" -function constraint_mc_voltage_angle_difference(pm::_PMs.AbstractWModels, n::Int, f_idx, angmin, angmax) +function constraint_mc_voltage_angle_difference(pm::_PM.AbstractWModels, n::Int, f_idx, angmin, angmax) i, f_bus, t_bus = f_idx - ncnds = length(_PMs.conductor_ids(pm, n)) + ncnds = length(conductor_ids(pm, n)) - w_fr = _PMs.var(pm, n, :w, f_bus) - w_to = _PMs.var(pm, n, :w, t_bus) - wr = [_PMs.var(pm, n, :wr)[(f_bus, t_bus, c, c)] for c in 1:ncnds] - wi = [_PMs.var(pm, n, :wi)[(f_bus, t_bus, c, c)] for c in 1:ncnds] + w_fr = var(pm, n, :w, f_bus) + w_to = var(pm, n, :w, t_bus) + wr = [var(pm, n, :wr)[(f_bus, t_bus, c, c)] for c in 1:ncnds] + wi = [var(pm, n, :wi)[(f_bus, t_bus, c, c)] for c in 1:ncnds] JuMP.@constraint(pm.model, wi .<= tan.(angmax).*wr) JuMP.@constraint(pm.model, wi .>= tan.(angmin).*wr) for c in 1:ncnds - _PMs.cut_complex_product_and_angle_difference(pm.model, w_fr[c], w_to[c], wr[c], wi[c], angmin[c], angmax[c]) + _PM.cut_complex_product_and_angle_difference(pm.model, w_fr[c], w_to[c], wr[c], wi[c], angmin[c], angmax[c]) end end -function constraint_mc_storage_on_off(pm::_PMs.AbstractPowerModel, n::Int, i, pmin, pmax, qmin, qmax, charge_ub, discharge_ub) - z_storage =_PMs.var(pm, n, :z_storage, i) - ps =_PMs.var(pm, n, :ps, i) - qs =_PMs.var(pm, n, :qs, i) +function constraint_mc_storage_on_off(pm::_PM.AbstractPowerModel, n::Int, i, pmin, pmax, qmin, qmax, charge_ub, discharge_ub) + z_storage =var(pm, n, :z_storage, i) + ps =var(pm, n, :ps, i) + qs =var(pm, n, :qs, i) JuMP.@constraint(pm.model, ps .<= z_storage.*pmax) JuMP.@constraint(pm.model, ps .>= z_storage.*pmin) @@ -252,12 +252,12 @@ end "" -function constraint_mc_generation_wye(pm::_PMs.AbstractPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) - _PMs.var(pm, nw, :pg_bus)[id] = _PMs.var(pm, nw, :pg, id) - _PMs.var(pm, nw, :qg_bus)[id] = _PMs.var(pm, nw, :qg, id) +function constraint_mc_generation_wye(pm::_PM.AbstractPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) + var(pm, nw, :pg_bus)[id] = var(pm, nw, :pg, id) + var(pm, nw, :qg_bus)[id] = var(pm, nw, :qg, id) if report - _PMs.sol(pm, nw, :gen, id)[:pg_bus] = _PMs.var(pm, nw, :pg_bus, id) - _PMs.sol(pm, nw, :gen, id)[:qg_bus] = _PMs.var(pm, nw, :qg_bus, id) + sol(pm, nw, :gen, id)[:pg_bus] = var(pm, nw, :pg_bus, id) + sol(pm, nw, :gen, id)[:qg_bus] = var(pm, nw, :qg_bus, id) end end diff --git a/src/form/wr.jl b/src/form/wr.jl index 4c4aaed29..94c4fab6a 100644 --- a/src/form/wr.jl +++ b/src/form/wr.jl @@ -1,17 +1,17 @@ # "" -# function constraint_mc_model_voltage(pm::_PMs.AbstractWRModel, n::Int) -# w = _PMs.var(pm, n, :w) -# wr = _PMs.var(pm, n, :wr) -# wi = _PMs.var(pm, n, :wi) +# function constraint_mc_model_voltage(pm::_PM.AbstractWRModel, n::Int) +# w = var(pm, n, :w) +# wr = var(pm, n, :wr) +# wi = var(pm, n, :wi) # -# for c in 1:length(_PMs.conductor_ids(pm, n)) -# for d in c:length(_PMs.conductor_ids(pm)) -# for (i,j) in _PMs.ids(pm, n, :buspairs) +# for c in 1:length(conductor_ids(pm, n)) +# for d in c:length(conductor_ids(pm)) +# for (i,j) in ids(pm, n, :buspairs) # InfrastructureModels.relaxation_complex_product(pm.model, w[i][d], w[j][c], wr[(i,j,c,d)], wi[(i,j,c,d)]) # end # # if d != c -# for i in _PMs.ids(pm, n, :bus) +# for i in ids(pm, n, :bus) # InfrastructureModels.relaxation_complex_product(pm.model, w[i][d], w[i][c], wr[(i,i,c,d)], wi[(i,i,c,d)]) # end # end @@ -21,21 +21,21 @@ # "power balance constraint with line shunts and transformers for relaxed WR forms" -# function constraint_mc_power_balance(pm::_PMs.AbstractWRModel, nw::Int, c::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) -# w = _PMs.var(pm, nw, c, :w, i) -# p = get(_PMs.var(pm, nw, c), :p, Dict()); _PMs._check_var_keys(p, bus_arcs, "active power", "branch") -# q = get(_PMs.var(pm, nw, c), :q, Dict()); _PMs._check_var_keys(q, bus_arcs, "reactive power", "branch") -# pg = get(_PMs.var(pm, nw, c), :pg, Dict()); _PMs._check_var_keys(pg, bus_gens, "active power", "generator") -# qg = get(_PMs.var(pm, nw, c), :qg, Dict()); _PMs._check_var_keys(qg, bus_gens, "reactive power", "generator") -# ps = get(_PMs.var(pm, nw, c), :ps, Dict()); _PMs._check_var_keys(ps, bus_storage, "active power", "storage") -# qs = get(_PMs.var(pm, nw, c), :qs, Dict()); _PMs._check_var_keys(qs, bus_storage, "reactive power", "storage") -# psw = get(_PMs.var(pm, nw, c), :psw, Dict()); _PMs._check_var_keys(psw, bus_arcs_sw, "active power", "switch") -# qsw = get(_PMs.var(pm, nw, c), :qsw, Dict()); _PMs._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") -# pt = get(_PMs.var(pm, nw, c), :pt, Dict()); _PMs._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") -# qt = get(_PMs.var(pm, nw, c), :qt, Dict()); _PMs._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") +# function constraint_mc_power_balance(pm::_PM.AbstractWRModel, nw::Int, c::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) +# w = var(pm, nw, c, :w, i) +# p = get(var(pm, nw, c), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") +# q = get(var(pm, nw, c), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") +# pg = get(var(pm, nw, c), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") +# qg = get(var(pm, nw, c), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") +# ps = get(var(pm, nw, c), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") +# qs = get(var(pm, nw, c), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") +# psw = get(var(pm, nw, c), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") +# qsw = get(var(pm, nw, c), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") +# pt = get(var(pm, nw, c), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") +# qt = get(var(pm, nw, c), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") # # -# _PMs.con(pm, nw, c, :kcl_p)[i] = JuMP.@constraint(pm.model, +# con(pm, nw, c, :kcl_p)[i] = JuMP.@constraint(pm.model, # sum(p[a] for a in bus_arcs) # + sum(psw[a_sw] for a_sw in bus_arcs_sw) # + sum(pt[a_trans] for a_trans in bus_arcs_trans) @@ -45,7 +45,7 @@ # - sum(pd for pd in values(bus_pd)) # - sum(gs for gs in values(bus_gs))*w # ) -# _PMs.con(pm, nw, c, :kcl_q)[i] = JuMP.@constraint(pm.model, +# con(pm, nw, c, :kcl_q)[i] = JuMP.@constraint(pm.model, # sum(q[a] for a in bus_arcs) # + sum(qsw[a_sw] for a_sw in bus_arcs_sw) # + sum(qt[a_trans] for a_trans in bus_arcs_trans) diff --git a/src/io/common.jl b/src/io/common.jl index 601e57faa..d280548a6 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -68,16 +68,16 @@ function correct_network_data!(data::Dict{String,Any}; make_pu::Bool=true) if make_pu make_per_unit!(data) - _PMs.check_connectivity(data) - _PMs.correct_transformer_parameters!(data) - _PMs.correct_voltage_angle_differences!(data) - _PMs.correct_thermal_limits!(data) - _PMs.correct_branch_directions!(data) - _PMs.check_branch_loops(data) - _PMs.correct_bus_types!(data) - _PMs.correct_dcline_limits!(data) - _PMs.correct_cost_functions!(data) - _PMs.standardize_cost_terms!(data) + _PM.check_connectivity(data) + _PM.correct_transformer_parameters!(data) + _PM.correct_voltage_angle_differences!(data) + _PM.correct_thermal_limits!(data) + _PM.correct_branch_directions!(data) + _PM.check_branch_loops(data) + _PM.correct_bus_types!(data) + _PM.correct_dcline_limits!(data) + _PM.correct_cost_functions!(data) + _PM.standardize_cost_terms!(data) end end end diff --git a/src/prob/common.jl b/src/prob/common.jl index 4d82fc52f..b205ff410 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -3,11 +3,11 @@ function run_mc_model(data::Dict{String,<:Any}, model_type, solver, build_mc; re if get(data, "data_model", "mathematical") == "engineering" data_math = transform_data_model(data) - result = _PMs.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) + result = _PM.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) result["solution"] = solution_math2eng(result["solution"], data_math; make_si=get(data, "per_unit", false), make_deg=get(data, "per_unit", false)) else - result = _PMs.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) + result = _PM.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) end return result diff --git a/src/prob/debug.jl b/src/prob/debug.jl index 803b89666..aea63e94b 100644 --- a/src/prob/debug.jl +++ b/src/prob/debug.jl @@ -25,7 +25,7 @@ end "OPF problem with slack power at every bus" -function build_mc_opf_pbs(pm::_PMs.AbstractPowerModel) +function build_mc_opf_pbs(pm::_PM.AbstractPowerModel) variable_mc_voltage(pm) variable_mc_branch_flow(pm) @@ -37,15 +37,15 @@ function build_mc_opf_pbs(pm::_PMs.AbstractPowerModel) constraint_mc_model_voltage(pm) - for i in _PMs.ids(pm, :ref_buses) + for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_power_balance_slack(pm, i) end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_ohms_yt_from(pm, i) constraint_mc_ohms_yt_to(pm, i) @@ -60,7 +60,7 @@ end "PF problem with slack power at every bus" -function build_mc_pf_pbs(pm::_PMs.AbstractPowerModel) +function build_mc_pf_pbs(pm::_PM.AbstractPowerModel) variable_mc_voltage(pm; bounded=false) variable_mc_branch_flow(pm; bounded=false) @@ -72,29 +72,29 @@ function build_mc_pf_pbs(pm::_PMs.AbstractPowerModel) constraint_mc_model_voltage(pm) - for (i,bus) in _PMs.ref(pm, :ref_buses) + for (i,bus) in ref(pm, :ref_buses) constraint_mc_theta_ref(pm, i) @assert bus["bus_type"] == 3 constraint_mc_voltage_magnitude_setpoint(pm, i) end - for (i,bus) in _PMs.ref(pm, :bus) + for (i,bus) in ref(pm, :bus) constraint_mc_power_balance_slack(pm, i) # PV Bus Constraints - if length(_PMs.ref(pm, :bus_gens, i)) > 0 && !(i in _PMs.ids(pm,:ref_buses)) + if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) # this assumes inactive generators are filtered out of bus_gens @assert bus["bus_type"] == 2 constraint_mc_voltage_magnitude_setpoint(pm, i) - for j in _PMs.ref(pm, :bus_gens, i) + for j in ref(pm, :bus_gens, i) constraint_mc_active_gen_setpoint(pm, j) end end end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_ohms_yt_from(pm, i) constraint_mc_ohms_yt_to(pm, i) end diff --git a/src/prob/mld.jl b/src/prob/mld.jl index 9783291b2..225e2acc3 100644 --- a/src/prob/mld.jl +++ b/src/prob/mld.jl @@ -35,7 +35,7 @@ end "Load shedding problem including storage (snap-shot)" -function build_mc_mld(pm::_PMs.AbstractPowerModel) +function build_mc_mld(pm::_PM.AbstractPowerModel) variable_mc_indicator_bus_voltage(pm; relax=true) variable_mc_bus_voltage_on_off(pm) @@ -46,9 +46,9 @@ function build_mc_mld(pm::_PMs.AbstractPowerModel) variable_mc_generation_on_off(pm) # variable_mc_storage(pm) - _PMs.variable_storage_energy(pm) - _PMs.variable_storage_charge(pm) - _PMs.variable_storage_discharge(pm) + _PM.variable_storage_energy(pm) + _PM.variable_storage_charge(pm) + _PM.variable_storage_discharge(pm) variable_mc_indicator_storage(pm; relax=true) variable_mc_on_off_storage(pm) @@ -57,28 +57,28 @@ function build_mc_mld(pm::_PMs.AbstractPowerModel) constraint_mc_model_voltage(pm) - for i in _PMs.ids(pm, :ref_buses) + for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end constraint_mc_bus_voltage_on_off(pm) - for i in _PMs.ids(pm, :gen) + for i in ids(pm, :gen) constraint_mc_generation_on_off(pm, i) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_power_balance_shed(pm, i) end - for i in _PMs.ids(pm, :storage) - _PMs.constraint_storage_state(pm, i) - _PMs.constraint_storage_complementarity_nl(pm, i) + for i in ids(pm, :storage) + _PM.constraint_storage_state(pm, i) + _PM.constraint_storage_complementarity_nl(pm, i) constraint_mc_storage_loss(pm, i) constraint_mc_storage_thermal_limit(pm, i) end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_ohms_yt_from(pm, i) constraint_mc_ohms_yt_to(pm, i) @@ -88,7 +88,7 @@ function build_mc_mld(pm::_PMs.AbstractPowerModel) constraint_mc_thermal_limit_to(pm, i) end - for i in _PMs.ids(pm, :transformer) + for i in ids(pm, :transformer) constraint_mc_trans(pm, i) end @@ -97,7 +97,7 @@ end "Load shedding problem for Branch Flow model" -function build_mc_mld_bf(pm::_PMs.AbstractPowerModel) +function build_mc_mld_bf(pm::_PM.AbstractPowerModel) variable_mc_indicator_bus_voltage(pm; relax=true) variable_mc_bus_voltage_on_off(pm) @@ -113,21 +113,21 @@ function build_mc_mld_bf(pm::_PMs.AbstractPowerModel) constraint_mc_model_current(pm) - for i in _PMs.ids(pm, :ref_buses) + for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end constraint_mc_bus_voltage_on_off(pm) - for i in _PMs.ids(pm, :gen) + for i in ids(pm, :gen) constraint_mc_generation_on_off(pm, i) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_power_balance_shed(pm, i) end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_flow_losses(pm, i) constraint_mc_model_voltage_magnitude_difference(pm, i) @@ -137,7 +137,7 @@ function build_mc_mld_bf(pm::_PMs.AbstractPowerModel) constraint_mc_thermal_limit_to(pm, i) end - for i in _PMs.ids(pm, :transformer) + for i in ids(pm, :transformer) constraint_mc_trans(pm, i) end @@ -146,7 +146,7 @@ end "Standard unit commitment (!relaxed) load shedding problem" -function build_mc_mld_uc(pm::_PMs.AbstractPowerModel) +function build_mc_mld_uc(pm::_PM.AbstractPowerModel) variable_mc_indicator_bus_voltage(pm; relax=false) variable_mc_bus_voltage_on_off(pm) @@ -165,28 +165,28 @@ function build_mc_mld_uc(pm::_PMs.AbstractPowerModel) constraint_mc_model_voltage(pm) - for i in _PMs.ids(pm, :ref_buses) + for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end constraint_mc_bus_voltage_on_off(pm) - for i in _PMs.ids(pm, :gen) + for i in ids(pm, :gen) constraint_mc_generation_on_off(pm, i) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_power_balance_shed(pm, i) end - for i in _PMs.ids(pm, :storage) - _PMs.constraint_storage_state(pm, i) - _PMs.constraint_storage_complementarity_nl(pm, i) + for i in ids(pm, :storage) + _PM.constraint_storage_state(pm, i) + _PM.constraint_storage_complementarity_nl(pm, i) constraint_mc_storage_loss(pm, i) constraint_mc_storage_thermal_limit(pm, i) end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_ohms_yt_from(pm, i) constraint_mc_ohms_yt_to(pm, i) @@ -196,7 +196,7 @@ function build_mc_mld_uc(pm::_PMs.AbstractPowerModel) constraint_mc_thermal_limit_to(pm, i) end - for i in _PMs.ids(pm, :transformer) + for i in ids(pm, :transformer) constraint_mc_trans(pm, i) end diff --git a/src/prob/opf.jl b/src/prob/opf.jl index fc027ab55..b11175ff9 100644 --- a/src/prob/opf.jl +++ b/src/prob/opf.jl @@ -1,6 +1,6 @@ "" function run_ac_mc_opf(file, solver; kwargs...) - return run_mc_opf(file, _PMs.ACPPowerModel, solver; kwargs...) + return run_mc_opf(file, _PM.ACPPowerModel, solver; kwargs...) end @@ -17,7 +17,7 @@ end "" -function build_mc_opf(pm::_PMs.AbstractPowerModel) +function build_mc_opf(pm::_PM.AbstractPowerModel) variable_mc_voltage(pm) variable_mc_branch_flow(pm) variable_mc_transformer_flow(pm) @@ -27,32 +27,32 @@ function build_mc_opf(pm::_PMs.AbstractPowerModel) constraint_mc_model_voltage(pm) - for i in _PMs.ids(pm, :ref_buses) + for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end # generators should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :gen) + for id in ids(pm, :gen) constraint_mc_generation(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :load) + for id in ids(pm, :load) constraint_mc_load(pm, id) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_power_balance_load(pm, i) end - for i in _PMs.ids(pm, :storage) - _PMs.constraint_storage_state(pm, i) - _PMs.constraint_storage_complementarity_nl(pm, i) + for i in ids(pm, :storage) + _PM.constraint_storage_state(pm, i) + _PM.constraint_storage_complementarity_nl(pm, i) constraint_mc_storage_loss(pm, i) constraint_mc_storage_thermal_limit(pm, i) end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_ohms_yt_from(pm, i) constraint_mc_ohms_yt_to(pm, i) @@ -62,9 +62,9 @@ function build_mc_opf(pm::_PMs.AbstractPowerModel) constraint_mc_thermal_limit_to(pm, i) end - for i in _PMs.ids(pm, :transformer) + for i in ids(pm, :transformer) constraint_mc_trans(pm, i) end - _PMs.objective_min_fuel_cost(pm) + _PM.objective_min_fuel_cost(pm) end diff --git a/src/prob/opf_bf.jl b/src/prob/opf_bf.jl index a2f547f62..75ea9f7f9 100644 --- a/src/prob/opf_bf.jl +++ b/src/prob/opf_bf.jl @@ -11,7 +11,7 @@ end "" -function build_mc_opf_bf(pm::_PMs.AbstractPowerModel) +function build_mc_opf_bf(pm::_PM.AbstractPowerModel) # Variables variable_mc_voltage(pm) variable_mc_branch_current(pm) @@ -22,15 +22,15 @@ function build_mc_opf_bf(pm::_PMs.AbstractPowerModel) # Constraints constraint_mc_model_current(pm) - for i in _PMs.ids(pm, :ref_buses) + for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_power_balance(pm, i) end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_flow_losses(pm, i) constraint_mc_model_voltage_magnitude_difference(pm, i) @@ -40,10 +40,10 @@ function build_mc_opf_bf(pm::_PMs.AbstractPowerModel) constraint_mc_thermal_limit_to(pm, i) end - for i in _PMs.ids(pm, :transformer) + for i in ids(pm, :transformer) constraint_mc_trans(pm, i) end # Objective - _PMs.objective_min_fuel_cost(pm) + _PM.objective_min_fuel_cost(pm) end diff --git a/src/prob/opf_bf_lm.jl b/src/prob/opf_bf_lm.jl index ea5588084..b560c017c 100644 --- a/src/prob/opf_bf_lm.jl +++ b/src/prob/opf_bf_lm.jl @@ -15,7 +15,7 @@ end "" -function build_mc_opf_bf_lm(pm::_PMs.AbstractPowerModel) +function build_mc_opf_bf_lm(pm::_PM.AbstractPowerModel) # Variables variable_mc_voltage(pm) variable_mc_branch_current(pm) @@ -27,11 +27,11 @@ function build_mc_opf_bf_lm(pm::_PMs.AbstractPowerModel) # Constraints constraint_mc_model_current(pm) - for i in _PMs.ids(pm, :ref_buses) + for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_flow_losses(pm, i) constraint_mc_model_voltage_magnitude_difference(pm, i) @@ -41,18 +41,18 @@ function build_mc_opf_bf_lm(pm::_PMs.AbstractPowerModel) constraint_mc_thermal_limit_to(pm, i) end - for i in _PMs.ids(pm, :load) + for i in ids(pm, :load) constraint_mc_load(pm, i) end - for i in _PMs.ids(pm, :gen) + for i in ids(pm, :gen) constraint_mc_generation(pm, i) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_power_balance(pm, i) end # Objective - _PMs.objective_min_fuel_cost(pm) + _PM.objective_min_fuel_cost(pm) end diff --git a/src/prob/opf_iv.jl b/src/prob/opf_iv.jl index 0f3b72fe5..310b8a6e6 100644 --- a/src/prob/opf_iv.jl +++ b/src/prob/opf_iv.jl @@ -11,7 +11,7 @@ end "" -function build_mc_opf_iv(pm::_PMs.AbstractPowerModel) +function build_mc_opf_iv(pm::_PM.AbstractPowerModel) # Variables variable_mc_voltage(pm) variable_mc_branch_current(pm) @@ -20,25 +20,25 @@ function build_mc_opf_iv(pm::_PMs.AbstractPowerModel) variable_mc_load(pm) # Constraints - for i in _PMs.ids(pm, :ref_buses) + for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end # gens should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :gen) + for id in ids(pm, :gen) constraint_mc_generation(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :load) + for id in ids(pm, :load) constraint_mc_load(pm, id) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_current_balance_load(pm, i) end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_current_from(pm, i) constraint_mc_current_to(pm, i) @@ -50,10 +50,10 @@ function build_mc_opf_iv(pm::_PMs.AbstractPowerModel) constraint_mc_thermal_limit_to(pm, i) end - for i in _PMs.ids(pm, :transformer) + for i in ids(pm, :transformer) constraint_mc_trans(pm, i) end # Objective - _PMs.objective_min_fuel_cost(pm) + _PM.objective_min_fuel_cost(pm) end diff --git a/src/prob/opf_oltc.jl b/src/prob/opf_oltc.jl index 83c165f6c..bdf33836b 100644 --- a/src/prob/opf_oltc.jl +++ b/src/prob/opf_oltc.jl @@ -1,6 +1,6 @@ "" function run_ac_mc_opf_oltc(file, solver; kwargs...) - return run_mc_opf_oltc(file, _PMs.ACPPowerModel, solver; kwargs...) + return run_mc_opf_oltc(file, _PM.ACPPowerModel, solver; kwargs...) end @@ -17,7 +17,7 @@ end "" -function build_mc_opf_oltc(pm::_PMs.AbstractPowerModel) +function build_mc_opf_oltc(pm::_PM.AbstractPowerModel) variable_mc_voltage(pm) variable_mc_branch_flow(pm) variable_mc_generation(pm) @@ -27,25 +27,25 @@ function build_mc_opf_oltc(pm::_PMs.AbstractPowerModel) constraint_mc_model_voltage(pm) - for i in _PMs.ids(pm, :ref_buses) + for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end # generators should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :gen) + for id in ids(pm, :gen) constraint_mc_generation(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :load) + for id in ids(pm, :load) constraint_mc_load(pm, id) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_power_balance_load(pm, i) end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_ohms_yt_from(pm, i) constraint_mc_ohms_yt_to(pm, i) @@ -55,9 +55,9 @@ function build_mc_opf_oltc(pm::_PMs.AbstractPowerModel) constraint_mc_thermal_limit_to(pm, i) end - for i in _PMs.ids(pm, :transformer) + for i in ids(pm, :transformer) constraint_mc_trans(pm, i, fix_taps=false) end - _PMs.objective_min_fuel_cost(pm) + _PM.objective_min_fuel_cost(pm) end diff --git a/src/prob/pf.jl b/src/prob/pf.jl index 0e99fda5e..d8ac4025b 100644 --- a/src/prob/pf.jl +++ b/src/prob/pf.jl @@ -1,12 +1,12 @@ "" function run_ac_mc_pf(data, solver; kwargs...) - return run_mc_pf(data, _PMs.ACPPowerModel, solver; kwargs...) + return run_mc_pf(data, _PM.ACPPowerModel, solver; kwargs...) end "" function run_dc_mc_pf(data, solver; kwargs...) - return run_mc_pf(data, _PMs.DCPPowerModel, solver; kwargs...) + return run_mc_pf(data, _PM.DCPPowerModel, solver; kwargs...) end @@ -23,7 +23,7 @@ end "" -function build_mc_pf(pm::_PMs.AbstractPowerModel) +function build_mc_pf(pm::_PM.AbstractPowerModel) variable_mc_voltage(pm; bounded=false) variable_mc_branch_flow(pm; bounded=false) variable_mc_transformer_flow(pm; bounded=false) @@ -32,7 +32,7 @@ function build_mc_pf(pm::_PMs.AbstractPowerModel) constraint_mc_model_voltage(pm) - for (i,bus) in _PMs.ref(pm, :ref_buses) + for (i,bus) in ref(pm, :ref_buses) @assert bus["bus_type"] == 3 constraint_mc_theta_ref(pm, i) @@ -40,36 +40,36 @@ function build_mc_pf(pm::_PMs.AbstractPowerModel) end # gens should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :gen) + for id in ids(pm, :gen) constraint_mc_generation(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :load) + for id in ids(pm, :load) constraint_mc_load(pm, id) end - for (i,bus) in _PMs.ref(pm, :bus) + for (i,bus) in ref(pm, :bus) constraint_mc_power_balance_load(pm, i) # PV Bus Constraints - if length(_PMs.ref(pm, :bus_gens, i)) > 0 && !(i in _PMs.ids(pm,:ref_buses)) + if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) # this assumes inactive generators are filtered out of bus_gens @assert bus["bus_type"] == 2 constraint_mc_voltage_magnitude_setpoint(pm, i) - for j in _PMs.ref(pm, :bus_gens, i) + for j in ref(pm, :bus_gens, i) constraint_mc_active_gen_setpoint(pm, j) end end end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_ohms_yt_from(pm, i) constraint_mc_ohms_yt_to(pm, i) end - for i in _PMs.ids(pm, :transformer) + for i in ids(pm, :transformer) constraint_mc_trans(pm, i) end end diff --git a/src/prob/pf_bf.jl b/src/prob/pf_bf.jl index e2a937d53..8eb01b20a 100644 --- a/src/prob/pf_bf.jl +++ b/src/prob/pf_bf.jl @@ -11,7 +11,7 @@ end "" -function build_mc_pf_bf(pm::_PMs.AbstractPowerModel) +function build_mc_pf_bf(pm::_PM.AbstractPowerModel) # Variables variable_mc_voltage(pm; bounded=false) variable_mc_branch_current(pm) @@ -21,29 +21,29 @@ function build_mc_pf_bf(pm::_PMs.AbstractPowerModel) # Constraints constraint_mc_model_current(pm) - for (i,bus) in _PMs.ref(pm, :ref_buses) + for (i,bus) in ref(pm, :ref_buses) constraint_mc_theta_ref(pm, i) @assert bus["bus_type"] == 3 constraint_mc_voltage_magnitude_setpoint(pm, i) end - for i in _PMs.ids(pm, :bus) + for i in ids(pm, :bus) constraint_mc_power_balance(pm, i) # PV Bus Constraints - if length(_PMs.ref(pm, :bus_gens, i)) > 0 && !(i in _PMs.ids(pm,:ref_buses)) + if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) # this assumes inactive generators are filtered out of bus_gens @assert bus["bus_type"] == 2 constraint_mc_voltage_magnitude_setpoint(pm, i) - for j in _PMs.ref(pm, :bus_gens, i) + for j in ref(pm, :bus_gens, i) constraint_mc_active_gen_setpoint(pm, j) end end end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_flow_losses(pm, i) constraint_mc_model_voltage_magnitude_difference(pm, i) constraint_mc_voltage_angle_difference(pm, i) @@ -53,5 +53,5 @@ function build_mc_pf_bf(pm::_PMs.AbstractPowerModel) end # Objective - _PMs.objective_min_fuel_cost(pm) + _PM.objective_min_fuel_cost(pm) end diff --git a/src/prob/pf_iv.jl b/src/prob/pf_iv.jl index 7f315c20a..545b5c2cb 100644 --- a/src/prob/pf_iv.jl +++ b/src/prob/pf_iv.jl @@ -11,7 +11,7 @@ end "" -function build_mc_pf_iv(pm::_PMs.AbstractPowerModel) +function build_mc_pf_iv(pm::_PM.AbstractPowerModel) # Variables variable_mc_voltage(pm, bounded = false) variable_mc_branch_current(pm, bounded = false) @@ -20,27 +20,27 @@ function build_mc_pf_iv(pm::_PMs.AbstractPowerModel) variable_mc_load(pm, bounded = false) # Constraints - for (i,bus) in _PMs.ref(pm, :ref_buses) + for (i,bus) in ref(pm, :ref_buses) @assert bus["bus_type"] == 3 constraint_mc_theta_ref(pm, i) constraint_mc_voltage_magnitude_setpoint(pm, i) end # gens should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :gen) + for id in ids(pm, :gen) constraint_mc_generation(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :load) + for id in ids(pm, :load) constraint_mc_load(pm, id) end - for (i,bus) in _PMs.ref(pm, :bus) + for (i,bus) in ref(pm, :bus) constraint_mc_current_balance_load(pm, i) # PV Bus Constraints - if length(_PMs.ref(pm, :bus_gens, i)) > 0 && !(i in _PMs.ids(pm,:ref_buses)) + if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) # this assumes inactive generators are filtered out of bus_gens @assert bus["bus_type"] == 2 constraint_mc_voltage_magnitude_setpoint(pm, i) @@ -50,14 +50,14 @@ function build_mc_pf_iv(pm::_PMs.AbstractPowerModel) end end - for i in _PMs.ids(pm, :branch) + for i in ids(pm, :branch) constraint_mc_current_from(pm, i) constraint_mc_current_to(pm, i) constraint_mc_voltage_drop(pm, i) end - for i in _PMs.ids(pm, :transformer) + for i in ids(pm, :transformer) constraint_mc_trans(pm, i) end end diff --git a/src/prob/test.jl b/src/prob/test.jl index c5b461f15..ac39b3c79 100644 --- a/src/prob/test.jl +++ b/src/prob/test.jl @@ -17,30 +17,30 @@ # # # "multi-network opf with storage" -# function _build_mn_mc_strg_opf(pm::_PMs.AbstractPowerModel) -# for (n, network) in _PMs.nws(pm) +# function _build_mn_mc_strg_opf(pm::_PM.AbstractPowerModel) +# for (n, network) in nws(pm) # variable_mc_voltage(pm; nw=n) # constraint_mc_model_voltage(pm; nw=n) # variable_mc_branch_flow(pm; nw=n) # variable_mc_generation(pm; nw=n) # variable_mc_storage(pm; nw=n) # -# for i in _PMs.ids(pm, :ref_buses; nw=n) +# for i in ids(pm, :ref_buses; nw=n) # constraint_mc_theta_ref(pm, i; nw=n) # end # -# for i in _PMs.ids(pm, :bus; nw=n) +# for i in ids(pm, :bus; nw=n) # constraint_mc_power_balance(pm, i; nw=n) # end # -# for i in _PMs.ids(pm, :storage; nw=n) -# _PMs.constraint_storage_state(pm, i; nw=n) -# _PMs.constraint_storage_complementarity_nl(pm, i; nw=n) +# for i in ids(pm, :storage; nw=n) +# _PM.constraint_storage_state(pm, i; nw=n) +# _PM.constraint_storage_complementarity_nl(pm, i; nw=n) # constraint_mc_storage_loss(pm, i; nw=n) # constraint_mc_storage_thermal_limit(pm, i; nw=n) # end # -# for i in _PMs.ids(pm, :branch; nw=n) +# for i in ids(pm, :branch; nw=n) # constraint_mc_ohms_yt_from(pm, i; nw=n) # constraint_mc_ohms_yt_to(pm, i; nw=n) # @@ -50,26 +50,26 @@ # constraint_mc_thermal_limit_to(pm, i; nw=n) # end # -# for i in _PMs.ids(pm, :transformer; nw=n) +# for i in ids(pm, :transformer; nw=n) # constraint_mc_trans(pm, i; nw=n) # end # end # -# network_ids = sort(collect(_PMs.nw_ids(pm))) +# network_ids = sort(collect(nw_ids(pm))) # # n_1 = network_ids[1] -# for i in _PMs.ids(pm, :storage; nw=n_1) -# _PMs.constraint_storage_state(pm, i; nw=n_1) +# for i in ids(pm, :storage; nw=n_1) +# _PM.constraint_storage_state(pm, i; nw=n_1) # end # # for n_2 in network_ids[2:end] -# for i in _PMs.ids(pm, :storage; nw=n_2) -# _PMs.constraint_storage_state(pm, i, n_1, n_2) +# for i in ids(pm, :storage; nw=n_2) +# _PM.constraint_storage_state(pm, i, n_1, n_2) # end # n_1 = n_2 # end # -# _PMs.objective_min_fuel_cost(pm) +# _PM.objective_min_fuel_cost(pm) # end @@ -85,8 +85,8 @@ function _run_mc_ucopf(file, model_type::Type, solver; kwargs...) end "" -function _build_mc_ucopf(pm::_PMs.AbstractPowerModel) - for (n, network) in _PMs.nws(pm) +function _build_mc_ucopf(pm::_PM.AbstractPowerModel) + for (n, network) in nws(pm) variable_mc_voltage(pm, nw=n) variable_mc_branch_flow(pm, nw=n) variable_mc_transformer_flow(pm, nw=n) @@ -100,23 +100,23 @@ function _build_mc_ucopf(pm::_PMs.AbstractPowerModel) variable_mc_on_off_storage(pm, nw=n) - _PMs.variable_storage_energy(pm, nw=n) - _PMs.variable_storage_charge(pm, nw=n) - _PMs.variable_storage_discharge(pm, nw=n) - _PMs.variable_storage_indicator(pm, nw=n) - _PMs.variable_storage_complementary_indicator(pm, nw=n) + _PM.variable_storage_energy(pm, nw=n) + _PM.variable_storage_charge(pm, nw=n) + _PM.variable_storage_discharge(pm, nw=n) + _PM.variable_storage_indicator(pm, nw=n) + _PM.variable_storage_complementary_indicator(pm, nw=n) - for i in _PMs.ids(pm, :ref_buses, nw=n) + for i in ids(pm, :ref_buses, nw=n) constraint_mc_theta_ref(pm, i, nw=n) end - for i in _PMs.ids(pm, :bus, nw=n) + for i in ids(pm, :bus, nw=n) constraint_mc_power_balance(pm, i, nw=n) end - for i in _PMs.ids(pm, :branch, nw=n) + for i in ids(pm, :branch, nw=n) constraint_mc_ohms_yt_from(pm, i, nw=n) constraint_mc_ohms_yt_to(pm, i, nw=n) @@ -127,7 +127,7 @@ function _build_mc_ucopf(pm::_PMs.AbstractPowerModel) end - for i in _PMs.ids(pm, :transformer, nw=n) + for i in ids(pm, :transformer, nw=n) constraint_mc_trans(pm, i, nw=n) end @@ -135,9 +135,9 @@ function _build_mc_ucopf(pm::_PMs.AbstractPowerModel) # constraint_mc_dcline(pm, i, nw=n) # end - for i in _PMs.ids(pm, :storage; nw=n) - # _PMs.constraint_storage_state(pm, i; nw=n) - _PMs.constraint_storage_complementarity_mi(pm, i; nw=n) + for i in ids(pm, :storage; nw=n) + # _PM.constraint_storage_state(pm, i; nw=n) + _PM.constraint_storage_complementarity_mi(pm, i; nw=n) constraint_mc_storage_loss(pm, i; nw=n) constraint_mc_storage_thermal_limit(pm, i; nw=n) @@ -146,20 +146,20 @@ function _build_mc_ucopf(pm::_PMs.AbstractPowerModel) end end - network_ids = sort(collect(_PMs.nw_ids(pm))) + network_ids = sort(collect(nw_ids(pm))) n_1 = network_ids[1] - for i in _PMs.ids(pm, :storage, nw=n_1) - _PMs.constraint_storage_state(pm, i, nw=n_1) + for i in ids(pm, :storage, nw=n_1) + _PM.constraint_storage_state(pm, i, nw=n_1) end for n_2 in network_ids[2:end] - for i in _PMs.ids(pm, :storage, nw=n_2) - _PMs.constraint_storage_state(pm, i, n_1, n_2) + for i in ids(pm, :storage, nw=n_2) + _PM.constraint_storage_state(pm, i, n_1, n_2) end n_1 = n_2 end - _PMs.objective_min_fuel_cost(pm) + _PM.objective_min_fuel_cost(pm) end @@ -169,8 +169,8 @@ function _run_mn_mc_opf(file, model_type::Type, optimizer; kwargs...) end "" -function _build_mn_mc_opf(pm::_PMs.AbstractPowerModel) - for (n, network) in _PMs.nws(pm) +function _build_mn_mc_opf(pm::_PM.AbstractPowerModel) + for (n, network) in nws(pm) variable_mc_voltage(pm, nw=n) variable_mc_branch_flow(pm, nw=n) variable_mc_transformer_flow(pm, nw=n) @@ -178,16 +178,16 @@ function _build_mn_mc_opf(pm::_PMs.AbstractPowerModel) constraint_mc_model_voltage(pm, nw=n) - for i in _PMs.ids(pm, :ref_buses, nw=n) + for i in ids(pm, :ref_buses, nw=n) constraint_mc_theta_ref(pm, i, nw=n) end - for i in _PMs.ids(pm, :bus, nw=n) + for i in ids(pm, :bus, nw=n) constraint_mc_power_balance(pm, i, nw=n) end - for i in _PMs.ids(pm, :branch, nw=n) + for i in ids(pm, :branch, nw=n) constraint_mc_ohms_yt_from(pm, i, nw=n) constraint_mc_ohms_yt_to(pm, i, nw=n) @@ -198,7 +198,7 @@ function _build_mn_mc_opf(pm::_PMs.AbstractPowerModel) end - for i in _PMs.ids(pm, :transformer, nw=n) + for i in ids(pm, :transformer, nw=n) constraint_mc_trans(pm, i, nw=n) end @@ -206,7 +206,7 @@ function _build_mn_mc_opf(pm::_PMs.AbstractPowerModel) # constraint_mc_dcline(pm, i, nw=n) # end end - _PMs.objective_min_fuel_cost(pm) + _PM.objective_min_fuel_cost(pm) end @@ -216,9 +216,9 @@ function _run_mn_mc_opf_strg(file, model_type::Type, optimizer; kwargs...) end "warning: this model is not realistic or physically reasonable, it is only for test coverage" -function _build_mn_mc_opf_strg(pm::_PMs.AbstractPowerModel) +function _build_mn_mc_opf_strg(pm::_PM.AbstractPowerModel) - for (n, network) in _PMs.nws(pm) + for (n, network) in nws(pm) variable_mc_voltage(pm, nw=n) variable_mc_branch_flow(pm, nw=n) variable_mc_transformer_flow(pm, nw=n) @@ -229,26 +229,26 @@ function _build_mn_mc_opf_strg(pm::_PMs.AbstractPowerModel) constraint_mc_model_voltage(pm, nw=n) - for i in _PMs.ids(pm, :ref_buses, nw=n) + for i in ids(pm, :ref_buses, nw=n) constraint_mc_theta_ref(pm, i, nw=n) end # generators should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :gen) + for id in ids(pm, :gen) constraint_mc_generation(pm, id, nw=n) end # loads should be constrained before KCL, or Pd/Qd undefined - for id in _PMs.ids(pm, :load) + for id in ids(pm, :load) constraint_mc_load(pm, id, nw=n) end - for i in _PMs.ids(pm, :bus, nw=n) + for i in ids(pm, :bus, nw=n) constraint_mc_power_balance_load(pm, i, nw=n) end - for i in _PMs.ids(pm, :branch, nw=n) + for i in ids(pm, :branch, nw=n) constraint_mc_ohms_yt_from(pm, i, nw=n) constraint_mc_ohms_yt_to(pm, i, nw=n) @@ -259,12 +259,12 @@ function _build_mn_mc_opf_strg(pm::_PMs.AbstractPowerModel) end - for i in _PMs.ids(pm, :transformer, nw=n) + for i in ids(pm, :transformer, nw=n) constraint_mc_trans(pm, i, nw=n) end - for i in _PMs.ids(pm, :storage, nw=n) - _PMs.constraint_storage_complementarity_nl(pm, i; nw=n) + for i in ids(pm, :storage, nw=n) + _PM.constraint_storage_complementarity_nl(pm, i; nw=n) constraint_mc_storage_loss(pm, i; nw=n) constraint_mc_storage_thermal_limit(pm, i, nw=n) end @@ -274,18 +274,18 @@ function _build_mn_mc_opf_strg(pm::_PMs.AbstractPowerModel) # end end - network_ids = sort(collect(_PMs.nw_ids(pm))) + network_ids = sort(collect(nw_ids(pm))) n_1 = network_ids[1] - for i in _PMs.ids(pm, :storage, nw=n_1) - _PMs.constraint_storage_state(pm, i, nw=n_1) + for i in ids(pm, :storage, nw=n_1) + _PM.constraint_storage_state(pm, i, nw=n_1) end for n_2 in network_ids[2:end] - for i in _PMs.ids(pm, :storage, nw=n_2) - _PMs.constraint_storage_state(pm, i, n_1, n_2) + for i in ids(pm, :storage, nw=n_2) + _PM.constraint_storage_state(pm, i, n_1, n_2) end n_1 = n_2 end - _PMs.objective_min_fuel_cost(pm) + _PM.objective_min_fuel_cost(pm) end From df566810f6492917e90207899ff92f85138746a3 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 31 Mar 2020 16:09:04 -0600 Subject: [PATCH 115/224] REF: opendss parsing simplification takes buses out of _create_{component} function calls adds more explicit function typing REF: removes references to _map_math2eng will only support mapping solution up to eng model removes :extras from map, unneeded now --- src/data_model/components.jl | 8 +- src/data_model/eng2math.jl | 87 +++------------- src/data_model/math2eng.jl | 197 +++++++++++------------------------ src/data_model/units.jl | 8 +- src/data_model/utils.jl | 7 +- src/io/common.jl | 10 +- src/io/dss_parse.jl | 50 ++++----- src/io/dss_structs.jl | 87 ++++++++-------- src/io/json.jl | 47 +++++---- src/io/opendss.jl | 42 ++++---- src/io/utils.jl | 2 +- test/opendss.jl | 6 +- 12 files changed, 211 insertions(+), 340 deletions(-) diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 535d3e49e..5d8f884a2 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -24,8 +24,8 @@ function add_object!(data_eng::Dict{String,<:Any}, obj_type::String, obj_id::Any end if obj_type == "voltage_source" - if !haskey(data_eng["settings"], "set_vbase_bus") - data_eng["settings"]["set_vbase_bus"] = object["bus"] + if !haskey(data_eng["settings"], "base_bus") + data_eng["settings"]["base_bus"] = object["bus"] end end @@ -63,8 +63,8 @@ function Model(model_type::String="engineering"; kwargs...)::Dict{String,Any} "per_unit" => false, "settings" => Dict{String,Any}( "v_var_scalar" => get(kwargs, :v_var_scalar, 1e3), - "set_vbase_val" => get(kwargs, :basekv, 1.0), - "set_sbase_val" => get(kwargs, :baseMVA, 1.0), + "vbase" => get(kwargs, :basekv, 1.0), + "sbase" => get(kwargs, :baseMVA, 1.0), "basefreq" => get(kwargs, :basefreq, 60.0), ) ) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 2516d0c3c..0d2453fd0 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -19,31 +19,13 @@ const _1to1_maps = Dict{String,Vector{String}}( "voltage_source" => ["source_id"], ) -const _extra_eng_data = Dict{String,Vector{String}}( - "root" => ["files", "dss_options", "settings"], - "bus" => ["grounded", "neutral", "awaiting_ground", "xg", "phases", "rg", "terminals"], - "load" => [], - "shunt_capacitor" => [], - "series_capacitor" => [], - "shunt" => [], - "shunt_reactor" => [], - "generator" => ["control_model"], - "solar" => [], - "storage" => [], - "line" => ["f_connections", "t_connections", "linecode"], - "switch" => [], - "line_reactor" => [], - "transformer" => [], - "voltage_source" => [], -) - const _node_elements = ["load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource"] const _edge_elements = ["line", "switch", "transformer", "line_reactor", "series_capacitor"] "" -function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, use_dss_bounds::Bool=true) +function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, adjust_bounds::Bool=true) @assert get(data_eng, "data_model", "mathematical") == "engineering" data_math = Dict{String,Any}( @@ -54,14 +36,12 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, ) data_math["conductors"] = kron_reduced ? 3 : 4 - data_math["basekv"] = data_eng["settings"]["set_vbase_val"] - data_math["baseMVA"] = data_eng["settings"]["set_sbase_val"]*data_eng["settings"]["v_var_scalar"]/1E6 + data_math["basekv"] = data_eng["settings"]["vbase"] + data_math["baseMVA"] = data_eng["settings"]["sbase"]*data_eng["settings"]["v_var_scalar"]/1E6 data_math["map"] = Dict{Int,Dict{Symbol,Any}}( 1 => Dict{Symbol,Any}( - :component_type => "root", :unmap_function => :_map_math2eng_root!, - :extra => Dict{String,Any}((k,v) for (k,v) in data_eng if k in _extra_eng_data["root"]) ) ) @@ -87,7 +67,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) - if !use_dss_bounds + if !adjust_bounds # _find_new_bounds(data_math) # TODO end @@ -161,7 +141,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, math_obj["vmin"] = fill(0.0, length(terminals)) math_obj["vmax"] = fill(Inf, length(terminals)) - math_obj["base_kv"] = data_eng["settings"]["set_vbase_val"] + math_obj["base_kv"] = data_eng["settings"]["vbase"] if kron_reduced filter = terminals.!=kr_neutral @@ -184,8 +164,6 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, :from => name, :to => "bus.$(math_obj["index"])", :unmap_function => :_map_math2eng_bus!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["bus"]) ) end end @@ -228,8 +206,6 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any :from => name, :to => "load.$(math_obj["index"])", :unmap_function => :_map_math2eng_load!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["load"]) ) end end @@ -277,8 +253,6 @@ function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng: :from => name, :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_shunt_capacitor!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["shunt_capacitor"]) ) end end @@ -313,8 +287,6 @@ function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An :from => name, :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_capacitor!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["shunt"]) ) end end @@ -351,8 +323,6 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D :from => name, :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_shunt_reactor!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["shunt_reactor"]) ) end end @@ -407,8 +377,6 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ :from => name, :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_generator!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["generator"]) ) end end @@ -455,8 +423,6 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An :from => "solar.$name", :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_solar!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["solar"]) ) end end @@ -500,8 +466,6 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: :from => name, :to => "storage.$(math_obj["index"])", :unmap_function => :_map_math2eng_storage!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["storage"]) ) end end @@ -532,11 +496,11 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] - math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) - math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) + math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) + math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) - math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) - math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) + math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) + math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) math_obj["angmin"] = fill(-60.0, nphases) math_obj["angmax"] = fill( 60.0, nphases) @@ -572,8 +536,6 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any :from => name, :to => "branch.$(math_obj["index"])", :unmap_function => :_map_math2eng_line!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["line"]) ) end end @@ -595,11 +557,11 @@ function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Di math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] - math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) - math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) + math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) + math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) - math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) - math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) + math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) + math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) math_obj["angmin"] = fill(-60.0, nphases) math_obj["angmax"] = fill( 60.0, nphases) @@ -635,8 +597,6 @@ function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Di :from => name, :to => "branch.$(math_obj["index"])", :unmap_function => :_map_math2eng_line_reactor!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["line_reactor"]) ) end end @@ -662,9 +622,6 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A :from => name, :to => "switch.$(math_obj["index"])", :unmap_function => :_map_math2eng_switch!, - :kron_reduced => kron_reduced, - :lossless => lossless, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["switch"]) ) else # build virtual bus @@ -707,10 +664,10 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, - "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9), - "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["g_to"] * eng_obj["length"] / 1e9), - "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9), - "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["basefreq"] * eng_obj["b_to"] * eng_obj["length"] / 1e9), + "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9), + "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9), + "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9), + "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9), "angmin" => fill(-60.0, nphases), "angmax" => fill( 60.0, nphases), "transformer" => false, @@ -755,9 +712,6 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A # :to_id => ["switch.$(switch_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], # TODO enable real switches :to => ["branch.$(branch_obj["index"])"], :unmap_function => :_map_math2eng_switch!, - :kron_reduced => kron_reduced, - :lossless => lossless, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["switch"]) ) end end @@ -773,8 +727,6 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic :from => name, :to => Vector{String}([]), :unmap_function => :_map_math2eng_transformer!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["transformer"]) ) to_map = data_math["map"][map_idx][:to] @@ -918,9 +870,6 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: :from => name, :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_voltage_source!, - :kron_reduced => kron_reduced, - :lossless => lossless, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) ) else bus_obj = Dict{String,Any}( @@ -982,8 +931,6 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: :from => name, :to => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], :unmap_function => :_map_math2eng_voltage_source!, - :kron_reduced => kron_reduced, - :extra => Dict{String,Any}((k,v) for (k,v) in eng_obj if k in _extra_eng_data["voltage_source"]) ) end end diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index 78b9062f0..9a2099f75 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -1,23 +1,5 @@ "" -function _map_math2eng!(data_math) - @assert get(data_math, "data_model", "mathematical") == "mathematical" "Cannot map data to engineering model: provided data is not a mathematical model" - @assert haskey(data_math, "map") "Cannot map data to engineering model: no mapping from mathematical to engineering data model is provided" - - data_eng = Dict{String,Any}() - - unmap_keys = sort(keys(data_math["map"]), rev=true) - reverse_bus_lookup = Dict{Int,Any}((bus_math, bus_eng) for (bus_eng, bus_math) in data_math["bus_lookup"]) - for key in unmap_keys - getfield(PowerModelsDistribution, map[:unmap_function])(data_eng, data_math, data_math["map"][key], reverse_bus_lookup) - end - - return data_eng -end - - -# MAP SOLUTION UP -"" -function solution_math2eng(solution_math::Dict, data_math::Dict; make_si::Bool=true, make_deg::Bool=true) +function solution_math2eng(solution_math::Dict{String,<:Any}, data_math::Dict{String,<:Any}; make_si::Bool=true, make_deg::Bool=true)::Dict{String,Any} solution_eng = Dict{String, Any}() if make_deg || make_si @@ -28,195 +10,138 @@ function solution_math2eng(solution_math::Dict, data_math::Dict; make_si::Bool=t for map_key in map_keys map = data_math["map"][map_key] reverse_bus_lookup = Dict{Int,Any}((bus_math, bus_eng) for (bus_eng, bus_math) in data_math["bus_lookup"]) - getfield(PowerModelsDistribution, map[:unmap_function])(solution_eng, solution_math, map, reverse_bus_lookup; map_solution=true) + getfield(PowerModelsDistribution, map[:unmap_function])(solution_eng, solution_math, map, reverse_bus_lookup) + end + + for (k,v) in solution_eng + if isempty(v) + delete!(solution_eng, k) + end end return solution_eng end -function _map_math2eng_voltage_source!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) +"" +function _map_math2eng_voltage_source!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) if !haskey(data_eng, "voltage_source") data_eng["voltage_source"] = Dict{Any,Any}() end - eng_obj = _init_unmap_eng_obj!(data_eng, "voltage_source", map; map_solution=map_solution) + eng_obj = _init_unmap_eng_obj!(data_eng, "voltage_source", map) - if map_solution - for to_id in map[:to] - math_obj = _get_math_obj(data_math, to_id) - if startswith(to_id, "gen") - for property in ["pg", "qg", "pg_bus", "qg_bus"] - if haskey(math_obj, property) - eng_obj[property] = math_obj[property] - end + for to_id in map[:to] + math_obj = _get_math_obj(data_math, to_id) + if startswith(to_id, "gen") + for property in ["pg", "qg", "pg_bus", "qg_bus"] + if haskey(math_obj, property) + eng_obj[property] = math_obj[property] end end end - else - for to_id in map[:to] - math_obj = _get_math_obj(data_math, to_id) - if startswith(to_id, "bus") - eng_obj["vm"] = math_obj["vm"] - eng_obj["va"] = math_obj["va"] - - elseif startswith(to_id, "gen") - eng_obj["source_id"] = strip(math_obj["source_id"], "_virtual_gen.") - - elseif startswith(to_id, "branch") - eng_obj["bus"] = bus_lookup[math_obj["t_bus"]] - eng_obj["rs"] = math_obj["br_r"] - eng_obj["xs"] = math_obj["br_x"] - else - Memento.warn(_LOGGER, "transforming from $to_id to $(map[:from]) is not supported") - end - end end - data_eng["voltage_source"][map[:from]] = eng_obj + if !isempty(eng_obj) + data_eng["voltage_source"][map[:from]] = eng_obj + end end -function _map_math2eng_bus!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) - eng_obj = _init_unmap_eng_obj!(data_eng, "bus", map; map_solution=map_solution) +"" +function _map_math2eng_bus!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "bus", map) math_obj = _get_math_obj(data_math, map[:to]) - if map_solution - eng_obj = math_obj - else - eng_obj["status"] = math_obj["bus_type"] == 4 ? 0 : 1 + merge!(eng_obj, math_obj) - for key in ["vm", "va", "vmin", "vmax"] - if haskey(math_obj, key) - eng_obj[key] = math_obj[key] - end - end + if !isempty(eng_obj) + data_eng["bus"][map[:from]] = eng_obj end - data_eng["bus"][map[:from]] = eng_obj end -function _map_math2eng_load!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) - eng_obj = _init_unmap_eng_obj!(data_eng, "load", map; map_solution=map_solution) +"" +function _map_math2eng_load!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "load", map) math_obj = _get_math_obj(data_math, map[:to]) - if map_solution - eng_obj = math_obj - else - eng_obj["vnom"] = math_obj["vnom_kv"] - eng_obj["bus"] = bus_lookup[math_obj["load_bus"]] - - eng_obj["pd"] = math_obj["pd"] - eng_obj["qd"] = math_obj["qd"] + merge!(eng_obj, math_obj) - eng_obj["status"] = math_obj["status"] - - eng_obj["configuration"] = math_obj["configuration"] + if !isempty(eng_obj) + data_eng["load"][map[:from]] = eng_obj end - - data_eng["load"][map[:from]] = eng_obj end -function _map_math2eng_shunt_capacitor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) - eng_obj = _init_unmap_eng_obj!(data_eng, "shunt_capacitor", map; map_solution=map_solution) +function _map_math2eng_shunt_capacitor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "shunt_capacitor", map) math_obj = _get_math_obj(data_math, map[:to]) - if map_solution - eng_obj = math_obj - else - eng_obj["bus"] = bus_lookup["shunt_bus"] - eng_obj["status"] = math_obj["status"] - - eng_obj["configuration"] = eng_obj["configuration"] - - B = math_obj["bs"] - - if math_obj["configuration"] == "wye" - b_cap_pu = diag(B) - else - # TODO - end + merge!(eng_obj, math_obj) - # TODO how to pull kv, kvar back out, this is a destructive transformation + if !isempty(eng_obj) + data_eng["shunt_capacitor"][map[:from]] = eng_obj end - - data_eng["shunt_capacitor"][map[:from]] = eng_obj end -function _map_math2eng_shunt!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) +function _map_math2eng_shunt!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) end -function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) +function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "generator", map) math_obj = _get_math_obj(data_math, map[:to]) - eng_obj["bus"] = bus_lookup[math_obj["gen_bus"]] - eng_obj["configuration"] = math_obj["configuration"] - eng_obj["status"] = math_obj["gen_status"] - - eng_obj["kw"] = math_obj["pg"] - eng_obj["kvar"] = math_obj["qg"] - eng_obj["kv"] = math_obj["vg"] + @warn "gen" math_obj eng_obj - eng_obj["kvar_min"] = math_obj["qmin"] - eng_obj["kvar_max"] = math_obj["qmax"] + merge!(eng_obj, math_obj) - eng_obj["kw_min"] = math_obj["pmin"] - eng_obj["kw_max"] = math_obj["pmax"] - - gen_bus = data_math["bus"]["$(math_obj["gen_bus"])"] - if gen_bus["bus_type"] == 2 - eng_obj["control_mode"] = 3 - else - eng_obj["control_mode"] = 1 + if !isempty(eng_obj) + data_eng["generator"][map[:from]] = eng_obj end - - data_eng["generator"][map[:from]] = eng_obj end -function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) +function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) end -function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) +function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) end -function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) +function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) end -function _map_math2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) +function _map_math2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) end -function _map_math2eng_switch!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) +function _map_math2eng_switch!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) end -function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) - eng_obj = _init_unmap_eng_obj!(data_eng, "transformer", map; map_solution=map_solution) +function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "transformer", map) - if map_solution - trans_2wa_ids = [index for (comp_type, index) in split.(map[:to], ".", limit=2) if comp_type=="transformer"] + trans_2wa_ids = [index for (comp_type, index) in split.(map[:to], ".", limit=2) if comp_type=="transformer"] - prop_map = Dict("pf"=>"p", "qf"=>"q") - for (prop_from, prop_to) in prop_map - eng_obj[prop_to] = [get(data_math["transformer"][id], prop_from, NaN) for id in trans_2wa_ids] - end - else + prop_map = Dict("pf"=>"p", "qf"=>"q") + for (prop_from, prop_to) in prop_map + eng_obj[prop_to] = [get(data_math["transformer"][id], prop_from, NaN) for id in trans_2wa_ids] end - data_eng["transformer"][map[:from]] = eng_obj + if !isempty(eng_obj) + data_eng["transformer"][map[:from]] = eng_obj + end end -function _map_math2eng_root!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}; map_solution::Bool=false) - if map_solution - else - end +function _map_math2eng_root!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + data_eng["per_unit"] = data_math["per_unit"] + + data_eng["settings"] = Dict{String,Any}("sbase" => data_math["baseMVA"]) end \ No newline at end of file diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 12b25952a..28bf4bf53 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -12,9 +12,9 @@ function make_per_unit!(data::Dict{String,<:Any}) if data_model_type == "mathematical" if !get(data, "per_unit", false) - bus_indexed_id = string(data["bus_lookup"][data["settings"]["set_vbase_bus"]]) - vbases = Dict(bus_indexed_id=>data["settings"]["set_vbase_val"]) - sbase = data["settings"]["set_sbase_val"] + bus_indexed_id = string(data["bus_lookup"][data["settings"]["base_bus"]]) + vbases = Dict(bus_indexed_id=>data["settings"]["vbase"]) + sbase = data["settings"]["sbase"] _make_math_per_unit!(data, vbases=vbases, sbase=sbase, v_var_scalar=data["settings"]["v_var_scalar"]) else @@ -389,5 +389,7 @@ function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true end end + solution_si["per_unit"] = false + return solution_si end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 4e2886419..1a6003023 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -442,15 +442,12 @@ end "initialization actions for unmapping" -function _init_unmap_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, map::Dict{Symbol,Any}; map_solution::Bool=false)::Dict{String,Any} +function _init_unmap_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, map::Dict{Symbol,Any})::Dict{String,Any} if !haskey(data_eng, eng_obj_type) data_eng[eng_obj_type] = Dict{Any,Any}() end eng_obj = Dict{String,Any}() - if !map_solution - merge!(eng_obj, map[:extras]) - end return eng_obj end @@ -459,7 +456,7 @@ end "returns component from the mathematical data model" function _get_math_obj(data_math::Dict{String,<:Any}, to_id::String)::Dict{String,Any} math_type, math_id = split(to_id, '.') - return data_math[math_type][math_id] + return haskey(data_math, math_type) && haskey(data_math[math_type], math_id) ? data_math[math_type][math_id] : Dict{String,Any}() end diff --git a/src/io/common.jl b/src/io/common.jl index d280548a6..c4002c9ee 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -3,12 +3,12 @@ Parses the IOStream of a file into a Three-Phase PowerModels data structure. """ -function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="mathematical", import_all::Bool=false, bank_transformers::Bool=true, lossless::Bool=false, use_dss_bounds::Bool=true)::Dict{String,Any} +function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="mathematical", import_all::Bool=false, bank_transformers::Bool=true, lossless::Bool=false, adjust_bounds::Bool=false, transformations::Vector{Function}=Vector{Function}([]))::Dict{String,Any} if filetype == "dss" data_eng = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) if data_model == "mathematical" - return transform_data_model(data_eng; make_pu=true, lossless=lossless, use_dss_bounds=use_dss_bounds) + return transform_data_model(data_eng; make_pu=true, lossless=lossless, adjust_bounds=adjust_bounds) else return data_eng end @@ -16,7 +16,7 @@ function parse_file(io::IO, filetype::AbstractString="json"; data_model::String= pmd_data = parse_json(io; validate=false) if get(pmd_data, "data_model", "mathematical") != data_model - return transform_data_model(pmd_data; lossless=lossless, use_dss_bounds=use_dss_bounds) + return transform_data_model(pmd_data; lossless=lossless, adjust_bounds=adjust_bounds) else return pmd_data end @@ -37,11 +37,11 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=false, lossless::Bool=false, use_dss_bounds::Bool=true)::Dict{String,Any} +function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=false, lossless::Bool=false, adjust_bounds::Bool=false)::Dict{String,Any} current_data_model = get(data, "data_model", "mathematical") if current_data_model == "engineering" - data_math = _map_eng2math(data; kron_reduced=kron_reduced, lossless=lossless, use_dss_bounds=use_dss_bounds) + data_math = _map_eng2math(data; kron_reduced=kron_reduced, lossless=lossless, adjust_bounds=adjust_bounds) correct_network_data!(data_math; make_pu=make_pu) diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 6d1e5649d..9841e33d9 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -266,7 +266,7 @@ const _dss_object_properties = Dict{String,Vector{String}}( "parses single column load profile files" -function _parse_csv_file(path::AbstractString, type::AbstractString; header::Bool=false, column::Int=1, interval::Bool=false) +function _parse_csv_file(path::AbstractString, type::AbstractString; header::Bool=false, column::Int=1, interval::Bool=false)::Union{Vector{String}, Tuple{Vector{String}, Vector{String}, Vector{String}}, Tuple{Vector{String}, Vector{String}}} open(path, "r") do f lines = readlines(f) if header @@ -274,36 +274,36 @@ function _parse_csv_file(path::AbstractString, type::AbstractString; header::Boo end if type == "mult" - return [split(line, ",")[column] for line in lines] + return Vector{String}([split(line, ",")[column] for line in lines]) elseif type == "csvfile" if interval - hour, mult = [], [] + hour, mult = Vector{String}([]), Vector{String}([]) for line in lines d = split(line, ",") - push!(hour, d[1]) - push!(mult, d[2]) + push!(hour, string(d[1])) + push!(mult, string(d[2])) end return hour, mult else - return [split(line, ",")[1] for line in lines] + return Vector{String}([split(line, ",")[1] for line in lines]) end elseif type == "pqcsvfile" if interval - hour, pmult, qmult = [], [], [] + hour, pmult, qmult = Vector{String}([]), Vector{String}([]), Vector{String}([]) for line in lines d = split(line, ",") - push!(hour, d[1]) - push!(pmult, d[2]) - push!(qmult, d[3]) + push!(hour, string(d[1])) + push!(pmult, string(d[2])) + push!(qmult, string(d[3])) end return hour, pmult, qmult else - pmult, qmult = [], [] + pmult, qmult = Vector{String}([]), Vector{String}([]) for line in lines d = split(line, ",") - push!(pmult, d[1]) - push!(qmult, d[2]) + push!(pmult, string(d[1])) + push!(qmult, string(d[2])) end return pmult, qmult @@ -314,7 +314,7 @@ end "parses sng and dbl precision loadshape binary files" -function _parse_binary_file(path::AbstractString, precision::Type; npts::Union{Int,Nothing}=nothing, interval::Bool=false) +function _parse_binary_file(path::AbstractString, precision::Type; npts::Union{Int,Nothing}=nothing, interval::Bool=false)::Union{Vector{precision}, Tuple{Vector{precision}, Vector{precision}}} open(path, "r") do f if npts === nothing data = precision[] @@ -357,7 +357,7 @@ end "parses pmult and qmult entries on loadshapes" -function _parse_mult_parameter(mult_string::AbstractString; path::AbstractString="", npts::Union{Int,Nothing}=nothing) +function _parse_mult_parameter(mult_string::AbstractString; path::AbstractString="", npts::Union{Int,Nothing}=nothing)::String if !occursin("=", mult_string) return mult_string else @@ -444,9 +444,9 @@ end "strips lines that are either commented (block or single) or empty" -function _strip_lines(lines::Array)::Array +function _strip_lines(lines::Vector{<:AbstractString})::Vector{String} blockComment = false - stripped_lines = [] + stripped_lines = Vector{String}([]) for line in lines if startswith(strip(line), "/*") || endswith(strip(line), "*/") blockComment = !blockComment @@ -463,7 +463,7 @@ Parses a Bus Coordinate `file`, in either "dat" or "csv" formats, where in "dat", columns are separated by spaces, and in "csv" by commas. File expected to contain "bus,x,y" on each line. """ -function _parse_buscoords_file(file::AbstractString)::Dict{String,Any} +function _parse_buscoords_file(file::String)::Dict{String,Any} file_str = read(open(file), String) regex = r",\s*" if endswith(lowercase(file), "csv") || endswith(lowercase(file), "dss") @@ -487,8 +487,8 @@ Parses a string of `properties` of a component type, character by character into an array with each element containing (if present) the property name, "=", and the property value. """ -function _parse_properties(properties::AbstractString)::Array{<:Any, 1} - parsed_properties = [] +function _parse_properties(properties::AbstractString)::Vector{String} + parsed_properties = Vector{SubString{String}}([]) end_array = true end_property = false end_equality = false @@ -565,7 +565,7 @@ function _add_component!(data_dss::Dict{String,<:Any}, obj_type_name::AbstractSt end -function _add_component_edits!(data_dss::Dict{String,<:Any}, obj_type_name::AbstractString, object::Dict{String,<:Any}) +function _add_component_edits!(data_dss::Dict{String,<:Any}, obj_type_name::SubString{String}, object::Dict{String,<:Any}) obj_type = split(obj_type_name, '.'; limit=2)[1] if !haskey(data_dss, obj_type) data_dss[obj_type] = Dict{String,Any}( @@ -587,7 +587,7 @@ the `key` and `value` of the property. If a property of the same name already exists inside `object`, the original value is converted to an array, and the new value is appended to the end. """ -function _add_property(object::Dict, key::AbstractString, value::Any)::Dict +function _add_property(object::Dict{String,<:Any}, key::SubString{String}, value::Any)::Dict{String,Any} if !haskey(object, "prop_order") object["prop_order"] = Vector{String}(["name"]) end @@ -621,11 +621,11 @@ Parses a `component` with `properties` into a `object`. If `object` is not defined, an empty dictionary will be used. Assumes that unnamed properties are given in order, but named properties can be given anywhere. """ -function _parse_component(component::AbstractString, properties::AbstractString, object::Dict{String,<:Any}=Dict{String,Any}(); path::AbstractString="") +function _parse_component(component::AbstractString, properties::AbstractString, object::Dict{String,<:Any}=Dict{String,Any}(); path::String="")::Dict{String,Any} obj_type, name = split(component, '.'; limit=2) if !haskey(object, "name") - object["name"] = name + object["name"] = string(name) end property_array = _parse_properties(properties) @@ -698,7 +698,7 @@ end Parses an already separated line given by `elements` (an array) of an OpenDSS file into `current_obj`. If not defined, `current_obj` is an empty dictionary. """ -function _parse_line(elements::Array, current_obj::Dict=Dict{String,Any}(); path::AbstractString="") +function _parse_line(elements::Vector{String}; current_obj::Dict{String,<:Any}=Dict{String,Any}(), path::AbstractString="")::Tuple{SubString{String}, Dict{String,Any}} current_obj_type = strip(elements[2], ['\"', '\'']) if startswith(current_obj_type, "object") current_obj_type = split(current_obj_type, '=')[2] diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index ff3ef43b9..3f6330750 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -21,8 +21,7 @@ Creates a Dict{String,Any} containing all of the properties of a Linecode. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_linecode(name::AbstractString=""; kwargs...)::Dict{String,Any} - kwargs = Dict{Symbol,Any}(kwargs) +function _create_linecode(name::String=""; kwargs...)::Dict{String,Any} phases = get(kwargs, :nphases, 3) circuit_basefreq = get(kwargs, :circuit_basefreq, 60.0) basefreq = get(kwargs, :basefreq, circuit_basefreq) @@ -114,8 +113,10 @@ Creates a Dict{String,Any} containing all of the properties for a Line. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_line(bus1="", bus2="", name::AbstractString=""; kwargs...)::Dict{String,Any} - kwargs = Dict{Symbol,Any}(kwargs) +function _create_line(name::String=""; kwargs...)::Dict{String,Any} + bus1 = get(kwargs, :bus1, "") + bus2 = get(kwargs, :bus2, "") + phases = get(kwargs, :phases, 3) circuit_basefreq = get(kwargs, :circuit_basefreq, 60.0) basefreq = get(kwargs, :basefreq, circuit_basefreq) @@ -154,8 +155,8 @@ function _create_line(bus1="", bus2="", name::AbstractString=""; kwargs...)::Dic Ys = (complex(0.0, 2 * pi * basefreq * c1) * 2.0 + complex(0.0, 2 * pi * basefreq * c0)) / 3.0 Ym = (complex(0.0, 2 * pi * basefreq * c0) - complex(0.0, 2 * pi * basefreq * c1)) / 3.0 - Z = zeros(Complex{Float64}, phases, phases) - Yc = zeros(Complex{Float64}, phases, phases) + Z = Matrix{Complex{Float64}}(undef, phases, phases) + Yc = Matrix{Complex{Float64}}(undef, phases, phases) for i in 1:phases Z[i,i] = Zs Yc[i,i] = Ys @@ -240,8 +241,9 @@ Creates a Dict{String,Any} containing all of the expected properties for a Load. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_load(bus1="", name::AbstractString=""; kwargs...)::Dict{String,Any} - kwargs = Dict{Symbol,Any}(kwargs) +function _create_load(name::String=""; kwargs...)::Dict{String,Any} + bus1 = get(kwargs, :bus1, "") + kv = get(kwargs, :kv, 12.47) kw = get(kwargs, :kw, 10.0) pf = get(kwargs, :pf, 0.88) @@ -305,7 +307,7 @@ function _create_load(bus1="", name::AbstractString=""; kwargs...)::Dict{String, "cvrcurve" => get(kwargs, :cvrcurve, ""), "numcust" => get(kwargs, :numcust, 1), "zipv" => get(kwargs, :zipv, ""), - "%seriesrl" => get(kwargs, "%seriesrl", 0.5), + "%seriesrl" => get(kwargs, Symbol("%seriesrl"), 0.5), "relweight" => get(kwargs, :relweight, 1.0), "vlowpu" => get(kwargs, :vlowpu, 0.5), "puxharm" => get(kwargs, :puxharm, 0.0), @@ -324,8 +326,9 @@ Creates a Dict{String,Any} containing all of the expected properties for a Generator. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_generator(bus1="", name::AbstractString=""; kwargs...)::Dict{String,Any} - kwargs = Dict{Symbol,Any}(kwargs) +function _create_generator(name::String=""; kwargs...)::Dict{String,Any} + bus1 = get(kwargs, :bus1, "") + conn = get(kwargs, :conn, "wye") kw = get(kwargs, :kw, 100.0) @@ -344,7 +347,7 @@ function _create_generator(bus1="", name::AbstractString=""; kwargs...)::Dict{St "model" => get(kwargs, :model, 1), "yearly" => get(kwargs, :yearly, get(kwargs, :daily, Vector{Float64}([0.0, 0.0]))), "daily" => get(kwargs, :daily, Vector{Float64}([0.0, 0.0])), - "duty" => get(kwargs, "duty", ""), + "duty" => get(kwargs, :duty, ""), "dispmode" => get(kwargs, :dispmode, "default"), "disvalue" => get(kwargs, :dispvalue, 0.0), "conn" => conn, @@ -390,8 +393,9 @@ Capacitor. If `bus2` is not specified, the capacitor will be treated as a shunt. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_capacitor(bus1="", name::AbstractString=""; kwargs...)::Dict{String,Any} - kwargs = Dict{Symbol,Any}(kwargs) +function _create_capacitor(name::String=""; kwargs...)::Dict{String,Any} + bus1 = get(kwargs, :bus1, "") + phases = get(kwargs, :phases, 3) bus2 = get(kwargs, :bus2, string(split(bus1, ".")[1],".",join(fill("0", phases), "."))) @@ -429,8 +433,10 @@ Reactor. If `bus2` is not specified Reactor is treated like a shunt. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_reactor(bus1="", name::AbstractString="", bus2=""; kwargs...)::Dict{String,Any} - kwargs = Dict{Symbol,Any}(kwargs) +function _create_reactor(name::String=""; kwargs...)::Dict{String,Any} + bus1 = get(kwargs, :bus1, "") + bus2 = get(kwargs, :bus2, "") + phases = get(kwargs, :phases, 3) kvar = get(kwargs, :kvar, 1200.0) kv = get(kwargs, :kv, 12.47) @@ -455,7 +461,7 @@ function _create_reactor(bus1="", name::AbstractString="", bus2=""; kwargs...):: # TODO: handle `parallel` if (haskey(kwargs, :kv) && haskey(kwargs, :kvar)) || haskey(kwargs, :x) || haskey(kwargs, :lmh) || haskey(kwargs, :z) - if haskey(kwargs, :kvar) && haskey(:kv) + if haskey(kwargs, :kvar) && haskey(kwargs, :kv) kvarperphase = kvar / phases if conn == "delta" phasekv = kv @@ -574,8 +580,10 @@ generator. Mostly used as `source` which represents the circuit. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_vsource(bus1="", name::AbstractString="", bus2=0; kwargs...)::Dict{String,Any} - kwargs = Dict{Symbol,Any}(kwargs) +function _create_vsource(name::String=""; kwargs...)::Dict{String,Any} + bus1 = get(kwargs, :bus1, "sourcebus") + bus2 = get(kwargs, :bus2, "") + x1r1 = get(kwargs, :x1r1, 4.0) x0r0 = get(kwargs, :x0r0, 3.0) @@ -787,8 +795,7 @@ Creates a Dict{String,Any} containing all of the expected properties for a Transformer. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_transformer(name::AbstractString=""; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) +function _create_transformer(name::String=""; kwargs...) windings = isempty(name) ? 3 : get(kwargs, :windings, 2) phases = get(kwargs, :phases, 3) @@ -908,8 +915,7 @@ end "Transformer codes contain all of the same properties as a transformer except bus, buses, bank, xfmrcode" -function _create_xfmrcode(name::AbstractString=""; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) +function _create_xfmrcode(name::String=""; kwargs...) windings = isempty(name) ? 3 : get(kwargs, :windings, 2) phases = get(kwargs, :phases, 3) @@ -1028,8 +1034,9 @@ PVSystem. See OpenDSS document https://github.com/tshort/OpenDSS/blob/master/Doc/OpenDSS%20PVSystem%20Model.doc for valid fields and ways to specify the different properties. """ -function _create_pvsystem(bus1="", name::AbstractString=""; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) +function _create_pvsystem(name::String=""; kwargs...) + bus1 = get(kwargs, :bus1, "") + kv = get(kwargs, :kv, 12.47) kw = get(kwargs, :kw, 10.0) pf = get(kwargs, :pf, 0.88) @@ -1105,9 +1112,7 @@ Creates a Dict{String,Any} containing all expected properties for a storage element. See OpenDSS documentation for valid fields and ways to specify the different properties. """ -function _create_storage(bus1="", name::AbstractString=""; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) - +function _create_storage(name::String=""; kwargs...) Dict{String,Any}( "name" => name, "%charge" => get(kwargs, :charge, 100.0), @@ -1121,7 +1126,7 @@ function _create_storage(bus1="", name::AbstractString=""; kwargs...) "%stored" => get(kwargs, :stored, 100.0), "%x" => get(kwargs, :x, 50.0), "basefreq" => get(kwargs, :basefreq, 60.0), - "bus1" => bus1, + "bus1" => get(kwargs, :bus1, ""), "chargetrigger" => get(kwargs, :chargetrigger, 0.0), "class" => get(kwargs, :class, 0), "conn" => get(kwargs, :conn, "wye"), @@ -1161,13 +1166,13 @@ Creates a Dict{String,Any} containing all expected properties for a LoadShape element. See OpenDSS documentation for valid fields and ways to specify different properties. """ -function _create_loadshape(name::AbstractString=""; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) - +function _create_loadshape(name::String=""; kwargs...) if haskey(kwargs, :minterval) - kwargs[:interval] = kwargs[:minterval] / 60 + interval = kwargs[:minterval] / 60 elseif haskey(kwargs, :sinterval) - kwargs[:interval] = kwargs[:sinterval] / 60 / 60 + interval = kwargs[:sinterval] / 60 / 60 + else + interval = get(kwargs, :interval, 1.0) end npts = get(kwargs, :npts, 1) @@ -1175,14 +1180,14 @@ function _create_loadshape(name::AbstractString=""; kwargs...) pmult = get(kwargs, :pmult, fill(1.0, npts))[1:npts] qmult = get(kwargs, :qmult, fill(1.0, npts))[1:npts] - hour = get(kwargs, :hour, collect(range(1.0, step=get(kwargs, :interval, 1.0), length=npts)))[1:npts] + hour = get(kwargs, :hour, collect(range(1.0, step=interval, length=npts)))[1:npts] Dict{String,Any}( "name" => name, "npts" => npts, - "interval" => get(kwargs, :interval, 1.0), - "minterval" => get(kwargs, :interval, 1.0) .* 60, - "sinterval" => get(kwargs, :interval, 1.0) .* 3600, + "interval" => interval, + "minterval" => interval * 60, + "sinterval" => interval * 3600, "pmult" => pmult, "qmult" => qmult, "hour" => hour, @@ -1207,9 +1212,7 @@ Creates a Dict{String,Any} containing all expected properties for a XYCurve object. See OpenDSS documentation for valid fields and ways to specify different properties. """ -function _create_xycurve(name::AbstractString=""; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) - +function _create_xycurve(name::String=""; kwargs...) if haskey(kwargs, :points) xarray = Vector{Float64}([]) yarray = Vector{Float64}([]) @@ -1258,8 +1261,6 @@ end "" function _create_options(; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) - Dict{String,Any}( "%growth" => get(kwargs, Symbol("%growth"), 2.5), "%mean" => get(kwargs, Symbol("%mean"), 65.0), diff --git a/src/io/json.jl b/src/io/json.jl index ac0c064e4..044eacf0f 100644 --- a/src/io/json.jl +++ b/src/io/json.jl @@ -5,33 +5,34 @@ import JSON.Serializations: CommonSerialization, StandardSerialization import JSON.Writer: StructuralContext, show_json struct PMDSerialization <: CommonSerialization end -function _jsonver2juliaver!(pm_data) - if haskey(pm_data, "source_version") && isa(pm_data["source_version"], Dict) - pm_data["source_version"] = "$(pm_data["source_version"]["major"]).$(pm_data["source_version"]["minor"]).$(pm_data["source_version"]["patch"])" + +function _jsonver2juliaver!(data::Dict{String,<:Any}) + if haskey(data, "source_version") && isa(data["source_version"], Dict) + data["source_version"] = "$(data["source_version"]["major"]).$(data["source_version"]["minor"]).$(data["source_version"]["patch"])" end end -function _parse_mats_recursive!(dict) - parse = haskey(dict, "type") && dict["type"]=="Matrix" && haskey(dict, "eltype") && haskey(dict, "value") +function _parse_mats_recursive!(data::Dict{String,<:Any})::Dict{String,Any} + parse = haskey(data, "type") && data["type"]=="Matrix" && haskey(data, "eltype") && haskey(data, "value") if parse - return _parse_matrix_value(dict["value"], dict["eltype"]) + return _parse_matrix_value(data["value"], data["eltype"]) else - for (k,v) in dict + for (k,v) in data if isa(v, Dict) - dict[k] = _parse_mats!(v) + data[k] = _parse_mats!(v) elseif isa(v, Vector) && Dict <: eltype(v) - dict[k] = [isa(x, Dict) ? _parse_mats!(x) : x for x in v] + data[k] = [isa(x, Dict) ? _parse_mats!(x) : x for x in v] end end - return dict + return data end end -function _parse_mats!(root_dict) - stack = [(root_dict, k, v) for (k, v) in root_dict] +function _parse_mats!(data::Dict{String,<:Any}) + stack = [(data, k, v) for (k, v) in data] while !isempty(stack) (store, k, v) = pop!(stack) @@ -52,38 +53,38 @@ end "" function parse_json(file::String; kwargs...) - pmd_data = open(file) do io + data = open(file) do io parse_json(io; filetype=split(lowercase(file), '.')[end], kwargs...) end - return pmd_data + return data end "Parses json from iostream or string" function parse_json(io::IO; kwargs...)::Dict{String,Any} - pm_data = JSON.parse(io) + data = JSON.parse(io) - _jsonver2juliaver!(pm_data) + _jsonver2juliaver!(data) - _parse_mats!(pm_data) + _parse_mats!(data) if get(kwargs, :validate, true) - PowerModels.correct_network_data!(pm_data) + PowerModels.correct_network_data!(data) end - return pm_data + return data end -function print_file(path::String, pmd_data) +function print_file(path::String, data) open(path, "w") do io - print_file(io, pmd_data) + print_file(io, data) end end -function print_file(io::IO, pmd_data) - JSON.print(io, JSON.parse(sprint(show_json, PMDSerialization(), pmd_data))) +function print_file(io::IO, data) + JSON.print(io, JSON.parse(sprint(show_json, PMDSerialization(), data))) end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 479545b04..3e9fc03c3 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -100,7 +100,7 @@ Note that in the current feature set, fixed therefore equals constant function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, ground_terminal::Int=4) for (name, dss_obj) in get(data_dss, "load", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "load") - defaults = _apply_ordered_properties(_create_load(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_load(name; _to_kwargs(dss_obj)...), dss_obj) # parse the model model = defaults["model"] @@ -183,7 +183,7 @@ end function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "capacitor", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "capacitor") - defaults = _apply_ordered_properties(_create_capacitor(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_capacitor(name; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] conn = defaults["conn"] @@ -246,7 +246,7 @@ function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< for (name, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) if !haskey(dss_obj, "bus2") _apply_like!(dss_obj, data_dss, "reactor") - defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_reactor(name; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}( "phases" => defaults["phases"], @@ -273,7 +273,7 @@ function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< else Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating reactor.$name like line") _apply_like!(dss_obj, data_dss, "reactor") - defaults = _apply_ordered_properties(_create_reactor(dss_obj["bus1"], name, dss_obj["bus2"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_reactor(name; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -320,7 +320,7 @@ end function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "generator", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "generator") - defaults = _apply_ordered_properties(_create_generator(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_generator(name; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] @@ -357,14 +357,14 @@ end function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "vsource", Dict{<:Any,<:Any}()) _apply_like!(dss_obj, data_dss, "vsource") - defaults = _create_vsource(get(dss_obj, "bus1", "sourcebus"), name; _to_kwargs(dss_obj)...) + defaults = _create_vsource(name; _to_kwargs(dss_obj)...) ph1_ang = defaults["angle"] vm_pu = defaults["pu"] phases = defaults["phases"] - vnom = data_eng["settings"]["set_vbase_val"] + vnom = data_eng["settings"]["vbase"] vm = fill(vm_pu, phases)*vnom va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) @@ -399,8 +399,7 @@ function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, for (name, dss_obj) in get(data_dss, "linecode", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "linecode") - dss_obj["units"] = get(dss_obj, "units", "none") - dss_obj["circuit_basefreq"] = data_eng["settings"]["basefreq"] + dss_obj["circuit_basefreq"] = data_eng["settings"]["base_frequency"] defaults = _apply_ordered_properties(_create_linecode(name; _to_kwargs(dss_obj)...), dss_obj) @@ -426,11 +425,11 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An for (name, dss_obj) in get(data_dss, "line", Dict()) _apply_like!(dss_obj, data_dss, "line") - if haskey(dss_obj, "basefreq") && dss_obj["basefreq"] != data_eng["settings"]["basefreq"] - Memento.warn(_LOGGER, "basefreq=$(dss_obj["basefreq"]) on line $(dss_obj["name"]) does not match circuit basefreq=$(data_eng["settings"]["basefreq"])") + if haskey(dss_obj, "basefreq") && dss_obj["basefreq"] != data_eng["settings"]["base_frequency"] + Memento.warn(_LOGGER, "basefreq=$(dss_obj["basefreq"]) on line $(dss_obj["name"]) does not match circuit basefreq=$(data_eng["settings"]["base_frequency"])") end - defaults = _apply_ordered_properties(_create_line(dss_obj["bus1"], dss_obj["bus2"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_line(name; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] @@ -488,7 +487,7 @@ end function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "xfmrcode", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "xfmrcode") - defaults = _apply_ordered_properties(_create_xfmrcode(dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_xfmrcode(name; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] nrw = defaults["windings"] @@ -592,7 +591,7 @@ end function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "transformer") - defaults = _apply_ordered_properties(_create_transformer(dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_transformer(name; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] nrw = defaults["windings"] @@ -698,7 +697,7 @@ end function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "pvsystem", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "pvsystem") - defaults = _apply_ordered_properties(_create_pvsystem(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_pvsystem(name; _to_kwargs(dss_obj)...), dss_obj) # TODO pick parameters for solar objects @@ -740,7 +739,7 @@ end function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) for (name, dss_obj) in get(data_dss, "storage", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "storage") - defaults = _apply_ordered_properties(_create_storage(dss_obj["bus1"], dss_obj["name"]; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_storage(name; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -804,8 +803,7 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban end if haskey(data_dss, "vsource") && haskey(data_dss["vsource"], "source") && haskey(data_dss, "circuit") - source = data_dss["vsource"]["source"] - defaults = _create_vsource(get(source, "bus1", "sourcebus"), source["name"]; _to_kwargs(source)...) + defaults = _create_vsource("source"; _to_kwargs(data_dss["vsource"]["source"])...) source_bus = _parse_busname(defaults["bus1"])[1] data_eng["name"] = data_dss["circuit"] @@ -813,10 +811,10 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban # TODO rename fields data_eng["settings"]["v_var_scalar"] = 1e3 - data_eng["settings"]["set_vbase_val"] = defaults["basekv"] / sqrt(3) - data_eng["settings"]["set_vbase_bus"] = source_bus - data_eng["settings"]["set_sbase_val"] = defaults["basemva"] * 1e3 - data_eng["settings"]["basefreq"] = get(get(data_dss, "options", Dict{String,Any}()), "defaultbasefreq", 60.0) + data_eng["settings"]["vbase"] = defaults["basekv"] / sqrt(3) + data_eng["settings"]["base_bus"] = source_bus + data_eng["settings"]["sbase"] = defaults["basemva"] * 1e3 + data_eng["settings"]["base_frequency"] = get(get(data_dss, "options", Dict{String,Any}()), "defaultbasefreq", 60.0) data_eng["files"] = data_dss["filename"] else diff --git a/src/io/utils.jl b/src/io/utils.jl index f82a4dc13..de49bea28 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -493,7 +493,7 @@ function _discover_buses(data_dss::Dict{String,<:Any})::Set for obj_type in _dss_edge_components for (name, dss_obj) in get(data_dss, obj_type, Dict{String,Any}()) if obj_type == "transformer" - dss_obj = _create_transformer(dss_obj["name"]; _to_kwargs(dss_obj)...) + dss_obj = _create_transformer(name; _to_kwargs(dss_obj)...) for bus in dss_obj["buses"] push!(buses, split(bus, '.'; limit=2)[1]) end diff --git a/test/opendss.jl b/test/opendss.jl index 7ce4d487e..1b1bf6c68 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -272,7 +272,7 @@ @testset "opendss parse verify mvasc3/mvasc1 circuit parse" begin dss = PMD.parse_dss("../test/data/opendss/test_simple.dss") - circuit = PMD._create_vsource("sourcebus", "source"; PMD._to_kwargs(dss["vsource"]["source"])...) + circuit = PMD._create_vsource("source"; PMD._to_kwargs(dss["vsource"]["source"])...) @test circuit["mvasc1"] == 2100.0 @test circuit["mvasc3"] == 1900.0 @@ -280,7 +280,7 @@ @test isapprox(circuit["isc1"], 10543.0; atol=1e-1) dss = PMD.parse_dss("../test/data/opendss/test_simple3.dss") - circuit = PMD._create_vsource("sourcebus", "source"; PMD._to_kwargs(dss["vsource"]["source"])...) + circuit = PMD._create_vsource("source"; PMD._to_kwargs(dss["vsource"]["source"])...) @test circuit["mvasc1"] == 2100.0 @test isapprox(circuit["mvasc3"], 1900.0; atol=1e-1) @@ -288,7 +288,7 @@ @test isapprox(circuit["isc1"], 10543.0; atol=1e-1) dss = PMD.parse_dss("../test/data/opendss/test_simple4.dss") - circuit = PMD._create_vsource("sourcebus", "source"; PMD._to_kwargs(dss["vsource"]["source"])...) + circuit = PMD._create_vsource("source"; PMD._to_kwargs(dss["vsource"]["source"])...) @test isapprox(circuit["mvasc1"], 2091.5; atol=1e-1) @test circuit["mvasc3"] == 2000.0 From 54e43aa236268a12442586048ca8d3b77715881d Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 1 Apr 2020 16:23:47 -0600 Subject: [PATCH 116/224] UPD: component checks --- .../data_model_test.jl | 71 +++++++------------ src/data_model/checks.jl | 27 ++++--- src/data_model/components.jl | 11 +-- src/data_model/eng2math.jl | 2 +- 4 files changed, 44 insertions(+), 67 deletions(-) rename {src/data_model => examples}/data_model_test.jl (66%) diff --git a/src/data_model/data_model_test.jl b/examples/data_model_test.jl similarity index 66% rename from src/data_model/data_model_test.jl rename to examples/data_model_test.jl index d7a89153e..d9815f243 100644 --- a/src/data_model/data_model_test.jl +++ b/examples/data_model_test.jl @@ -1,9 +1,10 @@ using PowerModelsDistribution -import LinearAlgebra +using Ipopt -function make_test_data_model() +import LinearAlgebra: diagm - data_model = create_data_model() +function make_test_data_model() + data_model = Model() add_linecode!(data_model, "6_conds", rs=ones(6, 6), xs=ones(6, 6)) add_linecode!(data_model, "4_conds", rs=ones(4, 4), xs=ones(4, 4)) @@ -11,18 +12,18 @@ function make_test_data_model() add_linecode!(data_model, "2_conds", rs=ones(2, 2), xs=ones(2, 2)) # 3 phase + 3 neutral conductors - add_line!(data_model, "1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6)) - add_line!(data_model, "2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4]) + add_line!(data_model, "1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6)) + add_line!(data_model, "2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4]) # 3 phase + 1 neutral conductors - add_line!(data_model, "3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2) + add_line!(data_model, "3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2) # 3 phase conductors - add_line!(data_model, "4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) + add_line!(data_model, "4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) # 2 phase + 1 neutral conductors - add_line!(data_model, "5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4]) + add_line!(data_model, "5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4]) # 1 phase + 1 neutral conductors - add_line!(data_model, "6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4]) + add_line!(data_model, "6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4]) # 2 phase conductors - add_line!(data_model, "7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) + add_line!(data_model, "7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) for i in 8:1000 add_line!(data_model, id="$i", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) end @@ -72,18 +73,18 @@ end function make_3wire_data_model() - data_model = create_data_model() + data_model = Model() - add_linecode!(data_model, "3_conds", rs=LinearAlgebra.diagm(0=>fill(1.0, 3)), xs=LinearAlgebra.diagm(0=>fill(1.0, 3))) + add_linecode!(data_model, "3_conds", rs=diagm(0=>fill(1.0, 3)), xs=diagm(0=>fill(1.0, 3))) - add_voltage_source!(data_model, "source", bus="sourcebus", connections=collect(1:3), + add_voltage_source!(data_model, "source", "sourcebus", connections=collect(1:3), vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), rs=ones(3,3)/10 ) # 3 phase conductors - add_line!(data_model, id=:test, f_bus="sourcebus", t_bus="tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) + add_line!(data_model, :test, "sourcebus", "tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) add_bus!(data_model, "sourcebus", terminals=collect(1:3)) add_bus!(data_model, "tr_prim", terminals=collect(1:4)) @@ -99,7 +100,7 @@ function make_3wire_data_model() # imag=0.00, # )) - add_transformer_nw!(data_model, "1", bus=["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], + add_transformer!(data_model, "1", ["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], vnom=[0.230, 0.230], snom=[0.230, 0.230], configuration=["wye", "wye"], xsc=[0.0], @@ -111,7 +112,7 @@ function make_3wire_data_model() # - add_load!(data_model, "1", bus="tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0]) + add_load!(data_model, "1", "tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0]) # add!(data_model, "generator", create_generator("1", "source", # connections=[1, 2, 3, 4], @@ -128,34 +129,10 @@ end data_model = make_3wire_data_model() -check_data_model(data_model) -data_model -# -data_model_map!(data_model) -#bsh = data_model["shunt"]["_virtual_1"]["b_sh"] -## -make_per_unit!(data_model, vbases=Dict("sourcebus"=>0.230)) -# -data_model_index!(data_model) -data_model_make_compatible_v8!(data_model) -## -import PowerModelsDistribution -PMD = PowerModelsDistribution -import PowerModels -PMs = PowerModels -import InfrastructureModels -IM = InfrastructureModels - -import JuMP, Ipopt - -ipopt_solver = JuMP.with_optimizer(Ipopt.Optimizer) -pm = PMs.instantiate_model(data_model, PMs.IVRPowerModel, PMD.build_mc_opf_iv, multiconductor=true, ref_extensions=[PMD.ref_add_arcs_trans!]) -sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) - -solution_make_si!(sol["solution"], data_model) -solution_identify!(sol["solution"], data_model) -solution_unmap!(sol["solution"], data_model) -#vm = sol["solution"]["bus"]["tr_sec"]["vm"] -#va = sol["solution"]["bus"]["tr_sec"]["va"] -#v = vm.*exp.(im*va/180*pi) -sol["solution"] +correct_network_data!(data_model) + +ipopt_solver = with_optimizer(Ipopt.Optimizer, tol=1e-6, print_level=3) + +result = run_mc_pf(data_model, ACPPowerModel, ipopt_solver) + + diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index 52875337a..7ce289c5a 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -76,7 +76,7 @@ const _eng_model_dtypes = Dict{Symbol,Dict{Symbol,Type}}( :qd => Array{<:Real, 1}, :pd_ref => Array{<:Real, 1}, :qd_ref => Array{<:Real, 1}, - :vnom => Array{<:Real, 1}, + :vnom => Real, :alpha => Array{<:Real, 1}, :beta => Array{<:Real, 1}, ), @@ -212,17 +212,16 @@ const _eng_model_req_fields= Dict{Symbol,Vector{Symbol}}( function check_eng_data_model(data_eng::Dict{String,<:Any}) for (component_type, components) in data_eng if isa(components, Dict) - for (name, component) in keys(components) + for (name, component) in components _check_eng_component_dtypes(data_eng, component_type, name) for field in get(_eng_model_req_fields, Symbol(component_type), Vector{Symbol}([])) @assert haskey(component, string(field)) "The property \'$field\' is missing on $component_type $name" end - for check in get(_eng_model_checks, Symbol(component_type), missing) - if !ismissing(check) - @eval $(check)(data_eng, name) - end + check = get(_eng_model_checks, Symbol(component_type), missing) + if !ismissing(check) + getfield(PowerModelsDistribution, check)(data_eng, name) end end end @@ -231,7 +230,7 @@ end "checks that an engineering model component has the correct data types" -function _check_eng_component_dtypes(data_eng::Dict{String,<:Any}, component_type::String, component_name::String; additional_dtypes=Dict{Symbol,Type}()) +function _check_eng_component_dtypes(data_eng::Dict{String,<:Any}, component_type::String, component_name::Any; additional_dtypes=Dict{Symbol,Type}()) if haskey(_eng_model_dtypes, Symbol(component_type)) dtypes = merge(_eng_model_dtypes[Symbol(component_type)], additional_dtypes) else @@ -243,7 +242,7 @@ function _check_eng_component_dtypes(data_eng::Dict{String,<:Any}, component_typ for (field, dtype) in dtypes if haskey(component, string(field)) - @assert isa(component[field], dtype) "$component_type $component_name: the property $field should be a $dtype, not a $(typeof(component[field]))" + @assert isa(component[string(field)], dtype) "$component_type $component_name: the property $field should be a $dtype, not a $(typeof(component[string(field)]))" end end else @@ -260,8 +259,8 @@ end "check that `fields` has size `data_size`" -function _check_has_size(component::Dict{String,<:Any}, fields::Vector{String}, data_size::Tuple; context::Union{String,Missing}=missing, allow_missing::Bool=true) - for key in keys +function _check_has_size(component::Dict{String,<:Any}, fields::Vector{String}, data_size::Union{Int, Tuple}; context::Union{String,Missing}=missing, allow_missing::Bool=true) + for key in fields if haskey(component, key) || !allow_missing @assert all(size(component[string(key)]).==data_size) "$context: the property $key should have as size $data_size" end @@ -356,7 +355,7 @@ end "linecode data checks" function _check_linecode(data_eng::Dict{String,<:Any}, name::Any) - _check_same_size(data_eng["linecode"][name], [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to]) + _check_same_size(data_eng["linecode"][name], string.([:rs, :xs, :g_fr, :g_to, :b_fr, :b_to])) end @@ -373,9 +372,9 @@ function _check_line(data_eng::Dict{String,<:Any}, name::Any) @assert haskey(data_eng, "linecode") && haskey(data_eng["linecode"], "$linecode_obj_name") "line $name: the linecode $linecode_obj_name is not defined." linecode = data_eng["linecode"]["$linecode_obj_name"] - for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - @assert !haskey(line, key) "line $name: a line with a linecode, should not specify $key; this is already done by the linecode." - end + # for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + # @assert !haskey(line, key) "line $name: a line with a linecode, should not specify $key; this is already done by the linecode." + # end N = size(linecode["rs"])[1] @assert length(line["f_connections"])==N "line $name: the number of terminals should match the number of conductors in the linecode." diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 5d8f884a2..2a9c3b531 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -4,7 +4,7 @@ "adds kwargs that were specified but unused by the required defaults to the component" -function _add_unused_kwargs!(object::Dict{String,<:Any}, kwargs::Dict{Symbol,<:Any}) +function _add_unused_kwargs!(object::Dict{String,<:Any}, kwargs) for (property, value) in kwargs if !haskey(object, string(property)) object[string(property)] = value @@ -36,7 +36,7 @@ function add_object!(data_eng::Dict{String,<:Any}, obj_type::String, obj_id::Any end if obj_type == "transformer" - for (wdg, bus_id) in enumerate(data_eng["bus"]) + for (wdg, bus_id) in enumerate(object["bus"]) if !haskey(data_eng["bus"], bus_id) data_eng["bus"][bus_id] = create_bus(; terminals=object["connections"][wdg]) end @@ -65,7 +65,7 @@ function Model(model_type::String="engineering"; kwargs...)::Dict{String,Any} "v_var_scalar" => get(kwargs, :v_var_scalar, 1e3), "vbase" => get(kwargs, :basekv, 1.0), "sbase" => get(kwargs, :baseMVA, 1.0), - "basefreq" => get(kwargs, :basefreq, 60.0), + "base_frequency" => get(kwargs, :basefreq, 60.0), ) ) @@ -127,10 +127,10 @@ function create_line(; kwargs...) @assert haskey(kwargs, :f_bus) && haskey(kwargs, :t_bus) "Line must at least have f_bus and t_bus specified" N = length(get(kwargs, :f_connections, collect(1:4))) + n_conductors = N # if no linecode, then populate loss parameters with zero if !haskey(kwargs, :linecode) - n_conductors = N for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] if haskey(kwargs, key) n_conductors = size(kwargs[key])[1] @@ -244,6 +244,7 @@ function create_transformer(; kwargs...) "tm_max" => get(kwargs, :tm_max, fill(fill(1.1, 3), n_windings)), "tm_step" => get(kwargs, :tm_step, fill(fill(1/32, 3), n_windings)), "tm_fix" => get(kwargs, :tm_fix, fill(fill(true, 3), n_windings)), + "fixed" => get(kwargs, :fixed, fill(1, n_windings)), "connections" => get(kwargs, :connections, fill(collect(1:4), n_windings)), ) @@ -323,7 +324,7 @@ add_linecode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(da # Edge objects add_line!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "line", id, create_line(; f_bus=f_bus, t_bus=t_bus, kwargs...)) -add_transformer!(data_eng::Dict{String,<:Any}, id::Any, bus::Vector{Any}; kwargs...) = add_object!(data_eng, "transformer", id, create_transformer(; bus=bus, kwargs...)) +add_transformer!(data_eng::Dict{String,<:Any}, id::Any, bus::Vector{<:Any}; kwargs...) = add_object!(data_eng, "transformer", id, create_transformer(; bus=bus, kwargs...)) # add_switch!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "switch", id, create_switch(; f_bus=f_bus, t_bus=t_bus, kwargs...)) # add_series_capacitor!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "series_capacitor", id, create_series_capacitor(; f_bus=f_bus, t_bus=t_bus, kwargs...)) # add_line_reactor!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "line_reactor", id, create_line_reactor(; f_bus=f_bus, t_bus=t_bus, kwargs...)) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 0d2453fd0..0f72a0c3e 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -29,7 +29,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, @assert get(data_eng, "data_model", "mathematical") == "engineering" data_math = Dict{String,Any}( - "name" => data_eng["name"], + "name" => get(data_eng, "name", ""), "per_unit" => get(data_eng, "per_unit", false), "data_model" => "mathematical", "settings" => data_eng["settings"], From d6a59e80ee3b976158ce11420431cc5408b70ee5 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 2 Apr 2020 16:04:15 -0600 Subject: [PATCH 117/224] FIX: Double export --- src/core/export.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/export.jl b/src/core/export.jl index d3c879c08..dac52aab7 100644 --- a/src/core/export.jl +++ b/src/core/export.jl @@ -43,4 +43,4 @@ end export ACPPowerModel, ACRPowerModel, DCPPowerModel, NFAPowerModel, SOCWRPowerModel, conductor_ids, ismulticonductor # InfrastructureModels Exports -export ids, ref, var, con, sol, nw_ids, nws, ismultinetwork, ismulticonductor, conductor_ids +export ids, ref, var, con, sol, nw_ids, nws, ismultinetwork From 0017c0cde4b1ece477e64d43e551ebda9b6ee742 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 7 Apr 2020 22:48:17 +1000 Subject: [PATCH 118/224] xfmrcode/transformer fix --- src/io/opendss.jl | 81 +++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 3e9fc03c3..274b20964 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -505,10 +505,10 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, "imag" => missing, "xsc" => Vector{Any}(missing, nrw == 2 ? 1 : 3), "leadlag" => missing, - "source_id" => "xfmrcode.$name" + "source_id" => "xfmrcode.$name", ) - eng_obj = Dict{String,Any}() + eng_obj = Dict{String,Any}("phases"=>nphases, "windings"=>nrw) winding_key = ["", "_2", "_3"] for w in 1:nrw @@ -577,6 +577,10 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["leadlag"] = defaults["leadlag"] end + if !haskey(eng_obj, "configuration") + eng_obj["configuration"] = fill("wye", nrw) + end + if import_all _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end @@ -593,11 +597,36 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri _apply_like!(dss_obj, data_dss, "transformer") defaults = _apply_ordered_properties(_create_transformer(name; _to_kwargs(dss_obj)...), dss_obj) - nphases = defaults["phases"] - nrw = defaults["windings"] + eng_obj = Dict{String, Any}() - dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") - confs = [dyz_map[x] for x in defaults["conns"]] + # import certain properties from the xfmrcode; needed to check connectivity + if haskey(dss_obj, "xfmrcode") + xfmrcode = data_eng["xfmrcode"][dss_obj["xfmrcode"]] + @show xfmrcode + nphases = xfmrcode["phases"] + nrw = xfmrcode["windings"] + confs = xfmrcode["configuration"] + #TODO also tm_min/tm_max + else + # import defaults and save in eng_obj + nphases = defaults["phases"] + nrw = defaults["windings"] + dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") + eng_obj["configuration"] = confs = [dyz_map[x] for x in defaults["conns"]] + eng_obj["tm_min"] = fill(fill(defaults["mintap"], nphases), nrw) + eng_obj["tm_max"] = fill(fill(defaults["maxtap"], nphases), nrw) + eng_obj["vnom"] = [defaults["kvs"][w] for w in 1:nrw] + eng_obj["snom"] = [defaults["kvas"][w] for w in 1:nrw] + # loss model (converted to SI units, referred to secondary) + eng_obj["rs"] = [defaults["%rs"][w]/100 for w in 1:nrw] + eng_obj["noloadloss"] = defaults["%noloadloss"]/100 + eng_obj["imag"] = defaults["%imag"]/100 + if nrw==2 + eng_obj["xsc"] = [defaults["xhl"]]/100 + elseif nrw==3 + eng_obj["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 + end + end # test if this transformer conforms with limitations if nphases<3 && "delta" in confs @@ -609,28 +638,19 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri Memento.error(_LOGGER, "For now parsing of xscarray is not supported. At most 3 windings are allowed, not $nrw.") end - eng_obj = Dict{String, Any}() + #TODO default is 2-element if xfmrcode is 3-winding + # eng_obj["tm"] = [fill(defaults["taps"][w], nphases) for w in 1:nrw] + eng_obj["fixed"] = [fill(true, nphases) for w in 1:nrw] + eng_obj["bus"] = Array{String, 1}(undef, nrw) eng_obj["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) - eng_obj["tm"] = Array{Array{Real, 1}, 1}(undef, nrw) - eng_obj["tm_min"] = Array{Array{Real, 1}, 1}(undef, nrw) - eng_obj["tm_max"] = Array{Array{Real, 1}, 1}(undef, nrw) - eng_obj["fixed"] = [[true for i in 1:nphases] for j in 1:nrw] - eng_obj["vnom"] = [defaults["kvs"][w] for w in 1:nrw] - eng_obj["snom"] = [defaults["kvas"][w] for w in 1:nrw] - eng_obj["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) - eng_obj["configuration"] = Array{String, 1}(undef, nrw) eng_obj["polarity"] = Array{Int, 1}(undef, nrw) - eng_obj["leadlag"] = defaults["leadlag"] - - eng_obj["nphases"] = defaults["phases"] + eng_obj["leadlag"] = defaults["leadlag"] #TODO import from xfmrcode or not? yes I guess for w in 1:nrw eng_obj["bus"][w] = _parse_busname(defaults["buses"][w])[1] - conf = dyz_map[defaults["conns"][w]] - eng_obj["configuration"][w] = conf - + conf = confs[w] terminals_default = conf=="wye" ? [1:nphases..., 0] : collect(1:nphases) # append ground if connections one too short @@ -641,13 +661,8 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"][w]], eng_obj["connections"][w]) end - eng_obj["polarity"][w] = 1 - eng_obj["tm"][w] = fill(defaults["taps"][w], nphases) - eng_obj["tm_min"][w] = fill(defaults["mintap"], nphases) - eng_obj["tm_max"][w] = fill(defaults["maxtap"], nphases) - if w>1 - prim_conf = eng_obj["configuration"][1] + prim_conf = confs[1] if defaults["leadlag"] in ["ansi", "lag"] if prim_conf=="delta" && conf=="wye" eng_obj["polarity"][w] = -1 @@ -659,6 +674,8 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["connections"][w] = _barrel_roll(eng_obj["connections"][w], -1) end end + else + eng_obj["polarity"][w] = 1 end end @@ -668,16 +685,6 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["bank"] = defaults["bank"] end - # loss model (converted to SI units, referred to secondary) - eng_obj["rs"] = [defaults["%rs"][w]/100 for w in 1:nrw] - eng_obj["noloadloss"] = defaults["%noloadloss"]/100 - eng_obj["imag"] = defaults["%imag"]/100 - if nrw==2 - eng_obj["xsc"] = [defaults["xhl"]]/100 - elseif nrw==3 - eng_obj["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 - end - eng_obj = create_transformer(; Dict(Symbol.(keys(eng_obj)).=>values(eng_obj))...) if !haskey(data_eng, "transformer") From def9fd178dddd6870e7988fe3fdfe97174b48e09 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 7 Apr 2020 23:55:23 +1000 Subject: [PATCH 119/224] trans ut working --- src/io/opendss.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 274b20964..377bce45d 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -638,13 +638,16 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri Memento.error(_LOGGER, "For now parsing of xscarray is not supported. At most 3 windings are allowed, not $nrw.") end - #TODO default is 2-element if xfmrcode is 3-winding - # eng_obj["tm"] = [fill(defaults["taps"][w], nphases) for w in 1:nrw] + if haskey(dss_obj, "taps") + eng_obj["tm"] = [fill(defaults["taps"][w], nphases) for w in 1:nrw] + else + eng_obj["tm"] = [fill(1.0, nphases) for w in 1:nrw] + end eng_obj["fixed"] = [fill(true, nphases) for w in 1:nrw] eng_obj["bus"] = Array{String, 1}(undef, nrw) eng_obj["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) - eng_obj["polarity"] = Array{Int, 1}(undef, nrw) + eng_obj["polarity"] = fill(1, nrw) eng_obj["leadlag"] = defaults["leadlag"] #TODO import from xfmrcode or not? yes I guess for w in 1:nrw @@ -674,8 +677,6 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["connections"][w] = _barrel_roll(eng_obj["connections"][w], -1) end end - else - eng_obj["polarity"][w] = 1 end end From f5c6c08a1258d17fad25f935d42eb62d4b66b081 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 15 Apr 2020 07:44:09 -0600 Subject: [PATCH 120/224] FIX: Buscoords parsing (func arg type) --- src/io/dss_parse.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 9841e33d9..7968b1ea5 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -463,7 +463,7 @@ Parses a Bus Coordinate `file`, in either "dat" or "csv" formats, where in "dat", columns are separated by spaces, and in "csv" by commas. File expected to contain "bus,x,y" on each line. """ -function _parse_buscoords_file(file::String)::Dict{String,Any} +function _parse_buscoords_file(file::AbstractString)::Dict{String,Any} file_str = read(open(file), String) regex = r",\s*" if endswith(lowercase(file), "csv") || endswith(lowercase(file), "dss") From 3b724088acee459d451294276cd6a7b53701f867 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 15 Apr 2020 15:11:08 -0600 Subject: [PATCH 121/224] FIX: xfmrcode parsing --- src/data_model/eng2math.jl | 55 +--- src/data_model/utils.jl | 42 ++- src/io/opendss.jl | 258 +++++++++--------- src/io/utils.jl | 17 +- .../opendss/case3_balanced_prop-order.dss | 2 +- test/data/opendss/test2_master.dss | 2 +- test/opendss.jl | 2 +- 7 files changed, 178 insertions(+), 200 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 0f72a0c3e..bcd81053c 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -474,15 +474,7 @@ end "" function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) - if haskey(eng_obj, "linecode") && haskey(data_eng, "linecode") && haskey(data_eng["linecode"], eng_obj["linecode"]) - linecode = data_eng["linecode"][eng_obj["linecode"]] - - for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - if !haskey(eng_obj, property) && haskey(linecode, property) - eng_obj[property] = linecode[property] - end - end - end + _apply_linecode!(eng_obj, data_eng) math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) math_obj["name"] = name @@ -731,52 +723,11 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic to_map = data_math["map"][map_idx][:to] + _apply_xfmrcode!(eng_obj, data_eng) + vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] - if haskey(eng_obj, "xfmrcode") && haskey(data_eng, "xfmrcode") && haskey(data_eng["xfmrcode"], eng_obj["xfmrcode"]) - xfmrcode = data_eng["xfmrcode"][eng_obj["xfmrcode"]] - - if haskey(xfmrcode, "leadlag") - eng_obj["leadlag"] = xfmrcode["leadlag"] - end - - for w in 1:length("configuration") - for prop in ["configuration", "tm", "tm_min", "tm_max", "vnom", "snom", "rs"] - if !ismissing(xfmrcode[prop][w]) - eng_obj[prop][w] = xfmrcode[prop][w] - end - end - - if w>1 - prim_conf = eng_obj["configuration"][1] - if eng_obj["leadlag"] in ["ansi", "lag"] - if prim_conf=="delta" && xfmrcode["configuration"][w]=="wye" - eng_obj["polarity"][w] = -1 - eng_obj["connections"][w] = [_barrel_roll(eng_obj["connections"][w][1:end-1], 1)..., eng_obj["connections"][w][end]] - end - else # hence eng_obj["leadlag"] in ["euro", "lead"] - if prim_conf=="wye" && xfmrcode["configuration"][w]=="delta" - eng_obj["polarity"][w] = -1 - eng_obj["connections"][w] = _barrel_roll(eng_obj["connections"][w], -1) - end - end - end - end - - for prop in ["noloadloss", "imag", "xsc"] - if prop == "xsc" - for (i, v) in enumerate(xfmrcode[prop]) - if !ismissing(v) - eng_obj[prop] = v - end - end - elseif !ismissing(xfmrcode[prop]) - eng_obj[prop] = xfmrcode[prop] - end - end - end - nrw = length(eng_obj["bus"]) # calculate zbase in which the data is specified, and convert to SI diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 1a6003023..fc0f57fa8 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -462,9 +462,41 @@ end "convert cost model names" function _add_gen_cost_model!(math_obj::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}) - math_obj["model"] = get(eng_obj, "cost_model", 2) - math_obj["startup"] = get(eng_obj, "startup_cost", 0.0) - math_obj["shutdown"] = get(eng_obj, "shutdown_cost", 0.0) - math_obj["ncost"] = get(eng_obj, "ncost_terms", 3) - math_obj["cost"] = get(eng_obj, "cost", [0.0, 1.0, 0.0]) + math_obj["model"] = get(eng_obj, "cost_pg_model", 2) + math_obj["startup"] = 0.0 + math_obj["shutdown"] = 0.0 + math_obj["cost"] = get(eng_obj, "cost_pg_parameters", [0.0, 1.0, 0.0]) + math_obj["ncost"] = length(math_obj["cost"]) +end + + +function _apply_xfmrcode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:Any}) + if haskey(eng_obj, "xfmrcode") && haskey(data_eng, "xfmrcode") && haskey(data_eng["xfmrcode"], eng_obj["xfmrcode"]) + xfmrcode = data_eng["xfmrcode"][eng_obj["xfmrcode"]] + + for (k, v) in xfmrcode + if !haskey(eng_obj, k) + eng_obj[k] = v + elseif haskey(eng_obj, k) && k in ["vnom", "snom", "tm"] + for (w, vw) in enumerate(eng_obj[k]) + if ismissing(vw) + eng_obj[k][w] = v[w] + end + end + end + end + end +end + + +function _apply_linecode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:Any}) + if haskey(eng_obj, "linecode") && haskey(data_eng, "linecode") && haskey(data_eng["linecode"], eng_obj["linecode"]) + linecode = data_eng["linecode"][eng_obj["linecode"]] + + for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + if !haskey(eng_obj, property) && haskey(linecode, property) + eng_obj[property] = linecode[property] + end + end + end end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 377bce45d..2c6b08291 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -431,12 +431,13 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An defaults = _apply_ordered_properties(_create_line(name; _to_kwargs(dss_obj)...), dss_obj) + _like_is_switch = haskey(dss_obj, "like") && get(get(data_dss["line"], dss_obj["like"], Dict{String,Any}()), "switch", false) nphases = defaults["phases"] eng_obj = Dict{String,Any}( "f_bus" => _parse_busname(defaults["bus1"])[1], "t_bus" => _parse_busname(defaults["bus2"])[1], - "length" => defaults["length"], + "length" => defaults["switch"] || _like_is_switch ? 0.001 : defaults["length"], "f_connections" => _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)), "t_connections" => _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)), "status" => convert(Int, defaults["enabled"]), @@ -463,11 +464,10 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An end # if the ground is used directly, register - if 0 in eng_obj["f_connections"] - _register_awaiting_ground!(data_eng["bus"][eng_obj["f_bus"]], eng_obj["f_connections"]) - end - if 0 in eng_obj["t_connections"] - _register_awaiting_ground!(data_eng["bus"][eng_obj["t_bus"]], eng_obj["t_connections"]) + for prefix in ["f_", "t_"] + if 0 in eng_obj["$(prefix)connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["$(prefix)bus"]], eng_obj["$(prefix)connections"]) + end end if import_all @@ -492,145 +492,149 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, nphases = defaults["phases"] nrw = defaults["windings"] - _eng_obj = Dict{String,Any}( - "tm" => Vector{Any}(missing, nrw), - "tm_min" => Vector{Any}(missing, nrw), - "tm_max" => Vector{Any}(missing, nrw), - "fixed" => Vector{Any}(missing, nrw), - "vnom" => Vector{Any}(missing, nrw), - "snom" => Vector{Any}(missing, nrw), - "configuration" => Vector{Any}(missing, nrw), - "rs" => Vector{Any}(missing, nrw), - "noloadloss" => missing, - "imag" => missing, - "xsc" => Vector{Any}(missing, nrw == 2 ? 1 : 3), - "leadlag" => missing, + eng_obj = Dict{String,Any}( + "tm" => Vector{Vector{Float64}}([fill(tap, nphases) for tap in defaults["taps"]]), + "tm_min" => Vector{Vector{Float64}}(fill(fill(defaults["mintap"], nphases), nrw)), + "tm_max" => Vector{Vector{Float64}}(fill(fill(defaults["maxtap"], nphases), nrw)), + "tm_fix" => Vector{Vector{Int}}([fill(1, nphases) for w in 1:nrw]), + "tm_step" => Vector{Vector{Float64}}(fill(fill(1/32, nphases), nrw)), + "fixed" => Vector{Int}(fill(1, nrw)), + "vnom" => Vector{Float64}(defaults["kvs"]), + "snom" => Vector{Float64}(defaults["kvas"]), + "configuration" => Vector{String}(defaults["conns"]), + "rs" => Vector{Float64}(defaults["%rs"] ./ 100), + "noloadloss" => defaults["%noloadloss"] / 100, + "imag" => defaults["%imag"] / 100, + "xsc" => nrw == 2 ? [defaults["xhl"] / 100] : [defaults["xhl"], defaults["xht"], defaults["xlt"]] ./ 100, + "leadlag" => defaults["leadlag"], "source_id" => "xfmrcode.$name", ) - eng_obj = Dict{String,Any}("phases"=>nphases, "windings"=>nrw) - - winding_key = ["", "_2", "_3"] - for w in 1:nrw - if haskey(dss_obj, "conns") || haskey(dss_obj, "conn$(winding_key[w])") - eng_obj["configuration"] = _eng_obj["configuration"] - eng_obj["configuration"][w] = defaults["conns"][w] - end - - if haskey(dss_obj, "taps") || haskey(dss_obj, "tap$(winding_key[w])") - eng_obj["tm"] = _eng_obj["tm"] - eng_obj["tm"][w] = fill(defaults["taps"][w], nphases) - end + if import_all + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + end - if haskey(dss_obj, "mintap") - eng_obj["tm_min"] = _eng_obj["tm_min"] - eng_obj["tm_min"][w] = fill(defaults["mintap"], nphases) - end + _add_eng_obj!(data_eng, "xfmrcode", name, eng_obj) + end +end - if haskey(dss_obj, "maxtap") - eng_obj["tm_max"] = _eng_obj["tm_max"] - eng_obj["tm_max"][w] = fill(defaults["maxtap"], nphases) - end - if haskey(dss_obj, "kvs") || haskey(dss_obj, "kv$(winding_key[w])") - eng_obj["vnom"] = _eng_obj["vnom"] - eng_obj["vnom"][w] = defaults["kvs"][w] - end +"Adds transformers to `data_eng` from `data_dss`" +function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) + for (name, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, "transformer") + defaults = _apply_ordered_properties(_create_transformer(name; _to_kwargs(dss_obj)...), dss_obj) - if haskey(dss_obj, "kvas") || haskey(dss_obj, "kva$(winding_key[w])") - eng_obj["snom"] = _eng_obj["snom"] - eng_obj["snom"][w] = defaults["kvas"][w] - end + eng_obj = Dict{String, Any}( + "source_id" => "transformer.$name" + ) - if haskey(dss_obj, "%rs") || haskey(dss_obj, "%r$(winding_key[w])") - eng_obj["rs"] = _eng_obj["rs"] - eng_obj["rs"][w] = defaults["%rs"][w] / 100 + # no way around checking xfmrcode for these properties + _shared = Dict{String,Any}( + "leadlag" => defaults["leadlag"], + "conns" => defaults["conns"], + "phases" => defaults["phases"], + "windings" => defaults["windings"] + ) + if haskey(dss_obj, "xfmrcode") + xfmrcode_dss_obj = deepcopy(data_dss["xfmrcode"][dss_obj["xfmrcode"]]) + _apply_like!(xfmrcode_dss_obj, data_dss, "xfmrcode") + xfmrcode = _apply_ordered_properties(_create_xfmrcode(string(dss_obj["xfmrcode"]); _to_kwargs(xfmrcode_dss_obj)...), xfmrcode_dss_obj) + + for key in ["leadlag", "conns", "phases", "windings"] + if haskey(dss_obj, key) && _is_after_xfmrcode(dss_obj["prop_order"], key) + _shared[key] = defaults[key] + else + _shared[key] = xfmrcode[key] + end end end - if haskey(dss_obj, "%noloadloss") - eng_obj["noloadloss"] = _eng_obj["noloadloss"] - eng_obj["noloadloss"] = defaults["%noloadloss"] / 100 - end + leadlag = _shared["leadlag"] + confs = _shared["conns"] + nphases = _shared["phases"] + nrw = _shared["windings"] - if haskey(dss_obj, "%imag") - eng_obj["imag"] = _eng_obj["imag"] - eng_obj["imag"] = defaults["%imag"] / 100 - end - - if haskey(dss_obj, "xhl") - eng_obj["xsc"] = _eng_obj["xsc"] - eng_obj["xsc"][1] = defaults["xhl"] / 100 + # taps + if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, "taps") && _is_after_xfmrcode(dss_obj["prop_order"], "taps")) || all(haskey(dss_obj, k) && _is_after_xfmrcode(dss_obj["prop_order"], k) for k in ["tap", "tap_2", "tap_3"]) + eng_obj["tm"] = [fill(defaults["taps"][w], nphases) for w in 1:nrw] + else + for (w, key_suffix) in enumerate(["", "_2", "_3"]) + if haskey(dss_obj, "tap$(key_suffix)") && _is_after_xfmrcode(dss_obj["prop_order"], "tap$(key_suffix)") + if !haskey(eng_obj, "tm") + eng_obj["tm"] = Vector{Any}(missing, nrw) + end + eng_obj["tm"][w] = fill(defaults["taps"][defaults["wdg$(key_suffix)"]], nphases) + end + end end - if haskey(dss_obj, "xht") && nrw == 3 - eng_obj["xsc"] = _eng_obj["xsc"] - eng_obj["xsc"][2] = defaults["xht"] / 100 + # kvs, kvas + for (fr_key, to_key) in zip(["kv", "kva"], ["vnom", "snom"]) + if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, "$(fr_key)s") && _is_after_xfmrcode(dss_obj["prop_order"], "$(fr_key)s")) || all(haskey(dss_obj, "$(fr_key)$(key_suffix)") && _is_after_xfmrcode(dss_obj["prop_order"], "$(fr_key)$(key_suffix)") for key_suffix in ["", "_2", "_3"]) + eng_obj[to_key] = defaults["$(fr_key)s"] + else + for (w, key_suffix) in enumerate(["", "_2", "_3"]) + if haskey(dss_obj, "$(fr_key)$(key_suffix)") && _is_after_xfmrcode(dss_obj["prop_order"], "$(fr_key)$(key_suffix)") + if !haskey(eng_obj, to_key) + eng_obj[to_key] = Vector{Any}(missing, nrw) + end + eng_obj[to_key][w] = defaults["$(fr_key)s"][defaults["wdg$(key_suffix)"]] + end + end + end end - if haskey(dss_obj, "xlt") && nrw == 3 - eng_obj["xsc"] = _eng_obj["xsc"] - eng_obj["xsc"][3] = defaults["xlt"] / 100 + # mintap, maxtap + for (fr_key, to_key) in zip(["mintap", "maxtap"], ["tm_min", "tm_max"]) + if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, fr_key) && _is_after_xfmrcode(dss_obj["prop_order"], fr_key)) + eng_obj[to_key] = fill(fill(defaults[fr_key], nphases), nrw) + end end - if haskey(dss_obj, "leadlag") - eng_obj["leadlag"] = defaults["leadlag"] + # %noloadloss, %imag + for (fr_key, to_key) in zip(["%noloadloss", "%imag", "%rs"], ["noloadloss", "imag", "rs"]) + if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, fr_key) && _is_after_xfmrcode(dss_obj["prop_order"], fr_key)) + eng_obj[to_key] = defaults[fr_key] / 100 + end end - if !haskey(eng_obj, "configuration") - eng_obj["configuration"] = fill("wye", nrw) + # loss model (converted to SI units, referred to secondary) + if nrw == 2 + if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, "xhl") && _is_after_xfmrcode(dss_obj["prop_order"], "xhl")) + eng_obj["xsc"] = [defaults["xhl"]] / 100 + end + elseif nrw == 3 + for (w, key) in enumerate(["xhl", "xht", "xlt"]) + if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, key) && _is_after_xfmrcode(dss_obj["prop_order"], key)) + if !haskey(eng_obj, "xsc") + eng_obj["xsc"] = Vector{Any}(missing, 3) + end + eng_obj["xsc"][w] = defaults[key] / 100 + end + end end - if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + # tm_fix, tm_step don't appear in opendss + if isempty(defaults["xfmrcode"]) + eng_obj["tm_fix"] = [fill(1, nphases) for w in 1:nrw] + eng_obj["tm_step"] = fill(fill(1/32, nphases), nrw) + eng_obj["fixed"] = fill(1, nrw) end - _add_eng_obj!(data_eng, "xfmrcode", name, eng_obj) - end -end - - - -"Adds transformers to `data_eng` from `data_dss`" -function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) - _apply_like!(dss_obj, data_dss, "transformer") - defaults = _apply_ordered_properties(_create_transformer(name; _to_kwargs(dss_obj)...), dss_obj) + # always required + eng_obj["bus"] = Array{String, 1}(undef, nrw) + eng_obj["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) + eng_obj["polarity"] = fill(1, nrw) - eng_obj = Dict{String, Any}() - # import certain properties from the xfmrcode; needed to check connectivity - if haskey(dss_obj, "xfmrcode") - xfmrcode = data_eng["xfmrcode"][dss_obj["xfmrcode"]] - @show xfmrcode - nphases = xfmrcode["phases"] - nrw = xfmrcode["windings"] - confs = xfmrcode["configuration"] - #TODO also tm_min/tm_max - else - # import defaults and save in eng_obj - nphases = defaults["phases"] - nrw = defaults["windings"] - dyz_map = Dict("wye"=>"wye", "delta"=>"delta", "ll"=>"delta", "ln"=>"wye") - eng_obj["configuration"] = confs = [dyz_map[x] for x in defaults["conns"]] - eng_obj["tm_min"] = fill(fill(defaults["mintap"], nphases), nrw) - eng_obj["tm_max"] = fill(fill(defaults["maxtap"], nphases), nrw) - eng_obj["vnom"] = [defaults["kvs"][w] for w in 1:nrw] - eng_obj["snom"] = [defaults["kvas"][w] for w in 1:nrw] - # loss model (converted to SI units, referred to secondary) - eng_obj["rs"] = [defaults["%rs"][w]/100 for w in 1:nrw] - eng_obj["noloadloss"] = defaults["%noloadloss"]/100 - eng_obj["imag"] = defaults["%imag"]/100 - if nrw==2 - eng_obj["xsc"] = [defaults["xhl"]]/100 - elseif nrw==3 - eng_obj["xsc"] = [defaults[x] for x in ["xhl", "xht", "xlt"]]/100 - end + if !haskey(dss_obj, "xfmrcode") + eng_obj["configuration"] = confs end # test if this transformer conforms with limitations if nphases<3 && "delta" in confs - Memento.error("Transformers with delta windings should have at least 3 phases to be well-defined: $name.") + Memento.error(_LOGGER, "Transformers with delta windings should have at least 3 phases to be well-defined: $name.") end if nrw>3 # All of the code is compatible with any number of windings, @@ -638,18 +642,6 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri Memento.error(_LOGGER, "For now parsing of xscarray is not supported. At most 3 windings are allowed, not $nrw.") end - if haskey(dss_obj, "taps") - eng_obj["tm"] = [fill(defaults["taps"][w], nphases) for w in 1:nrw] - else - eng_obj["tm"] = [fill(1.0, nphases) for w in 1:nrw] - end - eng_obj["fixed"] = [fill(true, nphases) for w in 1:nrw] - - eng_obj["bus"] = Array{String, 1}(undef, nrw) - eng_obj["connections"] = Array{Array{Int, 1}, 1}(undef, nrw) - eng_obj["polarity"] = fill(1, nrw) - eng_obj["leadlag"] = defaults["leadlag"] #TODO import from xfmrcode or not? yes I guess - for w in 1:nrw eng_obj["bus"][w] = _parse_busname(defaults["buses"][w])[1] @@ -666,7 +658,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri if w>1 prim_conf = confs[1] - if defaults["leadlag"] in ["ansi", "lag"] + if leadlag in ["ansi", "lag"] if prim_conf=="delta" && conf=="wye" eng_obj["polarity"][w] = -1 eng_obj["connections"][w] = [_barrel_roll(eng_obj["connections"][w][1:end-1], 1)..., eng_obj["connections"][w][end]] @@ -680,14 +672,12 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end end - eng_obj["source_id"] = "transformer.$(name)" - - if !isempty(defaults["bank"]) - eng_obj["bank"] = defaults["bank"] + for key in ["bank", "xfmrcode"] + if !isempty(defaults[key]) + eng_obj[key] = defaults[key] + end end - eng_obj = create_transformer(; Dict(Symbol.(keys(eng_obj)).=>values(eng_obj))...) - if !haskey(data_eng, "transformer") data_eng["transformer"] = Dict{String,Any}() end diff --git a/src/io/utils.jl b/src/io/utils.jl index de49bea28..2bbb62b98 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -41,11 +41,10 @@ _single_operators = Dict{String,Any}( const _array_delimiters = Vector{Char}(['\"', '\'', '[', '{', '(', ']', '}', ')']) "properties that should be excluded from being overwritten during the application of `like`" -const _like_exclusions = Dict{String,Vector{String}}( - "all" => ["name", "bus1", "bus2", "phases", "nphases", "enabled"], - "line" => ["switch"], - "transformer" => ["bank", "bus", "bus_2", "bus_3", "buses", "windings", "wdg", "wdg_2", "wdg_3"], - "linegeometry" => ["nconds"] +const _like_exclusions = Dict{String,Vector{Regex}}( + "all" => Vector{Regex}([r"name", r"enabled"]), + "line" => [r"switch"], + "transformer" => [] ) "" @@ -582,7 +581,7 @@ function _apply_like!(raw_dss::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, for prop in raw_dss["prop_order"] push!(new_prop_order, prop) - if prop in get(_like_exclusions, comp_type, []) || prop in _like_exclusions["all"] + if any(match(key, prop) !== nothing for key in [get(_like_exclusions, comp_type, [])..., _like_exclusions["all"]...]) continue end @@ -766,6 +765,12 @@ function _is_after_linecode(prop_order::Vector{String}, property::String)::Bool end +"checks to see if a property is after xfmrcode" +function _is_after_xfmrcode(prop_order::Vector{String}, property::String)::Bool + return _is_after(prop_order, property, "xfmrcode") +end + + "checks to see if property1 is after property2 in the prop_order" function _is_after(prop_order::Vector{String}, property1::String, property2::String)::Bool property1_idx = 0 diff --git a/test/data/opendss/case3_balanced_prop-order.dss b/test/data/opendss/case3_balanced_prop-order.dss index e08fab186..4d27068bc 100644 --- a/test/data/opendss/case3_balanced_prop-order.dss +++ b/test/data/opendss/case3_balanced_prop-order.dss @@ -19,7 +19,7 @@ New linecode.4/0QUAD nphases=3 basefreq=50 ! ohms per 100ft !Define lines New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM rmatrix=( 0.1000 | 0.0400 0.1000 | 0.0400 0.0400 0.1000) length=1 ! 5 mile line -New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 like=OHLine linecode = 4/0QUAD length=1 ! 100 ft +New Line.Quad like=OHLine Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase diff --git a/test/data/opendss/test2_master.dss b/test/data/opendss/test2_master.dss index 7e432fba2..18299a754 100644 --- a/test/data/opendss/test2_master.dss +++ b/test/data/opendss/test2_master.dss @@ -48,7 +48,7 @@ New Transformer.t4 phases=3 windings=2 buses=(b8, b9.1.2.3.0) rneut=0 xneut=0 ~ xhl=20.5 sub=y subname=t4_sub ~ wdg=1 %r=0.75000 ~ wdg=2 %r=0.75000 -new transformer.t5 buses=(b3-1, b5) like=t4 +new transformer.t5 like=t4 buses=(b3-1, b5) ! Generators New Generator.g1 Bus1=b1 kV= 150 kW=1 Model=3 Vpu=1.05 Maxkvar=70000 Minkvar=10000 ! kvar=50000 diff --git a/test/opendss.jl b/test/opendss.jl index 1b1bf6c68..8a850e46b 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -267,7 +267,7 @@ @test dss1 != dss2 @test all(a == b for (a, b) in zip(dss2["line"]["ohline"]["prop_order"],["name", "bus1", "bus2", "linecode", "rmatrix", "length"])) - @test all(a == b for (a, b) in zip(dss2["line"]["quad"]["prop_order"],["name", "bus1", "bus2", "like", "linecode", "length"])) + @test all(a == b for (a, b) in zip(dss2["line"]["quad"]["prop_order"],["name", "like", "bus1", "bus2", "linecode", "length"])) end @testset "opendss parse verify mvasc3/mvasc1 circuit parse" begin From 64930bca7655da12785ede95b4134030294f46d4 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 15 Apr 2020 15:11:39 -0600 Subject: [PATCH 122/224] REF: add types to function args in units.jl for debugging --- src/data_model/units.jl | 47 +++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 28bf4bf53..be0c764d3 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -33,7 +33,7 @@ end "finds voltage zones" -function _find_zones(data_model) +function _find_zones(data_model::Dict{String,<:Any}) unused_line_ids = Set(keys(data_model["branch"])) bus_lines = Dict([(id,Set()) for id in keys(data_model["bus"])]) for (line_id,line) in data_model["branch"] @@ -67,7 +67,7 @@ end "calculates voltage bases for each voltage zone" -function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) +function _calc_vbase(data_model::Dict{String,<:Any}, vbase_sources::Dict{String,<:Real}) # find zones of buses connected by lines zones = _find_zones(data_model) bus_to_zone = Dict([(bus,zone) for (zone, buses) in zones for bus in buses]) @@ -76,7 +76,7 @@ function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) zone_vbase = Dict{Int, Union{Missing,Real}}([(zone,missing) for zone in keys(zones)]) for (bus,vbase) in vbase_sources if !ismissing(zone_vbase[bus_to_zone[bus]]) - Memento.warn("You supplied multiple voltage bases for the same zone; ignoring all but the last one.") + Memento.warn(_LOGGER, "You supplied multiple voltage bases for the same zone; ignoring all but the last one.") end zone_vbase[bus_to_zone[bus]] = vbase end @@ -84,11 +84,11 @@ function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) # transformers form the edges between these zones zone_edges = Dict([(zone,[]) for zone in keys(zones)]) edges = Set() - for (i,(_,trans)) in enumerate(data_model["transformer"]) + for (i,(_,transformer)) in enumerate(get(data_model, "transformer", Dict{Any,Dict{String,Any}}())) push!(edges,i) - f_zone = bus_to_zone[string(trans["f_bus"])] - t_zone = bus_to_zone[string(trans["t_bus"])] - tm_nom = trans["configuration"]=="delta" ? trans["tm_nom"]/sqrt(3) : trans["tm_nom"] + f_zone = bus_to_zone[string(transformer["f_bus"])] + t_zone = bus_to_zone[string(transformer["t_bus"])] + tm_nom = transformer["configuration"]=="delta" ? transformer["tm_nom"]/sqrt(3) : transformer["tm_nom"] push!(zone_edges[f_zone], (i, t_zone, 1/tm_nom)) push!(zone_edges[t_zone], (i, f_zone, tm_nom)) end @@ -97,7 +97,6 @@ function _calc_vbase(data_model, vbase_sources::Dict{String,<:Real}) stack = [zone for (zone,vbase) in zone_vbase if !ismissing(vbase)] while !isempty(stack) - zone = pop!(stack) for (edge_id, zone_to, scale) in zone_edges[zone] @@ -117,9 +116,7 @@ end "converts to per unit from SI" -function _make_math_per_unit!(data_model; settings=missing, sbase=1.0, vbases=missing, v_var_scalar=missing) - - +function _make_math_per_unit!(data_model::Dict{String,<:Any}; settings::Union{Missing,Dict{String,<:Any}}=missing, sbase::Union{Real,Missing}=1.0, vbases::Union{Dict{String,<:Real},Missing}=missing, v_var_scalar::Union{Missing,Real}=missing) if ismissing(sbase) if !ismissing(settings) && haskey(settings, "set_sbase") sbase = settings["set_sbase"] @@ -196,7 +193,7 @@ end "per-unit conversion for buses" -function _rebase_pu_bus!(bus, vbase, sbase, sbase_old, v_var_scalar) +function _rebase_pu_bus!(bus::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real) # if not in p.u., these are normalized with respect to vnom prop_vnom = ["vm", "vmax", "vmin", "vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] @@ -232,7 +229,7 @@ end "per-unit conversion for branches" -function _rebase_pu_branch!(branch, vbase, sbase, sbase_old, v_var_scalar) +function _rebase_pu_branch!(branch::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real) if !haskey(branch, "vbase") z_old = 1 else @@ -255,7 +252,7 @@ end "per-unit conversion for shunts" -function _rebase_pu_shunt!(shunt, vbase, sbase, sbase_old, v_var_scalar) +function _rebase_pu_shunt!(shunt::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real) if !haskey(shunt, "vbase") z_old = 1 else @@ -276,7 +273,7 @@ end "per-unit conversion for loads" -function _rebase_pu_load!(load, vbase, sbase, sbase_old, v_var_scalar) +function _rebase_pu_load!(load::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real) if !haskey(load, "vbase") vbase_old = 1 sbase_old = 1 @@ -298,7 +295,7 @@ end "per-unit conversion for generators" -function _rebase_pu_generator!(gen, vbase, sbase, sbase_old, v_var_scalar, data_model) +function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real, data_model::Dict{String,<:Any}) vbase_old = get(gen, "vbase", 1.0/v_var_scalar) vbase_scale = vbase_old/vbase sbase_scale = sbase_old/sbase @@ -323,22 +320,22 @@ end "per-unit conversion for ideal 2-winding transformers" -function _rebase_pu_transformer_2w_ideal!(trans, f_vbase_new, t_vbase_new, sbase_old, sbase_new, v_var_scalar) - f_vbase_old = get(trans, "f_vbase", 1.0) - t_vbase_old = get(trans, "t_vbase", 1.0) +function _rebase_pu_transformer_2w_ideal!(transformer::Dict{String,<:Any}, f_vbase_new::Real, t_vbase_new::Real, sbase_old::Real, sbase_new::Real, v_var_scalar::Real) + f_vbase_old = get(transformer, "f_vbase", 1.0) + t_vbase_old = get(transformer, "t_vbase", 1.0) f_vbase_scale = f_vbase_old/f_vbase_new t_vbase_scale = t_vbase_old/t_vbase_new - scale(trans, "tm_nom", f_vbase_scale/t_vbase_scale) + scale(transformer, "tm_nom", f_vbase_scale/t_vbase_scale) # save new vbase - trans["f_vbase"] = f_vbase_new - trans["t_vbase"] = t_vbase_new + transformer["f_vbase"] = f_vbase_new + transformer["t_vbase"] = t_vbase_new end "helper function to apply a scale factor to given properties" -function _scale_props!(comp::Dict{String, Any}, prop_names::Array{String, 1}, scale::Real) +function _scale_props!(comp::Dict{String,<:Any}, prop_names::Vector{String}, scale::Real) for name in prop_names if haskey(comp, name) comp[name] *= scale @@ -348,7 +345,7 @@ end "" -function add_big_M!(data_model; kwargs...) +function add_big_M!(data_model::Dict{String,<:Any}; kwargs...) big_M = Dict{String, Any}() big_M["v_phase_pu_min"] = add_kwarg!(big_M, kwargs, :v_phase_pu_min, 0.5) @@ -361,7 +358,7 @@ end "" -function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true, convert_rad2deg=true) +function solution_make_si(solution::Dict{String,<:Any}, math_model::Dict{String,<:Any}; mult_sbase::Bool=true, mult_vbase::Bool=true, convert_rad2deg::Bool=true) solution_si = deepcopy(solution) sbase = math_model["sbase"] From 58fd1229a25ede97ec5fdd01f4bdb4693a07f5b8 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 15 Apr 2020 15:12:03 -0600 Subject: [PATCH 123/224] ADD: export optimizer_with_attributes from JuMP --- src/core/export.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/export.jl b/src/core/export.jl index dac52aab7..6b4b5a089 100644 --- a/src/core/export.jl +++ b/src/core/export.jl @@ -22,10 +22,15 @@ end # the follow items are also exported for user-friendlyness when calling # `using PowerModelsDistribution` -# so that users do not need to import JuMP to use a solver with PowerModelsDistribution +# so that users do not need to import JuMP to use a solver with PowerModels import JuMP: with_optimizer export with_optimizer +# so that users do not need to import JuMP to use a solver with PowerModels +# note does appear to be work with JuMP v0.20, but throws "could not import" warning +import JuMP: optimizer_with_attributes +export optimizer_with_attributes + import MathOptInterface: TerminationStatusCode export TerminationStatusCode From bb014ea65af2955448380efba2aec82455ecc833 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 15 Apr 2020 15:41:47 -0600 Subject: [PATCH 124/224] FIX: transformer banking using xfmrcodes --- src/data_model/utils.jl | 2 +- src/io/opendss.jl | 16 ++++++++- src/io/utils.jl | 15 ++++---- test/data/opendss/ut_trans_2w_yy_bank.dss | 43 +++++++++-------------- test/transformer.jl | 2 +- 5 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index fc0f57fa8..d40dc314b 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -477,7 +477,7 @@ function _apply_xfmrcode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:A for (k, v) in xfmrcode if !haskey(eng_obj, k) eng_obj[k] = v - elseif haskey(eng_obj, k) && k in ["vnom", "snom", "tm"] + elseif haskey(eng_obj, k) && k in ["vnom", "snom", "tm", "rs"] for (w, vw) in enumerate(eng_obj[k]) if ismissing(vw) eng_obj[k][w] = v[w] diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 2c6b08291..2ae8839fb 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -593,12 +593,26 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end # %noloadloss, %imag - for (fr_key, to_key) in zip(["%noloadloss", "%imag", "%rs"], ["noloadloss", "imag", "rs"]) + for (fr_key, to_key) in zip(["%noloadloss", "%imag"], ["noloadloss", "imag"]) if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, fr_key) && _is_after_xfmrcode(dss_obj["prop_order"], fr_key)) eng_obj[to_key] = defaults[fr_key] / 100 end end + # %rs + if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, "%rs") && _is_after_xfmrcode(dss_obj["prop_order"], "%rs")) || all(haskey(dss_obj, k) && _is_after_xfmrcode(dss_obj["prop_order"], k) for k in ["%r", "%r_2", "%r_3"]) + eng_obj["rs"] = defaults["%rs"] / 100 + else + for (w, key_suffix) in enumerate(["", "_2", "_3"]) + if haskey(dss_obj, "%r$(key_suffix)") && _is_after_xfmrcode(dss_obj["prop_order"], "%r$(key_suffix)") + if !haskey(eng_obj, "rs") + eng_obj["rs"] = Vector{Any}(missing, nrw) + end + eng_obj["rs"][w] = defaults["%rs"][defaults["wdg$(key_suffix)"]] / 100 + end + end + end + # loss model (converted to SI units, referred to secondary) if nrw == 2 if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, "xhl") && _is_after_xfmrcode(dss_obj["prop_order"], "xhl")) diff --git a/src/io/utils.jl b/src/io/utils.jl index 2bbb62b98..a088174f6 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -276,26 +276,32 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) bankable_transformers[bank] = ([], []) end push!(bankable_transformers[bank][1], id) - push!(bankable_transformers[bank][2], tr) + push!(bankable_transformers[bank][2], deepcopy(tr)) end end for (bank, (ids, trs)) in bankable_transformers + for tr in trs + _apply_xfmrcode!(tr, data_eng) + end # across-phase properties should be the same to be eligible for banking props = ["bus", "noloadloss", "xsc", "rs", "imag", "vnom", "snom", "polarity", "configuration"] btrans = Dict{String, Any}(prop=>trs[1][prop] for prop in props) if !all(tr[prop]==btrans[prop] for tr in trs, prop in props) + Memento.warn(_LOGGER, "Not all across-phase properties match among transfomers identified by bank='$bank', aborting attempt to bank") continue end nrw = length(btrans["bus"]) # only attempt to bank wye-connected transformers if !all(all(tr["configuration"].=="wye") for tr in trs) + Memento.warn(_LOGGER, "Not all configurations 'wye' on transformers identified by bank='$bank', aborting attempt to bank") continue end neutrals = [conns[end] for conns in trs[1]["connections"]] # ensure all windings have the same neutral if !all(all(conns[end]==neutrals[w] for (w, conns) in enumerate(tr["connections"])) for tr in trs) + Memento.warn(_LOGGER, "Not all neutral phases match on transfomers identified by bank='$bank', aborting attempt to bank") continue end @@ -321,12 +327,7 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) for id in ids delete!(data_eng["transformer"], id) end - btrans_name = bank - if haskey(data_eng["transformer"], bank) - Memento.warn("The bank name ($bank) is already used for another transformer; using the name of the first transformer $(ids[1]) in the bank instead.") - btrans_name = ids[1] - end - data_eng["transformer"][btrans_name] = btrans + data_eng["transformer"][bank] = btrans end end end diff --git a/test/data/opendss/ut_trans_2w_yy_bank.dss b/test/data/opendss/ut_trans_2w_yy_bank.dss index a2709360c..c9fc535b4 100644 --- a/test/data/opendss/ut_trans_2w_yy_bank.dss +++ b/test/data/opendss/ut_trans_2w_yy_bank.dss @@ -7,41 +7,32 @@ Set DefaultBaseFrequency=60 New circuit.ut_trans ~ BasekV=11 BaseMVA=1 pu=1.0 ISC3=9999999999 ISC1=9999999999 -! Transformers -New Transformer.TX1 windings=2 phases=1 Buses=[1.1 2.1] -~ Conns=[Wye Wye] -~ kVs=[11 4] -~ kVAs=[500 500] +new xfmrcode.tx phases=1 +~ windings=2 +~ conns=[wye wye] +~ kvs=[11 4] +~ kvas=[500 500] ~ xhl=5 +~ leadlag=lead ~ %noloadloss=5 ~ %imag=11 -~ leadlag=lead -~ taps=[1.05 0.95] -~ bank=TX1 ~ wdg=1 %r=1 ~ wdg=2 %r=2 -New Transformer.TX1b windings=2 phases=1 Buses=[1.2 2.2] -~ Conns=[Wye Wye] -~ kVs=[11 4] -~ kVAs=[500 500] -~ %Rs=[1 2] -~ xhl=5 -~ %noloadloss=5 -~ %imag=11 -~ leadlag=lead + +! Transformers +New Transformer.TX1a Buses=[1.1 2.1] +~ xfmrcode=tx +~ taps=[1.05 0.95] +~ bank=TX1 + +New Transformer.TX1b Buses=[1.2 2.2] +~ xfmrcode=tx ~ taps=[1.02 0.97] ~ bank=TX1 -New Transformer.TX1c windings=2 phases=1 Buses=[1.3 2.3] -~ Conns=[Wye Wye] -~ kVs=[11 4] -~ kVAs=[500 500] -~ %Rs=[1 2] -~ xhl=5 -~ %noloadloss=5 -~ %imag=11 -~ leadlag=lead +New Transformer.TX1c Buses=[1.3 2.3] +~ xfmrcode=tx ~ taps=[1.10 0.90] ~ bank=TX1 diff --git a/test/transformer.jl b/test/transformer.jl index 0124e0199..320436e60 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -74,7 +74,7 @@ # @test result1["solution"]["gen"] == result2["solution"]["gen"] # TODO need a new test, transformer model changed, use voltages on real bus dss = PMD.parse_dss(file) - trans = PMD._create_transformer(dss["transformer"]["tx1"]["name"]; PMD._to_kwargs(dss["transformer"]["tx1"])...) + trans = PMD._create_transformer(dss["transformer"]["tx1a"]["name"]; PMD._to_kwargs(dss["transformer"]["tx1"])...) @test all(trans["%rs"] .== [1.0, 2.0]) end From d1c6de07ba452b39a0d51ed56eff43878be53e2f Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 15 Apr 2020 16:12:11 -0600 Subject: [PATCH 125/224] FIX: transformer parsing bugs --- src/io/opendss.jl | 1 - src/io/utils.jl | 2 +- src/prob/common.jl | 4 ++-- test/transformer.jl | 12 ++++-------- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 2ae8839fb..c496ff907 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -506,7 +506,6 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, "noloadloss" => defaults["%noloadloss"] / 100, "imag" => defaults["%imag"] / 100, "xsc" => nrw == 2 ? [defaults["xhl"] / 100] : [defaults["xhl"], defaults["xht"], defaults["xlt"]] ./ 100, - "leadlag" => defaults["leadlag"], "source_id" => "xfmrcode.$name", ) diff --git a/src/io/utils.jl b/src/io/utils.jl index a088174f6..d2b5af02a 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -309,7 +309,7 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) # f_connections will be sorted from small to large f_phases_loc = Dict(hcat([[(c,(i,p)) for (p, c) in enumerate(tr["connections"][1][1:end-1])] for (i, tr) in enumerate(trs)]...)) locs = [f_phases_loc[x] for x in sort(collect(keys(f_phases_loc)))] - props_merge = ["connections", "tm", "tm_max", "tm_min", "fixed"] + props_merge = ["connections", "tm", "tm_max", "tm_min", "fixed", "tm_step", "tm_fix"] for prop in props_merge btrans[prop] = [[trs[i][prop][w][p] for (i,p) in locs] for w in 1:nrw] diff --git a/src/prob/common.jl b/src/prob/common.jl index b205ff410..34c891138 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -1,11 +1,11 @@ "alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" function run_mc_model(data::Dict{String,<:Any}, model_type, solver, build_mc; ref_extensions=[], kwargs...) if get(data, "data_model", "mathematical") == "engineering" - data_math = transform_data_model(data) + data_math = transform_data_model(data, make_pu=true) result = _PM.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) - result["solution"] = solution_math2eng(result["solution"], data_math; make_si=get(data, "per_unit", false), make_deg=get(data, "per_unit", false)) + result["solution"] = solution_math2eng(result["solution"], data_math; make_si=!get(data, "per_unit", false), make_deg=!get(data, "per_unit", false)) else result = _PM.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) end diff --git a/test/transformer.jl b/test/transformer.jl index 320436e60..db4130553 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -62,20 +62,16 @@ @testset "2w transformer ac pf yy - banked transformers" begin file = "../test/data/opendss/ut_trans_2w_yy_bank.dss" - pmd1 = PMD.parse_file(file) - pmd2 = PMD.parse_file(file; bank_transformers=false) - result1 = run_ac_mc_pf(pmd1, ipopt_solver) - result2 = run_ac_mc_pf(pmd2, ipopt_solver) + eng1 = PMD.parse_file(file; data_model="engineering") + eng2 = PMD.parse_file(file; bank_transformers=false, data_model="engineering") + result1 = run_ac_mc_pf(eng1, ipopt_solver) + result2 = run_ac_mc_pf(eng2, ipopt_solver) @test result1["termination_status"] == PMs.LOCALLY_SOLVED @test result2["termination_status"] == PMs.LOCALLY_SOLVED # @test result1["solution"]["bus"] == result2["solution"]["bus"] # TODO need a new test, transformer model changed, use voltages on real bus # @test result1["solution"]["gen"] == result2["solution"]["gen"] # TODO need a new test, transformer model changed, use voltages on real bus - - dss = PMD.parse_dss(file) - trans = PMD._create_transformer(dss["transformer"]["tx1a"]["name"]; PMD._to_kwargs(dss["transformer"]["tx1"])...) - @test all(trans["%rs"] .== [1.0, 2.0]) end @testset "three winding transformer pf" begin From 40506b07e2b808221ed4597ddbae6883ebee77fc Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 16 Apr 2020 09:34:44 -0600 Subject: [PATCH 126/224] FIX: discover_buses was missing islanded buses Buses could be defined by any dss object, but we were previously only checking over edge type objects (and missing a couple even) This change iterates over all known node and edge type dss components to find every bus. Also, the `like` property will copy even buses, which wasn't previously considered, so `_apply_like!` is now used during the bus discovery process. Adds unit tests Closes #260 --- src/io/dss_parse.jl | 11 +++++ src/io/utils.jl | 55 +++++++++++++++++++++--- test/data/opendss/test_bus_discovery.dss | 29 +++++++++++++ test/opendss.jl | 6 +++ 4 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 test/data/opendss/test_bus_discovery.dss diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 7968b1ea5..ecce21e7e 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -193,6 +193,10 @@ const _energymeter_properties = Vector{String}([ "3phaselosses", "vbaselosses", "basefreq", "enabled", "like" ]) +const _monitor_properties = Vector{String}([ + "element", "terminal", "mode", "action" +]) + const _pvsystem_properties = Vector{String}([ "phases", "bus1", "kv", "irradiance", "pmpp", "temperature", "pf", "conn", "kvar", "kva", "%cutin", "%cutout", "effcurve", "p-tcurve", "%r", @@ -257,6 +261,7 @@ const _dss_object_properties = Dict{String,Vector{String}}( "capcontrol" => _capacitor_properties, "regcontrol" => _regcontrol_properties, "energymeter" => _energymeter_properties, + "monitor" => _monitor_properties, "pvsystem" => _pvsystem_properties, "relay" => _relay_properties, "recloser" => _reactor_properties, @@ -624,10 +629,16 @@ given in order, but named properties can be given anywhere. function _parse_component(component::AbstractString, properties::AbstractString, object::Dict{String,<:Any}=Dict{String,Any}(); path::String="")::Dict{String,Any} obj_type, name = split(component, '.'; limit=2) + if !haskey(object, "prop_order") + object["prop_order"] = Vector{String}([]) + end + if !haskey(object, "name") object["name"] = string(name) end + push!(object["prop_order"], "name") + property_array = _parse_properties(properties) property_names = _dss_object_properties[obj_type] diff --git a/src/io/utils.jl b/src/io/utils.jl index d2b5af02a..a7f53c129 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -1,8 +1,30 @@ import Base.Iterators: flatten +"all node types that can help define buses" +const _dss_node_objects = Vector{String}([ + "isource", "load", "generator", "indmach012", "storage", "pvsystem" +]) "all edge types that can help define buses" -const _dss_edge_components = Vector{String}(["line", "transformer", "reactor", "capacitor"]) +const _dss_edge_objects = Vector{String}([ + "vsource", "fault", "capacitor", "line", "reactor", "transformer", "gictransformer", "gicline" +]) + +"all data holding objects" +const _dss_data_objects = Vector{String}([ + "options", "xfmrcode", "linecode", "loadshape", "xycurve", "linegeometry", + "linespacing", "growthshape", "tcc_curve", "cndata", "tsdata", "wiredata" +]) + +"all objects that define controls" +const _dss_control_objects = Vector{String}([ + "capcontrol", "regcontrol", "swtcontrol", "relay", "recloser", "fuse" +]) + +"all objects that provide montoring" +const _dss_monitor_objects = Vector{String}([ + "energymeter", "monitor" +]) "components currently supported for automatic data type parsing" const _dss_supported_components = Vector{String}([ @@ -490,16 +512,37 @@ end "Discovers all of the buses (not separately defined in OpenDSS), from \"lines\"" function _discover_buses(data_dss::Dict{String,<:Any})::Set buses = Set([]) - for obj_type in _dss_edge_components + for obj_type in _dss_node_objects for (name, dss_obj) in get(data_dss, obj_type, Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, obj_type) + push!(buses, split(dss_obj["bus1"], '.'; limit=2)[1]) + end + end + + for obj_type in _dss_edge_objects + for (name, dss_obj) in get(data_dss, obj_type, Dict{String,Any}()) + _apply_like!(dss_obj, data_dss, obj_type) if obj_type == "transformer" - dss_obj = _create_transformer(name; _to_kwargs(dss_obj)...) - for bus in dss_obj["buses"] + transformer = _create_transformer(name; _to_kwargs(dss_obj)...) + for bus in transformer["buses"] push!(buses, split(bus, '.'; limit=2)[1]) end - elseif haskey(dss_obj, "bus2") + elseif obj_type == "gictransformer" + for key in ["bush", "busx", "busnh", "busnx"] + if haskey(dss_obj, key) + push!(buses, split(dss_obj[key], '.'; limit=2)[1]) + end + end + elseif obj_type == "vsource" + push!(buses, split(get(dss_obj, "bus1", "sourcebus"), '.'; limit=2)[1]) + if haskey(dss_obj, "bus2") + push!(buses, split(dss_obj["bus2"], '.'; limit=2)[1]) + end + else for key in ["bus1", "bus2"] - push!(buses, split(dss_obj[key], '.'; limit=2)[1]) + if haskey(dss_obj, key) + push!(buses, split(dss_obj[key], '.'; limit=2)[1]) + end end end end diff --git a/test/data/opendss/test_bus_discovery.dss b/test/data/opendss/test_bus_discovery.dss new file mode 100644 index 000000000..7908a74a5 --- /dev/null +++ b/test/data/opendss/test_bus_discovery.dss @@ -0,0 +1,29 @@ +new circuit.test_islands + +new line.1 bus1=1 bus2=2 + +new transformer.1 windings=3 buses=[3 4 5] + +new reactor.1 bus1=6 bus2=7 + +new capacitor.1 bus1=8 bus2=9 + +new gictransformer.1 bush=10 busx=11 + +new gicline.1 bus1=12 bus2=13 + +new vsource.1 bus1=14 bus2=15 + +new fault.1 bus1=16 bus2=17 + +new isource.1 bus1=18 + +new load.1 bus1=19 + +new generator.1 bus1=20 + +new indmach012.1 bus1=21 + +new storage.1 bus1=22 + +new pvsystem.1 bus1=23 diff --git a/test/opendss.jl b/test/opendss.jl index 8a850e46b..3196d4933 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -1,6 +1,12 @@ @info "running opendss parser tests" @testset "test opendss parser" begin + @testset "bus discovery parsing" begin + eng = PMD.parse_file("../test/data/opendss/test_bus_discovery.dss"; data_model="engineering") + + @test length(eng["bus"]) == 24 + @test all(k in keys(eng["bus"]) for k in [["$i" for i in 1:23]..., "sourcebus"]) + end @testset "loadshape parsing" begin dss = PMD.parse_dss("../test/data/opendss/loadshapes.dss") From ef26601c5520eb8be53b226ce028808f26299a77 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 16 Apr 2020 10:20:47 -0600 Subject: [PATCH 127/224] FIX: import_all for mathematial data model import_all dss dicts weren't being passed on during model transformation --- src/data_model/eng2math.jl | 30 +++++++++++++++--------------- src/io/opendss.jl | 4 ++++ test/opendss.jl | 9 +++++++-- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index bcd81053c..68a73010c 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -2,21 +2,21 @@ import LinearAlgebra: diagm const _1to1_maps = Dict{String,Vector{String}}( - "bus" => ["vm", "va", "vmin", "vmax"], - "load" => ["model", "configuration", "status", "source_id"], - "shunt_capacitor" => ["status", "source_id"], - "series_capacitor" => [], - "shunt" => ["status", "source_id"], - "shunt_reactor" => ["status", "source_id"], - "generator" => ["source_id", "configuration"], - "solar" => ["source_id", "configuration"], - "storage" => ["status", "source_id"], - "line" => ["source_id"], - "line_reactor" => ["source_id"], - "switch" => ["source_id", "state", "status"], - "line_reactor" => ["source_id"], - "transformer" => ["source_id"], - "voltage_source" => ["source_id"], + "bus" => ["vm", "va", "vmin", "vmax", "dss"], + "load" => ["model", "configuration", "status", "source_id", "dss"], + "shunt_capacitor" => ["status", "source_id", "dss"], + "series_capacitor" => ["source_id", "dss"], + "shunt" => ["status", "source_id", "dss"], + "shunt_reactor" => ["status", "source_id", "dss"], + "generator" => ["source_id", "configuration", "dss"], + "solar" => ["source_id", "configuration", "dss", "dss"], + "storage" => ["status", "source_id", "dss"], + "line" => ["source_id", "dss"], + "line_reactor" => ["source_id", "dss"], + "switch" => ["source_id", "state", "status", "dss"], + "line_reactor" => ["source_id", "dss"], + "transformer" => ["source_id", "dss"], + "voltage_source" => ["source_id", "dss"], ) const _node_elements = ["load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource"] diff --git a/src/io/opendss.jl b/src/io/opendss.jl index c496ff907..c68dc6b7d 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -415,6 +415,10 @@ function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["g_fr"] = fill(0.0, nphases, nphases) eng_obj["g_to"] = fill(0.0, nphases, nphases) + if import_all + _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + end + _add_eng_obj!(data_eng, "linecode", name, eng_obj) end end diff --git a/test/opendss.jl b/test/opendss.jl index 3196d4933..2b8eaaf21 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -110,8 +110,8 @@ Memento.setlevel!(TESTLOG, "error") end - eng = PMD.parse_file("../test/data/opendss/test2_master.dss"; data_model="engineering") - pmd = PMD.parse_file("../test/data/opendss/test2_master.dss") + eng = PMD.parse_file("../test/data/opendss/test2_master.dss"; data_model="engineering", import_all=true) + pmd = PMD.parse_file("../test/data/opendss/test2_master.dss"; data_model="mathematical", import_all=true) len = 0.013516796 rmatrix=PMD._parse_matrix(Float64, "[1.5000 |0.200000 1.50000 |0.250000 0.25000 2.00000 ]") @@ -122,6 +122,11 @@ @test all(haskey(bus, "lon") && haskey(bus, "lat") for bus in values(pmd["bus"]) if "bus_i" in 1:10) end + @testset "import_all parsing" begin + @test all(haskey(comp, "dss") && isa(comp["dss"], Dict) for (comp_type, comps) in eng if isa(comps, Dict) for (_,comp) in comps if isa(comp, Dict) && comp_type != "bus" && comp_type != "settings") + @test all(haskey(comp, "dss") && isa(comp["dss"], Dict) for (comp_type, comps) in pmd if isa(comps, Dict) for (id,comp) in comps if isa(comp, Dict) && comp_type != "bus" && comp_type != "settings" && comp_type != "map" && !startswith(comp["name"], "_virtual")) + end + @testset "opendss parse generic parser verification" begin dss = PMD.parse_dss("../test/data/opendss/test2_master.dss") From 2378557d77caf90512f2f56e2df6f0e6827f900b Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 16 Apr 2020 16:19:09 -0600 Subject: [PATCH 128/224] ADD: Types for function args, docstrings, cleanup RM: unsed util functions REF: parse_dss_with_dtypes! -> internal function WIP: Make naming consistent with WIP document --- src/data_model/checks.jl | 359 +++++++++++++++++++++++++------------ src/data_model/eng2math.jl | 138 ++++++++++++-- src/data_model/units.jl | 55 +++--- src/data_model/utils.jl | 133 ++++++-------- src/io/dss_parse.jl | 2 +- src/io/utils.jl | 64 +------ 6 files changed, 451 insertions(+), 300 deletions(-) diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index 7ce289c5a..7fc7f9bf9 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -21,130 +21,237 @@ const _eng_model_checks = Dict{Symbol,Symbol}( "Data types of accepted fields in the engineering data model" const _eng_model_dtypes = Dict{Symbol,Dict{Symbol,Type}}( - :linecode => Dict{Symbol,Type}( - :rs => Array{<:Real, 2}, - :xs => Array{<:Real, 2}, - :g_fr => Array{<:Real, 2}, - :g_to => Array{<:Real, 2}, - :b_fr => Array{<:Real, 2}, - :b_to => Array{<:Real, 2} + :bus => Dict{Symbol,Type}( + :status => Int, + :terminals => Vector{Any}, + :phases => Vector{Any}, + :neutral => Any, + :grounded => Vector{Any}, + :rg => Vector{<:Real}, + :xg => Vector{<:Real}, + :vm_pn_lb => Real, + :vm_pn_ub => Real, + :vm_pp_lb => Real, + :vm_pp_ub => Real, + :vm_lb => Vector{<:Real}, + :vm_ub => Vector{<:Real}, + :vm => Vector{<:Real}, + :va => Vector{<:Real}, ), :line => Dict{Symbol,Type}( :status => Int, - :f_bus => AbstractString, - :t_bus => AbstractString, - :f_connections => Vector{<:Int}, - :t_connections => Vector{<:Int}, - :linecode => AbstractString, + :f_bus => Any, + :t_bus => Any, + :f_connections => Vector{Any}, + :t_connections => Vector{Any}, + :linecode => String, :length => Real, - :c_rating =>Vector{<:Real}, - :s_rating =>Vector{<:Real}, - :angmin=>Vector{<:Real}, - :angmax=>Vector{<:Real}, - :rs => Array{<:Real, 2}, - :xs => Array{<:Real, 2}, - :g_fr => Array{<:Real, 2}, - :g_to => Array{<:Real, 2}, - :b_fr => Array{<:Real, 2}, - :b_to => Array{<:Real, 2}, + :cm_ub =>Vector{<:Real}, + :sm_ub =>Vector{<:Real}, + :vad_lb=>Vector{<:Real}, + :vad_ub=>Vector{<:Real}, + :rs => Matrix{<:Real}, + :xs => Matrix{<:Real}, + :g_fr => Matrix{<:Real}, + :g_to => Matrix{<:Real}, + :b_fr => Matrix{<:Real}, + :b_to => Matrix{<:Real}, ), - :bus => Dict{Symbol,Type}( + :transformer => Dict{Symbol,Type}( :status => Int, - :bus_type => Int, - :terminals => Array{<:Any}, - :phases => Array{<:Int}, - :neutral => Union{Int, Missing}, - :grounded => Array{<:Any}, - :rg => Array{<:Real}, - :xg => Array{<:Real}, - :vm_pn_min => Real, - :vm_pn_max => Real, - :vm_pp_min => Real, - :vm_pp_max => Real, - :vm_min => Array{<:Real, 1}, - :vm_max => Array{<:Real, 1}, - :vm_fix => Array{<:Real, 1}, - :va_fix => Array{<:Real, 1}, + :bus => Vector{Any}, + :connections => Vector{Any}, + :vnom => Vector{<:Real}, + :snom => Vector{<:Real}, + :configuration => Vector{String}, + :polarity => Vector{Int}, + :xsc => Vector{<:Real}, + :rs => Vector{<:Real}, + :noloadloss => Real, + :imag => Real, + :tm_fix => Vector{Union{Vector{Int},Int}}, + :tm => Vector{Union{Vector{<:Real},<:Real}}, + :tm_min => Vector{Union{Vector{<:Real},<:Real}}, + :tm_max => Vector{Union{Vector{<:Real},<:Real}}, + :tm_step => Vector{Union{Vector{<:Real},<:Real}}, + :tm_nom => Union{Vector{<:Real}, Real}, + :f_bus => Any, + :t_bus => Any, + :f_connections => Vector{Any}, + :t_connections => Vector{Any}, + :configuration => String, + :xfmrcode => String, ), - :load => Dict{Symbol,Type}( + :switch => Dict{Symbol,Type}( + :status => Int, + :f_bus => Any, + :t_bus => Any, + :f_connections => Vector{Any}, + :t_connections => Vector{Any}, + :cm_ub => Vector{<:Real}, + :sm_ub => Vector{<:Real}, + :rs => Matrix{<:Real}, + :xs => Matrix{<:Real}, + :g_fr => Matrix{<:Real}, + :b_fr => Matrix{<:Real}, + :g_to => Matrix{<:Real}, + :b_to => Matrix{<:Real}, + :state => Int, + ), + :fuse => Dict{Symbol,Type}( + :status => Int, + :f_bus => Any, + :t_bus => Any, + :f_connections => Vector{Any}, + :t_connections => Vector{Any}, + :cm_ub => Vector{<:Real}, + :sm_ub => Vector{<:Real}, + :rs => Matrix{<:Real}, + :xs => Matrix{<:Real}, + :g_fr => Matrix{<:Real}, + :b_fr => Matrix{<:Real}, + :g_to => Matrix{<:Real}, + :b_to => Matrix{<:Real}, + :state => Int, + :fuse_curve => String, + :minimum_melting_curve => String, + ), + :line_reactor => Dict{Symbol,Type}(), + :series_capacitor => Dict{Symbol,Type}(), + :shunt => Dict{Symbol,Type}( :status => Int, :bus => Any, - :connections => Vector, + :connections => Vector{Any}, :configuration => String, - :model => String, - :pd => Array{<:Real, 1}, - :qd => Array{<:Real, 1}, - :pd_ref => Array{<:Real, 1}, - :qd_ref => Array{<:Real, 1}, + :gs => Matrix{<:Real}, + :bs => Matrix{<:Real}, :vnom => Real, - :alpha => Array{<:Real, 1}, - :beta => Array{<:Real, 1}, ), - :generator => Dict{Symbol,Type}( + :shunt_capacitor => Dict{Symbol,Type}( :status => Int, :bus => Any, - :connections => Vector, + :connections => Vector{Any}, :configuration => String, - :cost => Vector{<:Real}, - :pg => Array{<:Real, 1}, - :qg => Array{<:Real, 1}, - :pg_min => Array{<:Real, 1}, - :pg_max => Array{<:Real, 1}, - :qg_min => Array{<:Real, 1}, - :qg_max => Array{<:Real, 1}, + :bs => Matrix{<:Real}, + :vnom => Real, ), - :transformer => Dict{Symbol,Type}( + :shunt_reactor => Dict{Symbol,Type}( :status => Int, - :bus => Array{<:AbstractString, 1}, - :connections => Vector, - :vnom => Array{<:Real, 1}, - :snom => Array{<:Real, 1}, - :configuration => Array{String, 1}, - :polarity => Array{Bool, 1}, - :xsc => Array{<:Real, 1}, - :rs => Array{<:Real, 1}, - :noloadloss => Real, - :imag => Real, - :tm_fix => Array{Array{Bool, 1}, 1}, - :tm => Array{<:Array{<:Real, 1}, 1}, - :tm_min => Array{<:Array{<:Real, 1}, 1}, - :tm_max => Array{<:Array{<:Real, 1}, 1}, - :tm_step => Array{<:Array{<:Real, 1}, 1}, + :bus => Any, + :connections => Vector{Any}, + :configuration => String, + :bs => Matrix{<:Real}, + :vnom => Real, ), - :shunt_capacitor => Dict{Symbol,Type}( + :load => Dict{Symbol,Type}( :status => Int, :bus => Any, - :connections => Vector, + :connections => Vector{Any}, :configuration => String, - :qd_ref => Array{<:Real, 1}, + :model => String, + :pd_nom => Vector{<:Real}, + :qd_nom => Vector{<:Real}, :vnom => Real, + :pd_exp => Real, + :qd_exp => Real, + :pd_nom_z => Real, + :pd_nom_i => Real, + :pd_nom_p => Real, + :qd_nom_z => Real, + :qd_nom_i => Real, + :qd_nom_p => Real, ), - :shunt => Dict{Symbol,Type}( + :generator => Dict{Symbol,Type}( + :status => Int, + :bus => Any, + :connections => Vector{Any}, + :configuration => String, + :model => Int, + :pg => Vector{<:Real}, + :qg => Vector{<:Real}, + :pg_lb => Vector{<:Real}, + :pg_ub => Vector{<:Real}, + :qg_lb => Vector{<:Real}, + :qg_ub => Vector{<:Real}, + :cost_pg_parameters => Vector{<:Real}, + :cost_pg_model => Int, + ), + :solar => Dict{Symbol,Type}( :status => Int, :bus => Any, - :connections => Vector, - :g_sh => Array{<:Real, 2}, - :b_sh => Array{<:Real, 2}, + :connections => Vector{Any}, + :configuration => String, + :pg => Vector{<:Real}, + :qg => Vector{<:Real}, + :pg_lb => Vector{<:Real}, + :pg_ub => Vector{<:Real}, + :qg_lb => Vector{<:Real}, + :qg_ub => Vector{<:Real}, + :cost_pg_parameters => Vector{<:Real}, + :cost_pg_model => Int, + ), + :storage => Dict{Symbol,Type}( + :status => Int, + :bus => Any, + :connections => Vector{Any}, + :configuration => String, + :energy => Real, + :energy_ub => Real, + :charge_ub => Real, + :sm_ub => Vector{<:Real}, + :cm_ub => Vector{<:Real}, + :charge_efficiency => Real, + :discharge_efficiency => Real, + :qs_lb => Vector{<:Real}, + :qs_ub => Vector{<:Real}, + :rs => Vector{<:Real}, + :xs => Vector{<:Real}, + :pex => Real, + :qex => Real, ), :voltage_source => Dict{Symbol,Type}( :status => Int, :bus => Any, - :connections => Vector, - :vm =>Array{<:Real}, - :va =>Array{<:Real}, - :pg_max =>Array{<:Real}, - :pg_min =>Array{<:Real}, - :qg_max =>Array{<:Real}, - :qg_min =>Array{<:Real}, + :connections => Vector{Any}, + :configuration => String, + :vm => Vector{<:Real}, + :va => Real, + :rs => Matrix{<:Real}, + :xs => Matrix{<:Real}, + ), + :linecode => Dict{Symbol,Type}( + :rs => Matrix{<:Real}, + :xs => Matrix{<:Real}, + :g_fr => Matrix{<:Real}, + :g_to => Matrix{<:Real}, + :b_fr => Matrix{<:Real}, + :b_to => Matrix{<:Real} + ), + :xfmrcode => Dict{Symbol,Type}( + :configurations => Vector{String}, + :xsc => Vector{<:Real}, + :rs => Vector{<:Real}, + :tm_nom => Vector{<:Real}, + :tm_ub => Vector{<:Real}, + :tm_lb => Vector{<:Real}, + :tm_step => Vector{<:Real}, + :tm_set => Vector{<:Real}, + :tm_fix => Vector{<:Real}, + ), + :curve => Dict{Symbol,Type}( + :curve => Function, + ), + :time_series => Dict{Symbol,Type}( + :time => Vector{<:Real}, + :values => Vector{<:Real}, + :replace => Bool, ), - :solar => Dict{Symbol,Type}(), - :storage => Dict{Symbol,Type}(), - :switch => Dict{Symbol,Type}(), :grounding => Dict{Symbol,Type}( :bus => Any, :rg => Real, :xg => Real, ), + # Future Components # :ev => Dict{Symbol,Type}(), # :wind => Dict{Symbol,Type}(), # :autotransformer => Dict{Symbol,Type}(), @@ -157,53 +264,67 @@ const _eng_model_dtypes = Dict{Symbol,Dict{Symbol,Type}}( "required fields in the engineering data model" const _eng_model_req_fields= Dict{Symbol,Vector{Symbol}}( :bus => Vector{Symbol}([ - :status, :terminals, :grounded, :rg, :xg - ]), - :linecode => Vector{Symbol}([ - :rs, :xs, :g_fr, :g_to, :b_fr, :b_to - ]), - :xfmrcode => Vector{Symbol}([ - :vnom, :snom, :configuration, :polarity, :xsc, :rs, :noloadloss, :imag, - :tm_fix, :tm, :tm_min, :tm_max, :tm_step + :status, :terminals, :grounded, :rg, :xg, ]), :line => Vector{Symbol}([ - :status, :f_bus, :f_connections, :t_bus, :t_connections, :linecode, - :length + :status, :f_bus, :t_bus, :f_connections, :t_connections, :length, ]), :transformer => Vector{Symbol}([ - :status, :bus, :connections, :vnom, :snom, :configuration, :polarity, - :xsc, :rs, :noloadloss, :imag, :tm_fix, :tm, :tm_min, :tm_max, - :tm_step + :status, :configurations, :vnom, :snom, :polarity, :xsc, :rs, + :noloadloss, :imag, :tm_fix, :tm_set, :tm_step, ]), - :switch => Vector{Symbol}([]), - :line_reactor => Vector{Symbol}([]), - :series_capacitor => Vector{Symbol}([]), - :load => Vector{Symbol}([ - :status, :bus, :connections, :configuration + :switch => Vector{Symbol}([ + :status, :f_bus, :t_bus, :f_connections, :t_connections, ]), - :shunt_capacitor => Vector{Symbol}([ - :status, :bus, :connections, :configuration, :qd_ref, :vnom + :fuse => Vector{Symbol}([ + :status, :f_bus, :t_bus, :f_connections, :t_connections, + ]), + :line_reactor => Vector{Symbol}([ + :status, :f_bus, :t_bus, :f_connections, :t_connections, + ]), + :series_capacitor => Vector{Symbol}([ + :status, :f_bus, :t_bus, :f_connections, :t_connections, ]), - :shunt_reactor => Vector{Symbol}([]), :shunt => Vector{Symbol}([ - :status, :bus, :connections, :g_sh, :b_sh + :status, :bus, :connections, :configuration, :gs, :gs, :vnom, + ]), + :shunt_capacitor => Vector{Symbol}([ + :status, :bus, :connections, :configuration, :bs, :vnom, + ]), + :shunt_reactor => Vector{Symbol}([ + :status, :bus, :connections, :configuration, :bs, :vnom, + ]), + :load => Vector{Symbol}([ + :status, :bus, :connections, :configuration, :model, :pd_nom, :qd_nom, + :vnom, ]), :generator => Vector{Symbol}([ - :status, :bus, :connections + :status, :bus, :connections, :configuration, :model, + ]), + :solar => Vector{Symbol}([ + :status, :bus, :connections, :configuration, + ]), + :storage => Vector{Symbol}([ + :status, :bus, :connections, :configuration, :energy, + :charge_efficiency, :discharge_efficiency, :rs, :xs, :pex, :qex, ]), :voltage_source => Vector{Symbol}([ - :status, :bus, :connections, :vm, :va - ]), - :solar => Vector{Symbol}([]), - :storage => Vector{Symbol}([]), + :status, :bus, :connections, :configuration, :vm, :va, + ]), + :linecode => Vector{Symbol}([ + :rs, :xs, :g_fr, :g_to, :b_fr, :b_to, :cm_ub, + ]), + :xfmrcode => Vector{Symbol}([ + :status, :vnom, :snom, :xsc, :rs, :noloadloss, :imag, :tm_fix, :tm, + :tm_min, :tm_max, :tm_step, + ]), :grounding => Vector{Symbol}([]), + # Future Components # :ev => Vector{Symbol}([]), # :wind => Vector{Symbol}([]), # :autotransformer => Vector{Symbol}([]), - # :zip_load => Vector{Symbol}([]), - # :synchronous_generator => Vector{Symbol}([]), - # :boundary => Vector{Symbol}([]), + # :synchronous_generator => Vector{Symbol}([]), # same as generator? # :meter => Vector{Symbol}([]) ) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 68a73010c..380509c47 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -1,8 +1,8 @@ import LinearAlgebra: diagm - +"items that are mapped one-to-one from engineering to math models" const _1to1_maps = Dict{String,Vector{String}}( - "bus" => ["vm", "va", "vmin", "vmax", "dss"], + "bus" => ["vm", "va", "dss"], "load" => ["model", "configuration", "status", "source_id", "dss"], "shunt_capacitor" => ["status", "source_id", "dss"], "series_capacitor" => ["source_id", "dss"], @@ -19,12 +19,18 @@ const _1to1_maps = Dict{String,Vector{String}}( "voltage_source" => ["source_id", "dss"], ) -const _node_elements = ["load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource"] +"list of nodal type elements in the engineering model" +const _node_elements = Vector{String}([ + "load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource" +]) -const _edge_elements = ["line", "switch", "transformer", "line_reactor", "series_capacitor"] +"list of edge type elements in the engineering model" +const _edge_elements = Vector{String}([ + "line", "switch", "transformer", "line_reactor", "series_capacitor" +]) -"" +"base function for converting engineering model to mathematical model" function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, adjust_bounds::Bool=true) @assert get(data_eng, "data_model", "mathematical") == "engineering" @@ -75,7 +81,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, end -"" +"initializes the base math object of any type, and copies any one-to-one mappings" function _init_math_obj(obj_type::String, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} math_obj = Dict{String,Any}() @@ -91,7 +97,7 @@ function _init_math_obj(obj_type::String, eng_obj::Dict{String,<:Any}, index::In end -"" +"initializes the base components that are expected by powermodelsdistribution in the mathematical model" function _init_base_components!(data_math::Dict{String,<:Any}) for key in ["bus", "load", "shunt", "gen", "branch", "switch", "transformer", "storage", "dcline"] if !haskey(data_math, key) @@ -101,10 +107,8 @@ function _init_base_components!(data_math::Dict{String,<:Any}) end -"" +"Initializes the lookup table" function _init_lookup!(data_math::Dict{String,<:Any}) - - for key in keys(_1to1_maps) if !haskey(data_math["lookup"], key) data_math["lookup"][key] = Dict{Any,Int}() @@ -113,10 +117,9 @@ function _init_lookup!(data_math::Dict{String,<:Any}) end -"" +"converts engineering bus components into mathematical bus components" function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) - # TODO fix vnom phases = get(eng_obj, "phases", [1, 2, 3]) neutral = get(eng_obj, "neutral", 4) terminals = eng_obj["terminals"] @@ -138,8 +141,8 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, math_obj["va"] = eng_obj["va"] end - math_obj["vmin"] = fill(0.0, length(terminals)) - math_obj["vmax"] = fill(Inf, length(terminals)) + math_obj["vmin"] = get(eng_obj, "vm_lb", fill(0.0, length(terminals))) + math_obj["vmax"] = get(eng_obj, "vm_ub", fill(Inf, length(terminals))) math_obj["base_kv"] = data_eng["settings"]["vbase"] @@ -165,11 +168,25 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, :to => "bus.$(math_obj["index"])", :unmap_function => :_map_math2eng_bus!, ) + + # time series + for (fr, to) in zip(["status", "vm", "va", "vm_lb", "vm_ub"], ["bus_type", "vm", "va", "vmin", "vmax"]) + if haskey(eng_obj, "$(fr)_time_series") + if to == "bus_type" + time_series = deepcopy(data_eng["time_series"][eng_obj["$(fr)_time_series"]]) + time_series = [status == 1 ? 1 : 4 for status in time_series["values"]] + @assert time_series["replace"] + else + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + end + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "bus", "$(math_obj["index"])", to) + end + end end end -"" +"converts engineering load components into mathematical load components" function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("load", eng_obj, length(data_math["load"])+1) @@ -180,8 +197,6 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["status"] = eng_obj["status"] - math_obj["pd"] = eng_obj["pd"] math_obj["qd"] = eng_obj["qd"] @@ -207,6 +222,14 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any :to => "load.$(math_obj["index"])", :unmap_function => :_map_math2eng_load!, ) + + # time series + for (fr, to) in zip(["status", "pd", "qd"], ["status", "pd", "qd"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "load", "$(math_obj["index"])", to) + end + end end end @@ -216,7 +239,6 @@ function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng: for (name, eng_obj) in get(data_eng, "shunt_capacitor", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("shunt_capacitor", eng_obj, length(data_math["shunt"])+1) - # TODO change to new capacitor shunt calc logic math_obj["name"] = name math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] @@ -254,6 +276,15 @@ function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng: :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_shunt_capacitor!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) + end + end end end @@ -288,6 +319,14 @@ function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_capacitor!, ) + + # time series + for (fr, to) in zip(["status", "g_sh", "b_sh"], ["status", "gs", "bs"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) + end + end end end @@ -324,6 +363,15 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D :to => "shunt.$(math_obj["index"])", :unmap_function => :_map_math2eng_shunt_reactor!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) + end + end end end @@ -378,6 +426,15 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_generator!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "gen", "$(math_obj["index"])", to) + end + end end end @@ -424,6 +481,15 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_solar!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "gen", "$(math_obj["index"])", to) + end + end end end @@ -467,6 +533,15 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: :to => "storage.$(math_obj["index"])", :unmap_function => :_map_math2eng_storage!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "storage", "$(math_obj["index"])", to) + end + end end end @@ -529,6 +604,15 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any :to => "branch.$(math_obj["index"])", :unmap_function => :_map_math2eng_line!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) + end + end end end @@ -590,6 +674,15 @@ function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Di :to => "branch.$(math_obj["index"])", :unmap_function => :_map_math2eng_line_reactor!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) + end + end end end @@ -615,6 +708,15 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A :to => "switch.$(math_obj["index"])", :unmap_function => :_map_math2eng_switch!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "switch", "$(math_obj["index"])", to) + end + end else # build virtual bus f_bus = data_math["bus"]["$(data_math["bus_lookup"][eng_obj["f_bus"]])"] diff --git a/src/data_model/units.jl b/src/data_model/units.jl index be0c764d3..2092c4083 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -1,8 +1,18 @@ -const _dimensionalize_math = Dict( - "bus" => Dict("rad2deg"=>["va"], "vbase"=>["vm", "vr", "vi"]), - "gen" => Dict("sbase"=>["pg", "qg", "pg_bus", "qg_bus"]), - "load" => Dict("sbase"=>["pd", "qd", "pd_bus", "qd_bus"]), - "line" => Dict("sbase"=>["pf", "qf", "pt", "qt"]), +"lists of scaling factors and what they apply to" +const _dimensionalize_math = Dict{String,Dict{String,Vector{String}}}( + "bus" => Dict{String,Vector{String}}( + "rad2deg"=>Vector{String}(["va"]), + "vbase"=>Vector{String}(["vm", "vr", "vi"]) + ), + "gen" => Dict{String,Vector{String}}( + "sbase"=>Vector{String}(["pg", "qg", "pg_bus", "qg_bus"]) + ), + "load" => Dict{String,Vector{String}}( + "sbase"=>Vector{String}(["pd", "qd", "pd_bus", "qd_bus"]) + ), + "line" => Dict{String,Vector{String}}( + "sbase"=>Vector{String}(["pf", "qf", "pt", "qt"]) + ), ) @@ -218,7 +228,6 @@ function _rebase_pu_bus!(bus::Dict{String,<:Any}, vbase::Real, sbase::Real, sbas z_scale = z_old/z_new _scale_props!(bus, ["rg", "xg"], z_scale) - # TODO fix if haskey(bus ,"va") bus["va"] = deg2rad.(bus["va"]) end @@ -264,8 +273,8 @@ function _rebase_pu_shunt!(shunt::Dict{String,<:Any}, vbase::Real, sbase::Real, z_scale = z_old/z_new y_scale = 1/z_scale - scale(shunt, "gs", y_scale) - scale(shunt, "bs", y_scale) + _scale(shunt, "gs", y_scale) + _scale(shunt, "bs", y_scale) # save new vbase shunt["vbase"] = vbase @@ -283,11 +292,11 @@ function _rebase_pu_load!(load::Dict{String,<:Any}, vbase::Real, sbase::Real, sb vbase_old = get(load, "vbase", 1.0) vbase_scale = vbase_old/vbase - scale(load, "vnom_kv", vbase_scale) + _scale(load, "vnom_kv", vbase_scale) sbase_scale = sbase_old/sbase - scale(load, "pd", sbase_scale) - scale(load, "qd", sbase_scale) + _scale(load, "pd", sbase_scale) + _scale(load, "qd", sbase_scale) # save new vbase load["vbase"] = vbase @@ -301,7 +310,7 @@ function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real sbase_scale = sbase_old/sbase for key in ["pg", "qg", "pmin", "qmin", "pmax", "qmax"] - scale(gen, key, sbase_scale) + _scale(gen, key, sbase_scale) end # if not in per unit yet, the cost has is in $/MWh @@ -326,7 +335,7 @@ function _rebase_pu_transformer_2w_ideal!(transformer::Dict{String,<:Any}, f_vba f_vbase_scale = f_vbase_old/f_vbase_new t_vbase_scale = t_vbase_old/t_vbase_new - scale(transformer, "tm_nom", f_vbase_scale/t_vbase_scale) + _scale(transformer, "tm_nom", f_vbase_scale/t_vbase_scale) # save new vbase transformer["f_vbase"] = f_vbase_new @@ -344,21 +353,19 @@ function _scale_props!(comp::Dict{String,<:Any}, prop_names::Vector{String}, sca end -"" +"adds big M to data model" function add_big_M!(data_model::Dict{String,<:Any}; kwargs...) - big_M = Dict{String, Any}() - - big_M["v_phase_pu_min"] = add_kwarg!(big_M, kwargs, :v_phase_pu_min, 0.5) - big_M["v_phase_pu_max"] = add_kwarg!(big_M, kwargs, :v_phase_pu_max, 2.0) - big_M["v_neutral_pu_min"] = add_kwarg!(big_M, kwargs, :v_neutral_pu_min, 0.0) - big_M["v_neutral_pu_max"] = add_kwarg!(big_M, kwargs, :v_neutral_pu_max, 0.5) - - data_model["big_M"] = big_M + data_model["big_M"] = Dict{String, Any}( + "v_phase_pu_lb" => get(kwargs, :v_phase_pu_lb, 0.5), + "v_phase_pu_ub" => get(kwargs, :v_phase_pu_ub, 2.0), + "v_neutral_pu_lb" => get(kwargs, :v_neutral_pu_lb, 0.0), + "v_neutral_pu_ub" => get(kwargs, :v_neutral_pu_ub, 0.5) + ) end -"" -function solution_make_si(solution::Dict{String,<:Any}, math_model::Dict{String,<:Any}; mult_sbase::Bool=true, mult_vbase::Bool=true, convert_rad2deg::Bool=true) +"converts solution to si units" +function solution_make_si(solution::Dict{String,<:Any}, math_model::Dict{String,<:Any}; mult_sbase::Bool=true, mult_vbase::Bool=true, convert_rad2deg::Bool=true)::Dict{String,Any} solution_si = deepcopy(solution) sbase = math_model["sbase"] diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index d40dc314b..41158130f 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -1,83 +1,18 @@ -"" -function scale(dict, key, scale) +"function for applying a scale to a paramter" +function _scale(dict::Dict{String,<:Any}, key::String, scale::Real) if haskey(dict, key) dict[key] *= scale end end -"" -function _get_next_index(last_index, presets) - new_index = last_index+1 - while new_index in presets - new_index += 1 - end - return new_index -end - - -"" -function solution_identify!(solution, data_model; id_prop="id") - for comp_type in keys(solution) - if isa(solution[comp_type], Dict) - comp_dict = Dict{Any, Any}() - for (ind, comp) in solution[comp_type] - id = data_model[comp_type][ind][id_prop] - comp_dict[id] = comp - end - solution[comp_type] = comp_dict - end - end - - return solution -end - - -"" -function add_solution!(solution, comp_type, id, data) - if !haskey(solution, comp_type) - solution[comp_type] = Dict() - end - - if !haskey(solution[comp_type], id) - solution[comp_type][id] = Dict{String, Any}() - end - - for (key, prop) in data - solution[comp_type][id][key] = prop - end -end - - -"" -function delete_solution!(solution, comp_type, id, props) - if haskey(solution, comp_type) - if haskey(solution[comp_type], id) - for prop in props - delete!(solution[comp_type][id], prop) - end - end - end -end - - -"" -function delete_solution!(solution, comp_type, id) - if haskey(solution, comp_type) - if haskey(solution[comp_type], id) - delete!(solution[comp_type], id) - end - end -end - - -"" -function _get_new_ground(terminals) +"_get_ground helper function" +function _get_new_ground(terminals::Vector{<:Any}) if isa(terminals, Vector{Int}) return maximum(terminals)+1 else - nrs = [parse(Int, x[1]) for x in [match(r"n([1-9]{1}[0-9]*)", string(t)) for t in terminals] if x!=nothing] + nrs = [parse(Int, x[1]) for x in [match(r"n([1-9]{1}[0-9]*)", string(t)) for t in terminals] if x !== nothing] new = isempty(nrs) ? 1 : maximum(nrs)+1 if isa(terminals, Vector{Symbol}) return Symbol("g$new") @@ -88,7 +23,7 @@ function _get_new_ground(terminals) end -"" +"gets the grounding information for a bus" function _get_ground!(bus::Dict{String,<:Any}) # find perfect groundings (true ground) grounded_perfect = [] @@ -302,7 +237,7 @@ function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::Str end -"" +"performs kron reduction on branch" function _kron_reduce_branch!(object::Dict{String,<:Any}, Zs_keys::Vector{String}, Ys_keys::Vector{String}, terminals::Vector{Int}, neutral::Int)::Vector{Int} Zs = Vector{Matrix}([object[k] for k in Zs_keys]) Ys = Vector{Matrix}([object[k] for k in Ys_keys]) @@ -320,7 +255,13 @@ function _kron_reduce_branch!(object::Dict{String,<:Any}, Zs_keys::Vector{String end -"" +"get locations of terminal in connections list" +function _get_ilocs(vec::Vector{<:Any}, loc::Any)::Vector{Int} + return collect(1:length(vec))[vec.==loc] +end + + +"performs kron reduction on branch - helper function" function _kron_reduce_branch(Zs::Vector{Matrix}, Ys::Vector{Matrix}, terminals::Vector{Int}, neutral::Int)::Tuple{Vector{Matrix}, Vector{Matrix}, Vector{Int}} Zs_kr = Vector{Matrix}([deepcopy(Z) for Z in Zs]) Ys_kr = Vector{Matrix}([deepcopy(Y) for Y in Ys]) @@ -342,7 +283,7 @@ function _kron_reduce_branch(Zs::Vector{Matrix}, Ys::Vector{Matrix}, terminals:: end -"" +"pads properties to have the total number of conductors for the whole system" function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; pad_value::Real=0.0) @assert(all(c in phases for c in connections)) inds = _get_idxs(phases, connections) @@ -363,7 +304,7 @@ function _pad_properties!(object::Dict{<:Any,<:Any}, properties::Vector{String}, end -"" +"pads properties to have the total number of conductors for the whole system - delta connection variant" function _pad_properties_delta!(object::Dict{<:Any,<:Any}, properties::Vector{String}, connections::Vector{Int}, phases::Vector{Int}; invert::Bool=false) @assert(all(c in phases for c in connections)) @assert(length(connections) in [2, 3], "A delta configuration has to have at least 2 or 3 connections!") @@ -393,8 +334,8 @@ function _pad_properties_delta!(object::Dict{<:Any,<:Any}, properties::Vector{St end -"" -function _apply_filter!(obj, properties, filter) +"Filters out values of a vector or matrix for certain properties" +function _apply_filter!(obj::Dict{String,<:Any}, properties::Vector{String}, filter::Union{Array,BitArray}) for property in properties if haskey(obj, property) if isa(obj[property], Vector) @@ -414,7 +355,8 @@ Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the conductors 't_cnds', this method will return a list of conductors 'cnd' and a matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. """ -function calc_shunt(f_cnds::Vector{Int}, t_cnds::Vector{Int}, y::Vector{T})::Tuple{Vector{Int}, Matrix{T}} where T <: Number +function _calc_shunt(f_cnds::Vector{Int}, t_cnds::Vector{Int}, y)::Tuple{Vector{Int}, Matrix{Real}} + #TODO fix y::Type cnds = unique([f_cnds..., t_cnds...]) e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) @@ -429,7 +371,6 @@ method will calculate the reduced addmittance matrix if terminal 'ground' is grounded. """ function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{T}, ground::Int)::Tuple{Vector{Int}, Matrix{T}} where T <: Number - # TODO add types if ground in cnds cndsr = setdiff(cnds, ground) cndsr_inds = _get_idxs(cnds, cndsr) @@ -470,6 +411,7 @@ function _add_gen_cost_model!(math_obj::Dict{String,<:Any}, eng_obj::Dict{String end +"applies a xfmrcode to a transformer in preparation for converting to mathematical model" function _apply_xfmrcode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:Any}) if haskey(eng_obj, "xfmrcode") && haskey(data_eng, "xfmrcode") && haskey(data_eng["xfmrcode"], eng_obj["xfmrcode"]) xfmrcode = data_eng["xfmrcode"][eng_obj["xfmrcode"]] @@ -489,6 +431,7 @@ function _apply_xfmrcode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:A end +"applies a linecode to a line in preparation for converting to mathematical model" function _apply_linecode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:Any}) if haskey(eng_obj, "linecode") && haskey(data_eng, "linecode") && haskey(data_eng["linecode"], eng_obj["linecode"]) linecode = data_eng["linecode"][eng_obj["linecode"]] @@ -500,3 +443,35 @@ function _apply_linecode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:A end end end + + +"parses {}_times_series parameters into the expected InfrastructureModels format" +function _parse_time_series_parameter!(data_math::Dict{String,<:Any}, time_series::Dict{String,<:Any}, fr_parameter::Any, to_component_type::String, to_component_id::String, to_parameter::String) + if !haskey(data_math, "time_series") + data_math["time_series"] => Dict{String,Any}() + end + + if !haskey(data_math["time_series"], "time") + data_math["time_series"]["time_point"] = time_series["time"] + else + if time_series["time"] == data_math["time_series"]["time_point"] + Memento.warn(_LOGGER, "Time series data doesn't match between different objects, aborting") + end + end + + data_math["time_series"]["num_steps"] = length(time_series["time"]) + + if !haskey(data_math["time_series"], to_component_type) + data_math["time_series"][to_component_type] = Dict{String,Any}() + end + + if !haskey(data_math["time_series"][to_component_type], to_component_id) + data_math["time_series"][to_component_type][to_component_id] = Dict{String,Any}() + end + + if time_series["replace"] + data_math["time_series"][to_component_type][to_component_id][to_parameter] = time_series["values"] + else + data_math["time_series"][to_component_type][to_component_id][to_parameter] = fr_parameter .* time_series["values"] + end +end diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index ecce21e7e..9037875b0 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -902,7 +902,7 @@ function parse_dss(io::IOStream)::Dict{String,Any} end end - parse_dss_with_dtypes!(data_dss) + _parse_dss_with_dtypes!(data_dss) data_dss["data_model"] = "dss" diff --git a/src/io/utils.jl b/src/io/utils.jl index a7f53c129..411c7b469 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -66,10 +66,9 @@ const _array_delimiters = Vector{Char}(['\"', '\'', '[', '{', '(', ']', '}', ')' const _like_exclusions = Dict{String,Vector{Regex}}( "all" => Vector{Regex}([r"name", r"enabled"]), "line" => [r"switch"], - "transformer" => [] ) -"" +"Regexes for determining data types" const _dtype_regex = Dict{Regex, Type}( r"^[+-]{0,1}\d*\.{0,1}\d*[eE]{0,1}[+-]{0,1}\d*[+-]\d*\.{0,1}\d*[eE]{0,1}[+-]{0,1}\d*[ij]$" => ComplexF64, r"^[+-]{0,1}\d*\.{0,1}\d*[eE]{0,1}[+-]{0,1}\d*$" => Float64, @@ -460,7 +459,7 @@ end "Returns an ordered list of defined conductors. If ground=false, will omit any `0`" function _get_conductors_ordered(busname::AbstractString; default::Vector{Int}=Vector{Int}([]), check_length::Bool=true, pad_ground::Bool=false)::Vector{Int} parts = split(busname, '.'; limit=2) - ret = [] + ret = Vector{Int}([]) if length(parts)==2 conds_str = split(parts[2], '.') ret = [parse(Int, i) for i in conds_str] @@ -503,12 +502,6 @@ function _get_idxs(vec::Vector{<:Any}, els::Vector{<:Any})::Vector{Int} end -"" -function _get_ilocs(vec::Vector{<:Any}, loc::Any)::Vector{Int} - return collect(1:length(vec))[vec.==loc] -end - - "Discovers all of the buses (not separately defined in OpenDSS), from \"lines\"" function _discover_buses(data_dss::Dict{String,<:Any})::Set buses = Set([]) @@ -597,7 +590,7 @@ function _to_kwargs(data::Dict{String,Any})::Dict{Symbol,Any} end -"" +"apply properties in the order that they are given" function _apply_ordered_properties(defaults::Dict{String,<:Any}, raw_dss::Dict{String,<:Any}; code_dict::Dict{String,<:Any}=Dict{String,Any}())::Dict{String,Any} _defaults = deepcopy(defaults) @@ -665,12 +658,10 @@ end """ - parse_dss_with_dtypes!(data_dss, to_parse) - Parses the data in keys defined by `to_parse` in `data_dss` using types given by the default properties from the `get_prop_default` function. """ -function parse_dss_with_dtypes!(data_dss::Dict{String,<:Any}, to_parse::Vector{String}=_dss_supported_components) +function _parse_dss_with_dtypes!(data_dss::Dict{String,<:Any}, to_parse::Vector{String}=_dss_supported_components) for obj_type in to_parse if haskey(data_dss, obj_type) dtypes = _dss_parameter_data_types[obj_type] @@ -722,7 +713,7 @@ function _parse_element_with_dtype(dtype::Type, element::AbstractString) end -"" +"parses data type of properties of objects" function _parse_obj_dtypes!(obj_type::String, object::Dict{String,Any}, dtypes::Dict{String,Type}) for (k, v) in object if isa(v, Vector) && eltype(v) == Any || isa(eltype(v), AbstractString) @@ -748,51 +739,6 @@ function _parse_obj_dtypes!(obj_type::String, object::Dict{String,Any}, dtypes:: end -""" -Given a set of addmittances 'y' connected from the conductors 'f_cnds' to the -conductors 't_cnds', this method will return a list of conductors 'cnd' and a -matrix 'Y', which will satisfy I[cnds] = Y*V[cnds]. -""" -function _calc_shunt(f_cnds, t_cnds, y) - # TODO add types - cnds = unique([f_cnds..., t_cnds...]) - e(f,t) = reshape([c==f ? 1 : c==t ? -1 : 0 for c in cnds], length(cnds), 1) - Y = sum([e(f_cnds[i], t_cnds[i])*y[i]*e(f_cnds[i], t_cnds[i])' for i in 1:length(y)]) - return (cnds, Y) -end - - -""" -Given a set of terminals 'cnds' with associated shunt addmittance 'Y', this -method will calculate the reduced addmittance matrix if terminal 'ground' is -grounded. -""" -function _calc_ground_shunt_admittance_matrix(cnds::Vector{Int}, Y::Matrix{T}, ground::Int)::Tuple{Vector{Int}, Matrix{T}} where T - # TODO add types - if ground in cnds - cndsr = setdiff(cnds, ground) - cndsr_inds = _get_idxs(cnds, cndsr) - Yr = Y[cndsr_inds, cndsr_inds] - return (cndsr, Yr) - else - return cnds, Y - end -end - - -"" -function _rm_floating_cnd(cnds::Vector{Int}, Y::Matrix{T}, f::Int) where T - P = setdiff(cnds, f) - - f_inds = _get_idxs(cnds, [f]) - P_inds = _get_idxs(cnds, P) - - Yrm = Y[P_inds,P_inds]-(1/Y[f_inds,f_inds][1])*Y[P_inds,f_inds]*Y[f_inds,P_inds] - - return (P,Yrm) -end - - "" function _register_awaiting_ground!(bus::Dict{String,<:Any}, connections::Vector{Int}) if !haskey(bus, "awaiting_ground") From 96118a33f28d1d1bd0afb57f7b3e4e1716eb3ce8 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 17 Apr 2020 10:33:31 -0600 Subject: [PATCH 129/224] UPD: lossmodel building logic --- src/data_model/eng2math.jl | 169 ++++++++++++++++--------------------- src/io/common.jl | 18 ++-- 2 files changed, 82 insertions(+), 105 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 380509c47..d0c387061 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -31,7 +31,7 @@ const _edge_elements = Vector{String}([ "base function for converting engineering model to mathematical model" -function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, adjust_bounds::Bool=true) +function _map_eng2math(data_eng; kron_reduced::Bool=true) @assert get(data_eng, "data_model", "mathematical") == "engineering" data_math = Dict{String,Any}( @@ -61,22 +61,18 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) - _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) - _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) - _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) - if !adjust_bounds - # _find_new_bounds(data_math) # TODO - end - return data_math end @@ -377,7 +373,7 @@ end "" -function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) +function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) math_obj = _init_math_obj("generator", eng_obj, length(data_math["gen"])+1) @@ -440,7 +436,7 @@ end "" -function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) +function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("solar", eng_obj, length(data_math["gen"])+1) @@ -495,7 +491,7 @@ end "" -function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) +function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("storage", eng_obj, length(data_math["storage"])+1) @@ -688,38 +684,40 @@ end "" -function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) +function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) # TODO enable real switches (right now only using vitual lines) for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] - if lossless - math_obj = _init_math_obj("switch", eng_obj, length(data_math["switch"])+1) - math_obj["name"] = name + math_obj = _init_math_obj("switch", eng_obj, length(data_math["switch"])+1) + math_obj["name"] = name - math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - data_math["switch"]["$(math_obj["index"])"] = math_obj + # OPF bounds + for (fr_key, to_key) in zip(["cm_ub"], ["c_rating"]) + if haskey(eng_obj, fr_key) + math_obj[to_key] = eng_obj[fr_key] + end + end - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "switch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_switch!, - ) + map_to = "switch.$(math_obj["index"])" - # time series - # TODO - for (fr, to) in zip(["status"], ["status"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "switch", "$(math_obj["index"])", to) - end + # time series + # TODO switch time series + for (fr, to) in zip(["status", "state"], ["status", "state"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "switch", "$(math_obj["index"])", to) end - else + end + + if any(haskey(eng_obj, k) for k in ["rs", "xs", "linecode"]) # build virtual bus - f_bus = data_math["bus"]["$(data_math["bus_lookup"][eng_obj["f_bus"]])"] + + f_bus = data_math["bus"]["$(math_obj["f_bus"])"] bus_obj = Dict{String,Any}( "name" => "_virtual_bus.switch.$name", @@ -732,23 +730,18 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A "index" => length(data_math["bus"])+1, ) + #= TODO enable real switches + # math_obj["t_bus"] = bus_obj["bus_i"] # data_math["bus"]["$(bus_obj["index"])"] = bus_obj + =# # build virtual branch - if haskey(eng_obj, "linecode") - linecode = data_eng["linecode"][eng_obj["linecode"]] - - for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - if !haskey(eng_obj, property) && haskey(linecode, property) - eng_obj[property] = linecode[property] - end - end - end + _apply_linecode!(eng_obj, data_eng) branch_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) - Zbase = 1 + Zbase = 1 # TODO why Zbase=1 on switch impedance branch? _branch_obj = Dict{String,Any}( "name" => "_virtual_branch.switch.$name", @@ -758,10 +751,10 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, - "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9), - "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9), - "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9), - "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9), + "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * get(eng_obj,"g_fr",zeros(size(eng_obj["rs"]))) * eng_obj["length"] / 1e9), + "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * get(eng_obj,"g_to",zeros(size(eng_obj["rs"]))) * eng_obj["length"] / 1e9), + "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * get(eng_obj,"b_fr",zeros(size(eng_obj["rs"]))) * eng_obj["length"] / 1e9), + "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * get(eng_obj,"b_to",zeros(size(eng_obj["rs"]))) * eng_obj["length"] / 1e9), "angmin" => fill(-60.0, nphases), "angmax" => fill( 60.0, nphases), "transformer" => false, @@ -801,13 +794,18 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A # data_math["switch"]["$(switch_obj["index"])"] = switch_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - # :to_id => ["switch.$(switch_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], # TODO enable real switches - :to => ["branch.$(branch_obj["index"])"], - :unmap_function => :_map_math2eng_switch!, - ) + map_to = ["branch.$(branch_obj["index"])"] + # map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] # TODO enable real switches end + + # data_math["switch"]["$(math_obj["index"])"] = math_obj # TODO enable real switches + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => map_to, + :unmap_function => :_map_math2eng_switch!, + ) + end end @@ -901,30 +899,25 @@ end "" -function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4, lossless::Bool=false) +function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Any}()) nconductors = data_math["conductors"] - if lossless - math_obj = _init_math_obj("voltage_source", eng_obj, length(data_math["gen"])+1) + math_obj = _init_math_obj("voltage_source", eng_obj, length(data_math["gen"])+1) - math_obj["name"] = name - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] - math_obj["pg"] = fill(0.0, nconductors) - math_obj["qg"] = fill(0.0, nconductors) - math_obj["configuration"] = "wye" + math_obj["name"] = "_virtual_gen.voltage_source.$name" + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] + math_obj["pg"] = fill(0.0, nconductors) + math_obj["qg"] = fill(0.0, nconductors) + math_obj["configuration"] = "wye" + math_obj["source_id"] = "_virtual_gen.$(eng_obj["source_id"])" - _add_gen_cost_model!(math_obj, eng_obj) + _add_gen_cost_model!(math_obj, eng_obj) - data_math["gen"]["$(math_obj["index"])"] = math_obj + map_to = "gen.$(math_obj["index"])" - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_voltage_source!, - ) - else + if haskey(eng_obj, "rs") && haskey(eng_obj, "xs") bus_obj = Dict{String,Any}( "bus_i" => length(data_math["bus"])+1, "index" => length(data_math["bus"])+1, @@ -937,25 +930,9 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "basekv" => data_math["basekv"] ) - data_math["bus"]["$(bus_obj["index"])"] = bus_obj + math_obj["gen_bus"] = bus_obj["bus_i"] - gen_obj = Dict{String,Any}( - "gen_bus" => bus_obj["bus_i"], - "name" => "_virtual_gen.voltage_source.$name", - "gen_status" => eng_obj["status"], - "pg" => fill(0.0, nconductors), - "qg" => fill(0.0, nconductors), - "model" => 2, - "startup" => 0.0, - "shutdown" => 0.0, - "ncost" => 3, - "cost" => [0.0, 1.0, 0.0], - "configuration" => "wye", - "index" => length(data_math["gen"]) + 1, - "source_id" => "_virtual_gen.$(eng_obj["source_id"])" - ) - - data_math["gen"]["$(gen_obj["index"])"] = gen_obj + data_math["bus"]["$(bus_obj["index"])"] = bus_obj branch_obj = Dict{String,Any}( "name" => "_virtual_branch.voltage_source.$name", @@ -980,11 +957,15 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: data_math["branch"]["$(branch_obj["index"])"] = branch_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], - :unmap_function => :_map_math2eng_voltage_source!, - ) + map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] end + + data_math["gen"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => map_to, + :unmap_function => :_map_math2eng_voltage_source!, + ) end end diff --git a/src/io/common.jl b/src/io/common.jl index c4002c9ee..e1a9f0e00 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -3,12 +3,12 @@ Parses the IOStream of a file into a Three-Phase PowerModels data structure. """ -function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="mathematical", import_all::Bool=false, bank_transformers::Bool=true, lossless::Bool=false, adjust_bounds::Bool=false, transformations::Vector{Function}=Vector{Function}([]))::Dict{String,Any} +function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="mathematical", import_all::Bool=false, bank_transformers::Bool=true, transformations::Vector{Function}=Vector{Function}([]))::Dict{String,Any} if filetype == "dss" data_eng = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) if data_model == "mathematical" - return transform_data_model(data_eng; make_pu=true, lossless=lossless, adjust_bounds=adjust_bounds) + return transform_data_model(data_eng; make_pu=true) else return data_eng end @@ -16,7 +16,7 @@ function parse_file(io::IO, filetype::AbstractString="json"; data_model::String= pmd_data = parse_json(io; validate=false) if get(pmd_data, "data_model", "mathematical") != data_model - return transform_data_model(pmd_data; lossless=lossless, adjust_bounds=adjust_bounds) + return transform_data_model(pmd_data) else return pmd_data end @@ -37,19 +37,18 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=false, lossless::Bool=false, adjust_bounds::Bool=false)::Dict{String,Any} +function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=false)::Dict{String,Any} current_data_model = get(data, "data_model", "mathematical") if current_data_model == "engineering" - data_math = _map_eng2math(data; kron_reduced=kron_reduced, lossless=lossless, adjust_bounds=adjust_bounds) + data_math = _map_eng2math(data; kron_reduced=kron_reduced) correct_network_data!(data_math; make_pu=make_pu) return data_math elseif current_data_model == "mathematical" - data_eng = _map_math2eng(data) - - correct_network_data!(data_eng; make_pu=make_pu) + Memento.warn(_LOGGER, "A mathematical data model cannot be converted back to an engineering data model, irreversible transformations have been made") + return data else @warn "Data model '$current_data_model' is not recognized, no model type transformation performed" return data @@ -61,9 +60,6 @@ end function correct_network_data!(data::Dict{String,Any}; make_pu::Bool=true) if get(data, "data_model", "mathematical") == "engineering" check_eng_data_model(data) - if make_pu - make_per_unit!(data) - end else if make_pu make_per_unit!(data) From 04ddcbeb9a58f6567851ed95c7515197f2c5dfa7 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 17 Apr 2020 11:40:10 -0600 Subject: [PATCH 130/224] ADD: Transformations example --- src/PowerModelsDistribution.jl | 1 + src/data_model/transformations.jl | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/data_model/transformations.jl diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index fc51f8ab1..bc612f2e6 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -57,6 +57,7 @@ module PowerModelsDistribution include("data_model/components.jl") include("data_model/eng2math.jl") include("data_model/math2eng.jl") + include("data_model/transformations.jl") include("data_model/units.jl") include("prob/common.jl") diff --git a/src/data_model/transformations.jl b/src/data_model/transformations.jl new file mode 100644 index 000000000..4d4a0f83f --- /dev/null +++ b/src/data_model/transformations.jl @@ -0,0 +1,23 @@ +# This file contains useful transformation functions for the engineering data model + +const _loss_model_objects = Dict{String,Vector{String}}( + "switch" => Vector{String}(["linecode", "rs", "xs", "g_fr", "b_fr", "g_to", "b_to"]), + "voltage_source" => Vector{String}(["rs", "xs"]), + "transformer" => Vector{String}(["rs", "xsc", "imag", "noloadloss"]) +) + + +"remove parameters from objects with loss models to make them lossless" +function make_lossless!(data_eng::Dict{String,<:Any}) + for (object_type, parameters) in _loss_model_objects + if haskey(data_eng, object_type) + for (id, eng_obj) in data_eng[object_type] + for parameter in parameters + if haskey(eng_obj, parameter) + delete!(eng_obj, parameter) + end + end + end + end + end +end From 3692a73dad8c931c335635fe4f804197d37c7745 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 17 Apr 2020 13:30:04 -0600 Subject: [PATCH 131/224] UPD: Generator property names ADD: Time series parameters --- src/data_model/eng2math.jl | 178 ++++++++++++++++++++++++++++++------- src/data_model/utils.jl | 38 +++++++- src/io/opendss.jl | 12 +-- 3 files changed, 187 insertions(+), 41 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index d0c387061..00bb8eede 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -8,7 +8,7 @@ const _1to1_maps = Dict{String,Vector{String}}( "series_capacitor" => ["source_id", "dss"], "shunt" => ["status", "source_id", "dss"], "shunt_reactor" => ["status", "source_id", "dss"], - "generator" => ["source_id", "configuration", "dss"], + "generator" => ["source_id", "configuration", "dss", "pg", "qg"], "solar" => ["source_id", "configuration", "dss", "dss"], "storage" => ["status", "source_id", "dss"], "line" => ["source_id", "dss"], @@ -29,6 +29,119 @@ const _edge_elements = Vector{String}([ "line", "switch", "transformer", "line_reactor", "series_capacitor" ]) +"list of time-series supported parameters that map one-to-one" +const _time_series_parameters = Dict{String,Dict{String,Tuple{Function, String}}}( + "switch" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "state" => (_no_conversion, "state"), + "c_rating" => (_no_conversion, "cm_ub"), + "s_rating" => (_no_conversion, "sm_ub"), + "br_r" => (_impedance_conversion, "rs"), + "br_x" => (_impedance_conversion, "xs"), + "g_fr" => (_admittance_conversion, "g_fr"), + "g_to" => (_admittance_conversion, "g_to"), + "b_fr" => (_admittance_conversion, "b_fr"), + "b_to" => (_admittance_conversion, "b_to") + ), + "fuse" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "state" => (_no_conversion, "state"), + "c_rating" => (_no_conversion, "cm_ub"), + "s_rating" => (_no_conversion, "sm_ub"), + "br_r" => (_impedance_conversion, "rs"), + "br_x" => (_impedance_conversion, "xs"), + "g_fr" => (_admittance_conversion, "g_fr"), + "g_to" => (_admittance_conversion, "g_to"), + "b_fr" => (_admittance_conversion, "b_fr"), + "b_to" => (_admittance_conversion, "b_to") + ), + "line" => Dict{String,Tuple{Function, String}}( + "br_status" => (_no_conversion, "status"), + "c_rating" => (_no_conversion, "cm_ub"), + "s_rating" => (_no_conversion, "sm_ub"), + "br_r" => (_impedance_conversion, "rs"), + "br_x" => (_impedance_conversion, "xs"), + "g_fr" => (_admittance_conversion, "g_fr"), + "g_to" => (_admittance_conversion, "g_to"), + "b_fr" => (_admittance_conversion, "b_fr"), + "b_to" => (_admittance_conversion, "b_to") + ), + "transformer" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + # TODO need to figure out how to convert values for time series for decomposed transformers + ), + "bus" => Dict{String,Tuple{Function, String}}( + "bus_type" => (_bus_type_conversion, "status"), + "vmin" => (_no_conversion, "vm_lb"), + "vmax" => (_no_conversion, "vm_ub"), + "vm" => (_no_conversion, "vm"), + "va" => (_no_conversion, "va") + ), + "shunt" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "gs" => (_no_conversion, "gs"), + "bs" => (_no_conversion, "bs"), + ), + "shunt_capacitor" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "gs" => (_no_conversion, "gs"), + "bs" => (_no_conversion, "bs"), + ), + "shunt_reactor" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "gs" => (_no_conversion, "gs"), + "bs" => (_no_conversion, "bs"), + ), + "load" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "pd_ref" => (_no_conversion, "pd_nom"), + "qd_ref" => (_no_conversion, "qd_nom"), + ), + "generator" => Dict{String,Tuple{Function, String}}( + "gen_status" => (_no_conversion, "status"), + "pg" => (_no_conversion, "pg"), + "qg" => (_no_conversion, "qg"), + "vg" => (_vnom_conversion, "vg"), + "pmin" => (_no_conversion, "pg_lb"), + "pmax" => (_no_conversion, "pg_ub"), + "qmin" => (_no_conversion, "qg_lb"), + "qmax" => (_no_conversion, "qg_ub"), + ), + "solar" => Dict{String,Tuple{Function, String}}( + "gen_status" => (_no_conversion, "status"), + "pg" => (_no_conversion, "pg"), + "qg" => (_no_conversion, "qg"), + "vg" => (_vnom_conversion, "vg"), + "pmin" => (_no_conversion, "pg_lb"), + "pmax" => (_no_conversion, "pg_ub"), + "qmin" => (_no_conversion, "qg_lb"), + "qmax" => (_no_conversion, "qg_ub"), + ), + "storage" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "energy" => (_no_conversion, "energy"), + "energy_rating" => (_no_conversion, "energy_ub"), + "charge_rating" => (_no_conversion, "charge_ub"), + "discharge_rating" => (_no_conversion, "discharge_ub"), + "charge_efficiency" => (_no_conversion, "charge_efficiency"), + "discharge_efficiency" => (_no_conversion, "discharge_efficiency"), + "thermal_rating" => (_no_conversion, "cm_ub"), + "qmin" => (_no_conversion, "qs_lb"), + "qmax" => (_no_conversion, "qs_ub"), + "r" => (_no_conversion, "rs"), + "x" => (_no_conversion, "xs"), + "p_loss" => (_no_conversion, "pex"), + "q_loss" => (_no_conversion, "qex"), + "ps" => (_no_conversion, "ps"), + "qs" => (_no_conversion, "qs"), + ), + "voltage_source" => Dict{String,Tuple{Function, String}}( + "gen_status" => (_no_conversion, "status"), + "vm" => (_no_conversion, "vm"), + "va" => (_angle_shift_conversion, "va"), + ), + +) "base function for converting engineering model to mathematical model" function _map_eng2math(data_eng; kron_reduced::Bool=true) @@ -128,7 +241,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, math_obj["name"] = name math_obj["bus_i"] = math_obj["index"] - math_obj["bus_type"] = eng_obj["status"] == 1 ? 1 : 4 + math_obj["bus_type"] = _bus_type_conversion(data_eng, eng_obj, "status") if haskey(eng_obj, "vm") math_obj["vm"] = eng_obj["vm"] @@ -385,14 +498,14 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["name"] = name math_obj["gen_status"] = eng_obj["status"] - math_obj["pg"] = eng_obj["kw"] - math_obj["qg"] = eng_obj["kvar"] - math_obj["vg"] = eng_obj["kv"] ./ data_math["basekv"] + math_obj["pg"] = eng_obj["pg"] + math_obj["qg"] = eng_obj["qg"] + math_obj["vg"] = eng_obj["vg"] ./ data_math["basekv"] - math_obj["qmin"] = eng_obj["kvar_min"] - math_obj["qmax"] = eng_obj["kvar_max"] + math_obj["qmin"] = eng_obj["qg_lb"] + math_obj["qmax"] = eng_obj["qg_ub"] - math_obj["pmax"] = eng_obj["kw"] + math_obj["pmax"] = eng_obj["pg"] math_obj["pmin"] = zeros(phases) _add_gen_cost_model!(math_obj, eng_obj) @@ -411,7 +524,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ end # if PV generator mode convert attached bus to PV bus - if eng_obj["control_model"] == 3 + if eng_obj["control_mode"] == 3 data_math["bus"]["$(data_math["bus_lookup"][eng_obj["bus"]])"]["bus_type"] = 2 end @@ -480,10 +593,10 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An # time series # TODO - for (fr, to) in zip(["status"], ["status"]) + for (fr, (f, to)) in _time_series_parameters["solar"] if haskey(eng_obj, "$(fr)_time_series") time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "gen", "$(math_obj["index"])", to) + _parse_time_series_parameter!(data_math, time_series, eng_obj, "gen", "$(math_obj["index"])", fr, to, f) end end end @@ -556,14 +669,14 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] - math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] + math_obj["br_r"] = _impedance_conversion(data_eng, eng_obj, "rs") + math_obj["br_x"] = _impedance_conversion(data_eng, eng_obj, "xs") - math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) - math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) + math_obj["g_fr"] = _admittance_conversion(data_eng, eng_obj, "g_fr") + math_obj["g_to"] = _admittance_conversion(data_eng, eng_obj, "g_to") - math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) - math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) + math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") + math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") math_obj["angmin"] = fill(-60.0, nphases) math_obj["angmax"] = fill( 60.0, nphases) @@ -626,14 +739,14 @@ function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Di math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] - math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] + math_obj["br_r"] = _impedance_conversion(data_eng, eng_obj, "rs") + math_obj["br_x"] = _impedance_conversion(data_eng, eng_obj, "xs") - math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) - math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) + math_obj["g_fr"] = _admittance_conversion(data_eng, eng_obj, "g_fr") + math_obj["g_to"] = _admittance_conversion(data_eng, eng_obj, "g_to") - math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) - math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) + math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") + math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") math_obj["angmin"] = fill(-60.0, nphases) math_obj["angmax"] = fill( 60.0, nphases) @@ -740,21 +853,18 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A branch_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) - Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) - Zbase = 1 # TODO why Zbase=1 on switch impedance branch? - _branch_obj = Dict{String,Any}( "name" => "_virtual_branch.switch.$name", "source_id" => "_virtual_branch.switch.$name", # "f_bus" => bus_obj["bus_i"], # TODO enable real switches "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], - "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, - "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, - "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * get(eng_obj,"g_fr",zeros(size(eng_obj["rs"]))) * eng_obj["length"] / 1e9), - "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * get(eng_obj,"g_to",zeros(size(eng_obj["rs"]))) * eng_obj["length"] / 1e9), - "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * get(eng_obj,"b_fr",zeros(size(eng_obj["rs"]))) * eng_obj["length"] / 1e9), - "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * get(eng_obj,"b_to",zeros(size(eng_obj["rs"]))) * eng_obj["length"] / 1e9), + "br_r" => _impedance_conversion(data_eng, eng_obj, "rs"), + "br_x" => _impedance_conversion(data_eng, eng_obj, "xs"), + "g_fr" => _admittance_conversion(data_eng, eng_obj, "g_fr"), + "g_to" => _admittance_conversion(data_eng, eng_obj, "g_to"), + "b_fr" => _admittance_conversion(data_eng, eng_obj, "b_fr"), + "b_to" => _admittance_conversion(data_eng, eng_obj, "b_to"), "angmin" => fill(-60.0, nphases), "angmax" => fill( 60.0, nphases), "transformer" => false, @@ -946,8 +1056,8 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "tranformer" => false, "switch" => false, "br_status" => 1, - "br_r" => eng_obj["rs"], - "br_x" => eng_obj["xs"], + "br_r" => _impedance_conversion(data_eng, eng_obj, "rs"), + "br_x" => _impedance_conversion(data_eng, eng_obj, "xs"), "g_fr" => zeros(nconductors, nconductors), "g_to" => zeros(nconductors, nconductors), "b_fr" => zeros(nconductors, nconductors), diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 41158130f..3450e0fb4 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -446,7 +446,7 @@ end "parses {}_times_series parameters into the expected InfrastructureModels format" -function _parse_time_series_parameter!(data_math::Dict{String,<:Any}, time_series::Dict{String,<:Any}, fr_parameter::Any, to_component_type::String, to_component_id::String, to_parameter::String) +function _parse_time_series_parameter!(data_math::Dict{String,<:Any}, time_series::Dict{String,<:Any}, to_component_type::String, to_component_id::String, fr_parameter::Any, to_parameter::String, conversion_function::Function) if !haskey(data_math, "time_series") data_math["time_series"] => Dict{String,Any}() end @@ -475,3 +475,39 @@ function _parse_time_series_parameter!(data_math::Dict{String,<:Any}, time_serie data_math["time_series"][to_component_type][to_component_id][to_parameter] = fr_parameter .* time_series["values"] end end + + +"" +function _no_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) + eng_obj[key] +end + + +"" +function _impedance_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) + eng_obj[key] .* get(eng_obj, "length", 1.0) +end + + +"" +function _admittance_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) + 2.0 .* pi .* data_eng["settings"]["base_frequency"] .* eng_obj[key] .* get(eng_obj, "length", 1.0) ./ 1e9 +end + + +"" +function _bus_type_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) + eng_obj[key] == 0 ? 4 : 1 +end + + +"" +function _vnom_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) + eng_obj[key] ./ data_eng["settings"]["vbase"] +end + + +"" +function _angle_shift_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) + _wrap_to_180([120.0 * i for i in 1:3] .+ eng_obj[key]) +end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index c68dc6b7d..461470ff6 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -328,12 +328,12 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String "phases" => nphases, "connections" => _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)), "bus" => _parse_busname(defaults["bus1"])[1], - "kw" => fill(defaults["kw"] / nphases, nphases), - "kvar" => fill(defaults["kvar"] / nphases, nphases), - "kv" => fill(defaults["kv"], nphases), - "kvar_min" => fill(defaults["minkvar"] / nphases, nphases), - "kvar_max" => fill(defaults["maxkvar"] / nphases, nphases), - "control_model" => defaults["model"], + "pg" => fill(defaults["kw"] / nphases, nphases), + "qg" => fill(defaults["kvar"] / nphases, nphases), + "vg" => fill(defaults["kv"], nphases), + "qg_lb" => fill(defaults["minkvar"] / nphases, nphases), + "qg_ub" => fill(defaults["maxkvar"] / nphases, nphases), + "control_mode" => defaults["model"], "configuration" => "wye", "status" => convert(Int, defaults["enabled"]), "source_id" => "generator.$(name)" From 44116c8c1892d2441c715be67e25f7855d61417c Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 17 Apr 2020 13:34:04 -0600 Subject: [PATCH 132/224] REF: move utility functions --- src/data_model/eng2math.jl | 48 +++----------------------------------- src/data_model/utils.jl | 35 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 00bb8eede..05d0d7a42 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -190,42 +190,6 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) end -"initializes the base math object of any type, and copies any one-to-one mappings" -function _init_math_obj(obj_type::String, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} - math_obj = Dict{String,Any}() - - for key in _1to1_maps[obj_type] - if haskey(eng_obj, key) - math_obj[key] = eng_obj[key] - end - end - - math_obj["index"] = index - - return math_obj -end - - -"initializes the base components that are expected by powermodelsdistribution in the mathematical model" -function _init_base_components!(data_math::Dict{String,<:Any}) - for key in ["bus", "load", "shunt", "gen", "branch", "switch", "transformer", "storage", "dcline"] - if !haskey(data_math, key) - data_math[key] = Dict{String,Any}() - end - end -end - - -"Initializes the lookup table" -function _init_lookup!(data_math::Dict{String,<:Any}) - for key in keys(_1to1_maps) - if !haskey(data_math["lookup"], key) - data_math["lookup"][key] = Dict{Any,Int}() - end - end -end - - "converts engineering bus components into mathematical bus components" function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) @@ -279,16 +243,10 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, ) # time series - for (fr, to) in zip(["status", "vm", "va", "vm_lb", "vm_ub"], ["bus_type", "vm", "va", "vmin", "vmax"]) + for (fr, (f, to)) in _time_series_parameters["bus"] if haskey(eng_obj, "$(fr)_time_series") - if to == "bus_type" - time_series = deepcopy(data_eng["time_series"][eng_obj["$(fr)_time_series"]]) - time_series = [status == 1 ? 1 : 4 for status in time_series["values"]] - @assert time_series["replace"] - else - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - end - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "bus", "$(math_obj["index"])", to) + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj, "bus", "$(math_obj["index"])", fr, to, f) end end end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 3450e0fb4..f23e2dc14 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -1,3 +1,38 @@ +"initializes the base math object of any type, and copies any one-to-one mappings" +function _init_math_obj(obj_type::String, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} + math_obj = Dict{String,Any}() + + for key in _1to1_maps[obj_type] + if haskey(eng_obj, key) + math_obj[key] = eng_obj[key] + end + end + + math_obj["index"] = index + + return math_obj +end + + +"initializes the base components that are expected by powermodelsdistribution in the mathematical model" +function _init_base_components!(data_math::Dict{String,<:Any}) + for key in ["bus", "load", "shunt", "gen", "branch", "switch", "transformer", "storage", "dcline"] + if !haskey(data_math, key) + data_math[key] = Dict{String,Any}() + end + end +end + + +"Initializes the lookup table" +function _init_lookup!(data_math::Dict{String,<:Any}) + for key in keys(_1to1_maps) + if !haskey(data_math["lookup"], key) + data_math["lookup"][key] = Dict{Any,Int}() + end + end +end + "function for applying a scale to a paramter" function _scale(dict::Dict{String,<:Any}, key::String, scale::Real) From 5614f789e61fa15f14a7117fb1b85d1343af6719 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 17 Apr 2020 13:54:14 -0600 Subject: [PATCH 133/224] REF: reorganized file to match data model document REF: added component id to _init_math_obj! args --- src/data_model/eng2math.jl | 967 ++++++++++++++++++------------------- src/data_model/utils.jl | 6 +- 2 files changed, 481 insertions(+), 492 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 05d0d7a42..6b63f1981 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -3,19 +3,18 @@ import LinearAlgebra: diagm "items that are mapped one-to-one from engineering to math models" const _1to1_maps = Dict{String,Vector{String}}( "bus" => ["vm", "va", "dss"], - "load" => ["model", "configuration", "status", "source_id", "dss"], - "shunt_capacitor" => ["status", "source_id", "dss"], - "series_capacitor" => ["source_id", "dss"], - "shunt" => ["status", "source_id", "dss"], - "shunt_reactor" => ["status", "source_id", "dss"], - "generator" => ["source_id", "configuration", "dss", "pg", "qg"], - "solar" => ["source_id", "configuration", "dss", "dss"], - "storage" => ["status", "source_id", "dss"], "line" => ["source_id", "dss"], - "line_reactor" => ["source_id", "dss"], - "switch" => ["source_id", "state", "status", "dss"], - "line_reactor" => ["source_id", "dss"], "transformer" => ["source_id", "dss"], + "switch" => ["status", "state", "source_id", "dss"], + "line_reactor" => ["source_id", "dss"], + "series_capacitor" => ["source_id", "dss"], + "shunt" => ["status", "gs", "bs", "source_id", "dss"], + "shunt_capacitor" => ["status", "bs", "source_id", "dss"], + "shunt_reactor" => ["status", "source_id", "dss"], + "load" => ["model", "configuration", "connections", "status", "source_id", "dss"], + "generator" => ["pg", "qg", "configuration", "source_id", "dss"], + "solar" => ["configuration", "source_id", "dss"], + "storage" => ["status", "energy", "ps", "qs", "source_id", "dss"], "voltage_source" => ["source_id", "dss"], ) @@ -180,7 +179,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) - # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO + # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO build conversion for series capacitors _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) @@ -253,95 +252,63 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, end -"converts engineering load components into mathematical load components" -function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("load", eng_obj, length(data_math["load"])+1) +"converts engineering lines into mathematical branches" +function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) + _apply_linecode!(eng_obj, data_eng) + + math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) math_obj["name"] = name - connections = eng_obj["connections"] + nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] - math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - - math_obj["pd"] = eng_obj["pd"] - math_obj["qd"] = eng_obj["qd"] - - math_obj["configuration"] = eng_obj["configuration"] - - if kron_reduced - if math_obj["configuration"]=="wye" - @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pd", "qd"], connections[connections.!=kr_neutral], kr_phases) - else - _pad_properties_delta!(math_obj, ["pd", "qd"], connections, kr_phases) - end - else - math_obj["connections"] = connections - end - - math_obj["vnom_kv"] = eng_obj["vnom"] - - data_math["load"]["$(math_obj["index"])"] = math_obj - - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "load.$(math_obj["index"])", - :unmap_function => :_map_math2eng_load!, - ) - - # time series - for (fr, to) in zip(["status", "pd", "qd"], ["status", "pd", "qd"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "load", "$(math_obj["index"])", to) - end - end - end -end - - -"" -function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt_capacitor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt_capacitor", eng_obj, length(data_math["shunt"])+1) + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - math_obj["name"] = name - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["br_r"] = _impedance_conversion(data_eng, eng_obj, "rs") + math_obj["br_x"] = _impedance_conversion(data_eng, eng_obj, "xs") - f_terminals = eng_obj["f_connections"] - t_terminals = eng_obj["t_connections"] + math_obj["g_fr"] = _admittance_conversion(data_eng, eng_obj, "g_fr") + math_obj["g_to"] = _admittance_conversion(data_eng, eng_obj, "g_to") - vnom_ln = eng_obj["kv"] - qnom = eng_obj["kvar"] ./ 1e3 - b = qnom ./ vnom_ln.^2 + math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") + math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") - # convert to a shunt matrix - terminals, B = _calc_shunt(f_terminals, t_terminals, b) + math_obj["angmin"] = fill(-60.0, nphases) + math_obj["angmax"] = fill( 60.0, nphases) - # if one terminal is ground (0), reduce shunt addmittance matrix - terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) + math_obj["transformer"] = false + math_obj["shift"] = zeros(nphases) + math_obj["tap"] = ones(nphases) - math_obj["bs"] = B - math_obj["gs"] = zeros(size(B)) + f_bus = data_eng["bus"][eng_obj["f_bus"]] + t_bus = data_eng["bus"][eng_obj["t_bus"]] if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") filter = _kron_reduce_branch!(math_obj, - Vector{String}([]), ["gs", "bs"], + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], eng_obj["f_connections"], kr_neutral ) + _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) connections = eng_obj["f_connections"][filter] - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) else - math_obj["connections"] = eng_obj["connections"] + math_obj["f_connections"] = eng_obj["f_connections"] + math_obj["f_connections"] = eng_obj["t_connections"] end - data_math["shunt"]["$(math_obj["index"])"] = math_obj + math_obj["switch"] = false + + math_obj["br_status"] = eng_obj["status"] + + data_math["branch"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_shunt_capacitor!, + :to => "branch.$(math_obj["index"])", + :unmap_function => :_map_math2eng_line!, ) # time series @@ -349,277 +316,231 @@ function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng: for (fr, to) in zip(["status"], ["status"]) if haskey(eng_obj, "$(fr)_time_series") time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) end end end end -"" -function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt", eng_obj, length(data_math["shunt"])+1) +"converts engineering n-winding transformers into mathematical ideal 2-winding lossless transformer branches and impedance branches to represent the loss model" +function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "transformer", Dict{Any,Dict{String,Any}}()) + # Build map first, so we can update it as we decompose the transformer + map_idx = length(data_math["map"])+1 + data_math["map"][map_idx] = Dict{Symbol,Any}( + :from => name, + :to => Vector{String}([]), + :unmap_function => :_map_math2eng_transformer!, + ) - # TODO change to new capacitor shunt calc logic - math_obj["name"] = name - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + to_map = data_math["map"][map_idx][:to] + + _apply_xfmrcode!(eng_obj, data_eng) - math_obj["gs"] = eng_obj["g_sh"] - math_obj["bs"] = eng_obj["b_sh"] + vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] + snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] - if kron_reduced - filter = _kron_reduce_branch!(math_obj, - Vector{String}([]), ["gs", "bs"], - eng_obj["connections"], kr_neutral + nrw = length(eng_obj["bus"]) + + # calculate zbase in which the data is specified, and convert to SI + zbase = (vnom.^2) ./ snom + + # x_sc is specified with respect to first winding + x_sc = eng_obj["xsc"] .* zbase[1] + + # rs is specified with respect to each winding + r_s = eng_obj["rs"] .* zbase + + g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 + b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 + + # data is measured externally, but we now refer it to the internal side + ratios = vnom/data_eng["settings"]["v_var_scalar"] + x_sc = x_sc./ratios[1]^2 + r_s = r_s./ratios.^2 + g_sh = g_sh*ratios[1]^2 + b_sh = b_sh*ratios[1]^2 + + # convert x_sc from list of upper triangle elements to an explicit dict + y_sh = g_sh + im*b_sh + z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) + + transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh) + + for w in 1:nrw + # 2-WINDING TRANSFORMER + # make virtual bus and mark it for reduction + tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] + transformer_2wa_obj = Dict{String,Any}( + "name" => "_virtual_transformer.$name.$w", + "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", + "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], + "t_bus" => transformer_t_bus_w[w], + "tm_nom" => tm_nom, + "f_connections" => eng_obj["connections"][w], + "t_connections" => collect(1:4), + "configuration" => eng_obj["configuration"][w], + "polarity" => eng_obj["polarity"][w], + "tm" => eng_obj["tm"][w], + "fixed" => eng_obj["fixed"][w], + "index" => length(data_math["transformer"])+1 ) - connections = eng_obj["connections"][filter] - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) - else - math_obj["connections"] = eng_obj["connections"] - end - data_math["shunt"]["$(math_obj["index"])"] = math_obj + for prop in ["tm_min", "tm_max", "tm_step"] + if haskey(eng_obj, prop) + transformer_2wa_obj[prop] = eng_obj[prop][w] + end + end - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_capacitor!, - ) + if kron_reduced + # TODO fix how padding works, this is a workaround to get bank working + if all(eng_obj["configuration"] .== "wye") + f_connections = transformer_2wa_obj["f_connections"] + _pad_properties!(transformer_2wa_obj, ["tm_min", "tm_max", "tm", "fixed"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) - # time series - for (fr, to) in zip(["status", "g_sh", "b_sh"], ["status", "gs", "bs"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) + transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] + end end + + data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj + + push!(to_map, "transformer.$(transformer_2wa_obj["index"])") end end end -"" -function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt_reactor", eng_obj, length(data_math["shunt"])+1) - - nphases = eng_obj["phases"] - connections = eng_obj["connections"] +"converts engineering switches into mathematical switches and (if neeed) impedance branches to represent loss model" +function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + # TODO enable real switches (right now only using vitual lines) + for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) + nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] - Gcap = sum(eng_obj["kvar"]) / (nphases * 1e3 * (data_math["basekv"])^2) - + math_obj = _init_math_obj("switch", eng_obj, length(data_math["switch"])+1) math_obj["name"] = name - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gs"] = fill(0.0, nphases, nphases) - math_obj["bs"] = diagm(0=>fill(Gcap, nphases)) + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - if kron_reduced - if eng_obj["configuration"] == "wye" - _pad_properties!(math_obj, ["gs", "bs"], connections[1:end-1], kr_phases) - else - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + # OPF bounds + for (fr_key, to_key) in zip(["cm_ub"], ["c_rating"]) + if haskey(eng_obj, fr_key) + math_obj[to_key] = eng_obj[fr_key] end end - data_math["shunt"]["$(math_obj["index"])"] = math_obj - - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_shunt_reactor!, - ) + map_to = "switch.$(math_obj["index"])" # time series - # TODO - for (fr, to) in zip(["status"], ["status"]) + # TODO switch time series + for (fr, to) in zip(["status", "state"], ["status", "state"]) if haskey(eng_obj, "$(fr)_time_series") time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "switch", "$(math_obj["index"])", to) end end - end -end + if any(haskey(eng_obj, k) for k in ["rs", "xs", "linecode"]) + # build virtual bus -"" -function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) - math_obj = _init_math_obj("generator", eng_obj, length(data_math["gen"])+1) - - phases = eng_obj["phases"] - connections = eng_obj["connections"] - nconductors = data_math["conductors"] + f_bus = data_math["bus"]["$(math_obj["f_bus"])"] - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["name"] = name - math_obj["gen_status"] = eng_obj["status"] - - math_obj["pg"] = eng_obj["pg"] - math_obj["qg"] = eng_obj["qg"] - math_obj["vg"] = eng_obj["vg"] ./ data_math["basekv"] - - math_obj["qmin"] = eng_obj["qg_lb"] - math_obj["qmax"] = eng_obj["qg_ub"] - - math_obj["pmax"] = eng_obj["pg"] - math_obj["pmin"] = zeros(phases) - - _add_gen_cost_model!(math_obj, eng_obj) - - math_obj["configuration"] = eng_obj["configuration"] - - if kron_reduced - if math_obj["configuration"]=="wye" - @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) - else - _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) - end - else - math_obj["connections"] = connections - end - - # if PV generator mode convert attached bus to PV bus - if eng_obj["control_mode"] == 3 - data_math["bus"]["$(data_math["bus_lookup"][eng_obj["bus"]])"]["bus_type"] = 2 - end - - data_math["gen"]["$(math_obj["index"])"] = math_obj - - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_generator!, - ) - - # time series - # TODO - for (fr, to) in zip(["status"], ["status"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "gen", "$(math_obj["index"])", to) - end - end - end -end - - -"" -function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("solar", eng_obj, length(data_math["gen"])+1) - - connections = eng_obj["connections"] - nconductors = data_math["conductors"] + bus_obj = Dict{String,Any}( + "name" => "_virtual_bus.switch.$name", + "bus_i" => length(data_math["bus"])+1, + "bus_type" => 1, + "vmin" => f_bus["vmin"], + "vmax" => f_bus["vmax"], + "base_kv" => f_bus["base_kv"], + "status" => 1, + "index" => length(data_math["bus"])+1, + ) - math_obj["name"] = name - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] + #= TODO enable real switches + # math_obj["t_bus"] = bus_obj["bus_i"] + # data_math["bus"]["$(bus_obj["index"])"] = bus_obj + =# - math_obj["pg"] = eng_obj["kva"] - math_obj["qg"] = eng_obj["kvar"] - math_obj["vg"] = eng_obj["kv"] + # build virtual branch + _apply_linecode!(eng_obj, data_eng) - math_obj["pmin"] = get(eng_obj, "minkva", zeros(size(eng_obj["kva"]))) - math_obj["pmax"] = get(eng_obj, "maxkva", eng_obj["kva"]) + branch_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) - math_obj["qmin"] = get(eng_obj, "minkvar", -eng_obj["kvar"]) - math_obj["qmax"] = get(eng_obj, "maxkvar", eng_obj["kvar"]) + _branch_obj = Dict{String,Any}( + "name" => "_virtual_branch.switch.$name", + "source_id" => "_virtual_branch.switch.$name", + # "f_bus" => bus_obj["bus_i"], # TODO enable real switches + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], + "br_r" => _impedance_conversion(data_eng, eng_obj, "rs"), + "br_x" => _impedance_conversion(data_eng, eng_obj, "xs"), + "g_fr" => _admittance_conversion(data_eng, eng_obj, "g_fr"), + "g_to" => _admittance_conversion(data_eng, eng_obj, "g_to"), + "b_fr" => _admittance_conversion(data_eng, eng_obj, "b_fr"), + "b_to" => _admittance_conversion(data_eng, eng_obj, "b_to"), + "angmin" => fill(-60.0, nphases), + "angmax" => fill( 60.0, nphases), + "transformer" => false, + "shift" => zeros(nphases), + "tap" => ones(nphases), + "switch" => false, + "br_status" => 1, + ) - _add_gen_cost_model!(math_obj, eng_obj) + merge!(branch_obj, _branch_obj) - if kron_reduced - if math_obj["configuration"]=="wye" - @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) + if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") + filter = _kron_reduce_branch!(branch_obj, + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], + eng_obj["f_connections"], kr_neutral + ) + _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) + connections = eng_obj["f_connections"][filter] + _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) else - _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) - end - else - math_obj["connections"] = connections - end - - data_math["gen"]["$(math_obj["index"])"] = math_obj - - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "solar.$name", - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_solar!, - ) - - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["solar"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj, "gen", "$(math_obj["index"])", fr, to, f) + branch_obj["f_connections"] = eng_obj["f_connections"] + branch_obj["f_connections"] = eng_obj["t_connections"] end - end - end -end - - -"" -function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("storage", eng_obj, length(data_math["storage"])+1) - - connections = eng_obj["connections"] - nconductors = data_math["conductors"] - math_obj["name"] = name - math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + data_math["branch"]["$(branch_obj["index"])"] = branch_obj - math_obj["energy"] = eng_obj["kwhstored"] / 1e3 - math_obj["energy_rating"] = eng_obj["kwhrated"] / 1e3 - math_obj["charge_rating"] = eng_obj["%charge"] * eng_obj["kwrated"] / 1e3 / 100.0 - math_obj["discharge_rating"] = eng_obj["%discharge"] * eng_obj["kwrated"] / 1e3 / 100.0 - math_obj["charge_efficiency"] = eng_obj["%effcharge"] / 100.0 - math_obj["discharge_efficiency"] = eng_obj["%effdischarge"] / 100.0 - math_obj["thermal_rating"] = eng_obj["kva"] ./ 1e3 - math_obj["qmin"] = -eng_obj["kvar"] ./ 1e3 - math_obj["qmax"] = eng_obj["kvar"] ./ 1e3 - math_obj["r"] = eng_obj["%r"] ./ 100.0 - math_obj["x"] = eng_obj["%x"] ./ 100.0 - math_obj["p_loss"] = eng_obj["%idlingkw"] .* eng_obj["kwrated"] ./ 1e3 - math_obj["q_loss"] = eng_obj["%idlingkvar"] * sum(eng_obj["kvar"]) / 1e3 + # build switch + switch_obj = Dict{String,Any}( + "name" => name, + "source_id" => eng_obj["source_id"], + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => bus_obj["bus_i"], + "status" => eng_obj["status"], + "index" => length(data_math["switch"])+1 + ) - math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["kva"]))) - math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["kva"]))) + # data_math["switch"]["$(switch_obj["index"])"] = switch_obj - if kron_reduced - _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, kr_phases) + map_to = ["branch.$(branch_obj["index"])"] + # map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] # TODO enable real switches end - data_math["storage"]["$(math_obj["index"])"] = math_obj + # data_math["switch"]["$(math_obj["index"])"] = math_obj # TODO enable real switches data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => "storage.$(math_obj["index"])", - :unmap_function => :_map_math2eng_storage!, + :to => map_to, + :unmap_function => :_map_math2eng_switch!, ) - # time series - # TODO - for (fr, to) in zip(["status"], ["status"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "storage", "$(math_obj["index"])", to) - end - end end end -"" -function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) - _apply_linecode!(eng_obj, data_eng) - +"converts engineering line reactors into mathematical branches" +function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + # TODO support line reactors natively, currently treated like branches + for (name, eng_obj) in get(data_eng, "line_reactor", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) - math_obj["name"] = name + math_obj["name"] = "_virtual_branch.$(eng_obj["source_id"])" nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] @@ -669,7 +590,7 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, :to => "branch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_line!, + :unmap_function => :_map_math2eng_line_reactor!, ) # time series @@ -684,289 +605,355 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any end -"" -function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - # TODO support line reactors natively, currently treated like branches - for (name, eng_obj) in get(data_eng, "line_reactor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) - math_obj["name"] = "_virtual_branch.$(eng_obj["source_id"])" - - nphases = length(eng_obj["f_connections"]) - nconductors = data_math["conductors"] - - math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - - math_obj["br_r"] = _impedance_conversion(data_eng, eng_obj, "rs") - math_obj["br_x"] = _impedance_conversion(data_eng, eng_obj, "xs") - - math_obj["g_fr"] = _admittance_conversion(data_eng, eng_obj, "g_fr") - math_obj["g_to"] = _admittance_conversion(data_eng, eng_obj, "g_to") - - math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") - math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") - - math_obj["angmin"] = fill(-60.0, nphases) - math_obj["angmax"] = fill( 60.0, nphases) +"converts engineering generic shunt components into mathematical shunt components" +function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "shunt", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt", name, eng_obj, length(data_math["shunt"])+1) - math_obj["transformer"] = false - math_obj["shift"] = zeros(nphases) - math_obj["tap"] = ones(nphases) + # TODO change to new capacitor shunt calc logic + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - f_bus = data_eng["bus"][eng_obj["f_bus"]] - t_bus = data_eng["bus"][eng_obj["t_bus"]] + math_obj["gs"] = eng_obj["gs"] + math_obj["bs"] = eng_obj["bs"] if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") filter = _kron_reduce_branch!(math_obj, - ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], - eng_obj["f_connections"], kr_neutral + Vector{String}([]), ["gs", "bs"], + eng_obj["connections"], kr_neutral ) - _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) - connections = eng_obj["f_connections"][filter] - _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + connections = eng_obj["connections"][filter] + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) else - math_obj["f_connections"] = eng_obj["f_connections"] - math_obj["f_connections"] = eng_obj["t_connections"] + math_obj["connections"] = eng_obj["connections"] end - math_obj["switch"] = false - - math_obj["br_status"] = eng_obj["status"] - - data_math["branch"]["$(math_obj["index"])"] = math_obj + data_math["shunt"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => "branch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_line_reactor!, + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_capacitor!, ) # time series - # TODO - for (fr, to) in zip(["status"], ["status"]) + for (fr, (f, to)) in _time_series_parameters["shunt"] if haskey(eng_obj, "$(fr)_time_series") time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) end end end end -"" -function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - # TODO enable real switches (right now only using vitual lines) - for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) - nphases = length(eng_obj["f_connections"]) - nconductors = data_math["conductors"] +"converts engineering shunt capacitors into mathematical shunts" +function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "shunt_capacitor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt_capacitor", name, eng_obj, length(data_math["shunt"])+1) - math_obj = _init_math_obj("switch", eng_obj, length(data_math["switch"])+1) - math_obj["name"] = name + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + f_terminals = eng_obj["f_connections"] + t_terminals = eng_obj["t_connections"] - # OPF bounds - for (fr_key, to_key) in zip(["cm_ub"], ["c_rating"]) - if haskey(eng_obj, fr_key) - math_obj[to_key] = eng_obj[fr_key] - end + vnom_ln = eng_obj["kv"] + qnom = eng_obj["kvar"] ./ 1e3 + b = qnom ./ vnom_ln.^2 + + # convert to a shunt matrix + terminals, B = _calc_shunt(f_terminals, t_terminals, b) + + # if one terminal is ground (0), reduce shunt addmittance matrix + terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) + + math_obj["bs"] = B + math_obj["gs"] = zeros(size(B)) + + if kron_reduced + filter = _kron_reduce_branch!(math_obj, + Vector{String}([]), ["gs", "bs"], + eng_obj["f_connections"], kr_neutral + ) + connections = eng_obj["f_connections"][filter] + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + else + math_obj["connections"] = eng_obj["connections"] end - map_to = "switch.$(math_obj["index"])" + data_math["shunt"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_shunt_capacitor!, + ) # time series - # TODO switch time series - for (fr, to) in zip(["status", "state"], ["status", "state"]) + # TODO + for (fr, (f, to)) in _time_series_parameters["shunt_capacitor"] if haskey(eng_obj, "$(fr)_time_series") time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "switch", "$(math_obj["index"])", to) + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) end end + end +end - if any(haskey(eng_obj, k) for k in ["rs", "xs", "linecode"]) - # build virtual bus - f_bus = data_math["bus"]["$(math_obj["f_bus"])"] +"converts engineering shunt reactors into mathematical shunts" +function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt_reactor", name, eng_obj, length(data_math["shunt"])+1) - bus_obj = Dict{String,Any}( - "name" => "_virtual_bus.switch.$name", - "bus_i" => length(data_math["bus"])+1, - "bus_type" => 1, - "vmin" => f_bus["vmin"], - "vmax" => f_bus["vmax"], - "base_kv" => f_bus["base_kv"], - "status" => 1, - "index" => length(data_math["bus"])+1, - ) + nphases = eng_obj["phases"] + connections = eng_obj["connections"] + nconductors = data_math["conductors"] - #= TODO enable real switches - # math_obj["t_bus"] = bus_obj["bus_i"] - # data_math["bus"]["$(bus_obj["index"])"] = bus_obj - =# + Gcap = sum(eng_obj["kvar"]) / (nphases * 1e3 * (data_math["basekv"])^2) - # build virtual branch - _apply_linecode!(eng_obj, data_eng) + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - branch_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) + math_obj["gs"] = fill(0.0, nphases, nphases) + math_obj["bs"] = diagm(0=>fill(Gcap, nphases)) - _branch_obj = Dict{String,Any}( - "name" => "_virtual_branch.switch.$name", - "source_id" => "_virtual_branch.switch.$name", - # "f_bus" => bus_obj["bus_i"], # TODO enable real switches - "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], - "br_r" => _impedance_conversion(data_eng, eng_obj, "rs"), - "br_x" => _impedance_conversion(data_eng, eng_obj, "xs"), - "g_fr" => _admittance_conversion(data_eng, eng_obj, "g_fr"), - "g_to" => _admittance_conversion(data_eng, eng_obj, "g_to"), - "b_fr" => _admittance_conversion(data_eng, eng_obj, "b_fr"), - "b_to" => _admittance_conversion(data_eng, eng_obj, "b_to"), - "angmin" => fill(-60.0, nphases), - "angmax" => fill( 60.0, nphases), - "transformer" => false, - "shift" => zeros(nphases), - "tap" => ones(nphases), - "switch" => false, - "br_status" => 1, - ) + if kron_reduced + if eng_obj["configuration"] == "wye" + _pad_properties!(math_obj, ["gs", "bs"], connections[1:end-1], kr_phases) + else + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + end + end - merge!(branch_obj, _branch_obj) + data_math["shunt"]["$(math_obj["index"])"] = math_obj - if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") - filter = _kron_reduce_branch!(branch_obj, - ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], - eng_obj["f_connections"], kr_neutral - ) - _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) - connections = eng_obj["f_connections"][filter] - _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) - else - branch_obj["f_connections"] = eng_obj["f_connections"] - branch_obj["f_connections"] = eng_obj["t_connections"] + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_shunt_reactor!, + ) + + # time series + # TODO + for (fr, (f, to)) in _time_series_parameters["shunt_reactor"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) end + end + end +end - data_math["branch"]["$(branch_obj["index"])"] = branch_obj - # build switch - switch_obj = Dict{String,Any}( - "name" => name, - "source_id" => eng_obj["source_id"], - "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => bus_obj["bus_i"], - "status" => eng_obj["status"], - "index" => length(data_math["switch"])+1 - ) +"converts engineering load components into mathematical load components" +function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("load", name, eng_obj, length(data_math["load"])+1) - # data_math["switch"]["$(switch_obj["index"])"] = switch_obj + connections = eng_obj["connections"] - map_to = ["branch.$(branch_obj["index"])"] - # map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] # TODO enable real switches + math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + + math_obj["pd"] = eng_obj["pd_nom"] + math_obj["qd"] = eng_obj["qd_nom"] + + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pd", "qd"], connections[connections.!=kr_neutral], kr_phases) + else + _pad_properties_delta!(math_obj, ["pd", "qd"], connections, kr_phases) + end + else + math_obj["connections"] = connections end - # data_math["switch"]["$(math_obj["index"])"] = math_obj # TODO enable real switches + math_obj["vnom_kv"] = eng_obj["vnom"] + + data_math["load"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => map_to, - :unmap_function => :_map_math2eng_switch!, + :to => "load.$(math_obj["index"])", + :unmap_function => :_map_math2eng_load!, ) + # time series + for (fr, (f, to)) in _time_series_parameters["load"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "load", "$(math_obj["index"])", to) + end + end end end -"" -function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "transformer", Dict{Any,Dict{String,Any}}()) - # Build map first, so we can update it as we decompose the transformer - map_idx = length(data_math["map"])+1 - data_math["map"][map_idx] = Dict{Symbol,Any}( +"converts engineering generators into mathematical generators" +function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) + math_obj = _init_math_obj("generator", name, eng_obj, length(data_math["gen"])+1) + + phases = eng_obj["phases"] + connections = eng_obj["connections"] + nconductors = data_math["conductors"] + + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] + + math_obj["vg"] = eng_obj["vg"] ./ data_math["basekv"] + + math_obj["qmin"] = eng_obj["qg_lb"] + math_obj["qmax"] = eng_obj["qg_ub"] + + math_obj["pmax"] = eng_obj["pg"] + math_obj["pmin"] = zeros(phases) + + _add_gen_cost_model!(math_obj, eng_obj) + + math_obj["configuration"] = eng_obj["configuration"] + + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) + else + _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) + end + else + math_obj["connections"] = connections + end + + # if PV generator mode convert attached bus to PV bus + if eng_obj["control_mode"] == 3 + data_math["bus"]["$(data_math["bus_lookup"][eng_obj["bus"]])"]["bus_type"] = 2 + end + + data_math["gen"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => Vector{String}([]), - :unmap_function => :_map_math2eng_transformer!, + :to => "gen.$(math_obj["index"])", + :unmap_function => :_map_math2eng_generator!, ) - to_map = data_math["map"][map_idx][:to] + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "gen", "$(math_obj["index"])", to) + end + end + end +end - _apply_xfmrcode!(eng_obj, data_eng) - vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] - snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] +"converts engineering solar components into mathematical generators" +function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("solar", name, eng_obj, length(data_math["gen"])+1) - nrw = length(eng_obj["bus"]) + connections = eng_obj["connections"] + nconductors = data_math["conductors"] - # calculate zbase in which the data is specified, and convert to SI - zbase = (vnom.^2) ./ snom + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] - # x_sc is specified with respect to first winding - x_sc = eng_obj["xsc"] .* zbase[1] + math_obj["pg"] = eng_obj["kva"] + math_obj["qg"] = eng_obj["kvar"] + math_obj["vg"] = eng_obj["kv"] - # rs is specified with respect to each winding - r_s = eng_obj["rs"] .* zbase + math_obj["pmin"] = get(eng_obj, "minkva", zeros(size(eng_obj["kva"]))) + math_obj["pmax"] = get(eng_obj, "maxkva", eng_obj["kva"]) - g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 - b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 + math_obj["qmin"] = get(eng_obj, "minkvar", -eng_obj["kvar"]) + math_obj["qmax"] = get(eng_obj, "maxkvar", eng_obj["kvar"]) - # data is measured externally, but we now refer it to the internal side - ratios = vnom/data_eng["settings"]["v_var_scalar"] - x_sc = x_sc./ratios[1]^2 - r_s = r_s./ratios.^2 - g_sh = g_sh*ratios[1]^2 - b_sh = b_sh*ratios[1]^2 + _add_gen_cost_model!(math_obj, eng_obj) - # convert x_sc from list of upper triangle elements to an explicit dict - y_sh = g_sh + im*b_sh - z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) + else + _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) + end + else + math_obj["connections"] = connections + end - transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh) + data_math["gen"]["$(math_obj["index"])"] = math_obj - for w in 1:nrw - # 2-WINDING TRANSFORMER - # make virtual bus and mark it for reduction - tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] - transformer_2wa_obj = Dict{String,Any}( - "name" => "_virtual_transformer.$name.$w", - "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", - "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], - "t_bus" => transformer_t_bus_w[w], - "tm_nom" => tm_nom, - "f_connections" => eng_obj["connections"][w], - "t_connections" => collect(1:4), - "configuration" => eng_obj["configuration"][w], - "polarity" => eng_obj["polarity"][w], - "tm" => eng_obj["tm"][w], - "fixed" => eng_obj["fixed"][w], - "index" => length(data_math["transformer"])+1 - ) + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => "solar.$name", + :to => "gen.$(math_obj["index"])", + :unmap_function => :_map_math2eng_solar!, + ) - for prop in ["tm_min", "tm_max", "tm_step"] - if haskey(eng_obj, prop) - transformer_2wa_obj[prop] = eng_obj[prop][w] - end + # time series + # TODO + for (fr, (f, to)) in _time_series_parameters["solar"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj, "gen", "$(math_obj["index"])", fr, to, f) end + end + end +end - if kron_reduced - # TODO fix how padding works, this is a workaround to get bank working - if all(eng_obj["configuration"] .== "wye") - f_connections = transformer_2wa_obj["f_connections"] - _pad_properties!(transformer_2wa_obj, ["tm_min", "tm_max", "tm", "fixed"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) - transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] - end - end +"converts engineering storage into mathematical storage" +function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("storage", name, eng_obj, length(data_math["storage"])+1) - data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj + connections = eng_obj["connections"] + nconductors = data_math["conductors"] - push!(to_map, "transformer.$(transformer_2wa_obj["index"])") + math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + + math_obj["energy"] = eng_obj["kwhstored"] / 1e3 + math_obj["energy_rating"] = eng_obj["kwhrated"] / 1e3 + math_obj["charge_rating"] = eng_obj["%charge"] * eng_obj["kwrated"] / 1e3 / 100.0 + math_obj["discharge_rating"] = eng_obj["%discharge"] * eng_obj["kwrated"] / 1e3 / 100.0 + math_obj["charge_efficiency"] = eng_obj["%effcharge"] / 100.0 + math_obj["discharge_efficiency"] = eng_obj["%effdischarge"] / 100.0 + math_obj["thermal_rating"] = eng_obj["kva"] ./ 1e3 + math_obj["qmin"] = -eng_obj["kvar"] ./ 1e3 + math_obj["qmax"] = eng_obj["kvar"] ./ 1e3 + math_obj["r"] = eng_obj["%r"] ./ 100.0 + math_obj["x"] = eng_obj["%x"] ./ 100.0 + math_obj["p_loss"] = eng_obj["%idlingkw"] .* eng_obj["kwrated"] ./ 1e3 + math_obj["q_loss"] = eng_obj["%idlingkvar"] * sum(eng_obj["kvar"]) / 1e3 + + math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["kva"]))) + math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["kva"]))) + + if kron_reduced + _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, kr_phases) + end + + data_math["storage"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "storage.$(math_obj["index"])", + :unmap_function => :_map_math2eng_storage!, + ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "storage", "$(math_obj["index"])", to) + end end end end -"" +"converts engineering voltage sources into mathematical generators and (if needed) impedance branches to represent the loss model" function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Any}()) nconductors = data_math["conductors"] diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index f23e2dc14..bc0104aa3 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -1,6 +1,8 @@ "initializes the base math object of any type, and copies any one-to-one mappings" -function _init_math_obj(obj_type::String, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} - math_obj = Dict{String,Any}() +function _init_math_obj(obj_type::String, eng_id::Any, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} + math_obj = Dict{String,Any}( + "name" => "$eng_id" + ) for key in _1to1_maps[obj_type] if haskey(eng_obj, key) From c54ee88ad73df36b0f91cfbc94881bcc2e6882aa Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 17 Apr 2020 13:56:36 -0600 Subject: [PATCH 134/224] FIX: _init_math_obj! calls --- src/data_model/eng2math.jl | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 6b63f1981..d1ecedb5a 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -199,9 +199,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, @assert all(t in [phases..., neutral] for t in terminals) - math_obj = _init_math_obj("bus", eng_obj, length(data_math["bus"])+1) - - math_obj["name"] = name + math_obj = _init_math_obj("bus", name, eng_obj, length(data_math["bus"])+1) math_obj["bus_i"] = math_obj["index"] math_obj["bus_type"] = _bus_type_conversion(data_eng, eng_obj, "status") @@ -257,8 +255,7 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) _apply_linecode!(eng_obj, data_eng) - math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) - math_obj["name"] = name + math_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] @@ -418,8 +415,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] - math_obj = _init_math_obj("switch", eng_obj, length(data_math["switch"])+1) - math_obj["name"] = name + math_obj = _init_math_obj("switch", name, eng_obj, length(data_math["switch"])+1) math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] @@ -466,7 +462,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A # build virtual branch _apply_linecode!(eng_obj, data_eng) - branch_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) + branch_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) _branch_obj = Dict{String,Any}( "name" => "_virtual_branch.switch.$name", @@ -539,7 +535,7 @@ end function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) # TODO support line reactors natively, currently treated like branches for (name, eng_obj) in get(data_eng, "line_reactor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) + math_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) math_obj["name"] = "_virtual_branch.$(eng_obj["source_id"])" nphases = length(eng_obj["f_connections"]) @@ -958,7 +954,7 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Any}()) nconductors = data_math["conductors"] - math_obj = _init_math_obj("voltage_source", eng_obj, length(data_math["gen"])+1) + math_obj = _init_math_obj("voltage_source", name, eng_obj, length(data_math["gen"])+1) math_obj["name"] = "_virtual_gen.voltage_source.$name" math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] From 0e815bb805e93666daaa7dac68be520ee16f25fa Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 17 Apr 2020 14:00:07 -0600 Subject: [PATCH 135/224] REF: more reorganization to match data model document --- src/data_model/eng2math.jl | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index d1ecedb5a..9d861905a 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -165,8 +165,17 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _init_base_components!(data_math) + # convert buses _map_eng2math_bus!(data_math, data_eng; kron_reduced=kron_reduced) + # convert edges + _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) + # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO build conversion for series capacitors + + # convert nodes _map_eng2math_load!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) @@ -178,13 +187,6 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) - # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO build conversion for series capacitors - _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) - - _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) - return data_math end @@ -834,7 +836,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ # time series # TODO - for (fr, to) in zip(["status"], ["status"]) + for (fr, (f, to)) in _time_series_parameters["generator"] if haskey(eng_obj, "$(fr)_time_series") time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "gen", "$(math_obj["index"])", to) @@ -939,7 +941,7 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: # time series # TODO - for (fr, to) in zip(["status"], ["status"]) + for (fr, (f, to)) in _time_series_parameters["storage"] if haskey(eng_obj, "$(fr)_time_series") time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "storage", "$(math_obj["index"])", to) From 1f7a24a3ebf16ee44536126d28c03ddbf5991576 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 17 Apr 2020 14:23:31 -0600 Subject: [PATCH 136/224] UPD: Storage parameter names --- src/data_model/eng2math.jl | 33 ++++++++++++------------- src/io/opendss.jl | 49 +++++++++++++++++--------------------- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 9d861905a..a3767437f 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -910,22 +910,23 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["energy"] = eng_obj["kwhstored"] / 1e3 - math_obj["energy_rating"] = eng_obj["kwhrated"] / 1e3 - math_obj["charge_rating"] = eng_obj["%charge"] * eng_obj["kwrated"] / 1e3 / 100.0 - math_obj["discharge_rating"] = eng_obj["%discharge"] * eng_obj["kwrated"] / 1e3 / 100.0 - math_obj["charge_efficiency"] = eng_obj["%effcharge"] / 100.0 - math_obj["discharge_efficiency"] = eng_obj["%effdischarge"] / 100.0 - math_obj["thermal_rating"] = eng_obj["kva"] ./ 1e3 - math_obj["qmin"] = -eng_obj["kvar"] ./ 1e3 - math_obj["qmax"] = eng_obj["kvar"] ./ 1e3 - math_obj["r"] = eng_obj["%r"] ./ 100.0 - math_obj["x"] = eng_obj["%x"] ./ 100.0 - math_obj["p_loss"] = eng_obj["%idlingkw"] .* eng_obj["kwrated"] ./ 1e3 - math_obj["q_loss"] = eng_obj["%idlingkvar"] * sum(eng_obj["kvar"]) / 1e3 - - math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["kva"]))) - math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["kva"]))) + # needs to be in units MW + math_obj["energy"] = eng_obj["energy"] * data_eng["settings"]["v_var_scalar"] / 1e6 + math_obj["energy_rating"] = eng_obj["energy_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 + math_obj["charge_rating"] = eng_obj["charge_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 + math_obj["discharge_rating"] = eng_obj["discharge_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 + math_obj["charge_efficiency"] = eng_obj["charge_efficiency"] / 100.0 + math_obj["discharge_efficiency"] = eng_obj["discharge_efficiency"] / 100.0 + math_obj["thermal_rating"] = eng_obj["cm_ub"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + math_obj["qmin"] = eng_obj["qs_lb"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + math_obj["qmax"] = eng_obj["qs_ub"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + math_obj["r"] = eng_obj["rs"] + math_obj["x"] = eng_obj["xs"] + math_obj["p_loss"] = eng_obj["pex"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + math_obj["q_loss"] = eng_obj["qex"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + + math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["cm_ub"]))) + math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["cm_ub"]))) if kron_reduced _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, kr_phases) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 461470ff6..d44b9ab43 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -163,8 +163,8 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["vnom"] = kv - eng_obj["pd"] = fill(defaults["kw"]/nphases, nphases) - eng_obj["qd"] = fill(defaults["kvar"]/nphases, nphases) + eng_obj["pd_nom"] = fill(defaults["kw"]/nphases, nphases) + eng_obj["qd_nom"] = fill(defaults["kvar"]/nphases, nphases) if import_all _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) @@ -756,39 +756,34 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< _apply_like!(dss_obj, data_dss, "storage") defaults = _apply_ordered_properties(_create_storage(name; _to_kwargs(dss_obj)...), dss_obj) - eng_obj = Dict{String,Any}() - nphases = defaults["phases"] - eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], check_length=false) - eng_obj["name"] = name - eng_obj["bus"] = _parse_busname(defaults["bus1"])[1] + eng_obj = Dict{String,Any}( + "bus" => _parse_busname(defaults["bus1"])[1], + "connections" => _get_conductors_ordered(defaults["bus1"], check_length=false), + "configuration" => "wye", + "energy" => defaults["kwhstored"], + "energy_ub" => defaults["kwrated"], + "charge_ub" => defaults["%charge"] / 100.0 * defaults["kwrated"], + "discharge_ub" => defaults["%discharge"] / 100.0 * defaults["kwrated"], + "cm_ub" => fill(defaults["kva"] / nphases, nphases), + "charge_efficiency" => defaults["%effcharge"], + "discharge_efficiency" => defaults["%effdischarge"], + "qs_lb" => -fill(defaults["kvar"] / nphases, nphases), + "qs_ub" => fill(defaults["kvar"] / nphases, nphases), + "rs" => fill(defaults["%r"] / nphases / 100.0, nphases), + "xs" => fill(defaults["%x"] / nphases / 100.0, nphases), + "pex" => defaults["%idlingkw"] .* defaults["kwrated"], + "qex" => defaults["%idlingkvar"] .* defaults["kvar"], + "status" => convert(Int, defaults["enabled"]), + "source_id" => "storage.$(name)", + ) # if the ground is used directly, register load if 0 in eng_obj["connections"] _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end - eng_obj["kwhstored"] = defaults["kwhstored"] - eng_obj["kwhrated"] = defaults["kwhrated"] - eng_obj["kwrated"] = defaults["kwrated"] - - eng_obj["%charge"] = defaults["%charge"] - eng_obj["%discharge"] = defaults["%discharge"] - eng_obj["%effcharge"] = defaults["%effcharge"] - eng_obj["%effdischarge"] = defaults["%effdischarge"] - eng_obj["kva"] = fill(defaults["kva"] / nphases, nphases) - eng_obj["kvar"] = fill(defaults["kvar"] / nphases, nphases) - eng_obj["%r"] = fill(defaults["%r"] / nphases, nphases) - eng_obj["%x"] = fill(defaults["%x"] / nphases, nphases) - eng_obj["%idlingkw"] = defaults["%idlingkw"] - eng_obj["%idlingkvar"] = defaults["%idlingkvar"] - - eng_obj["status"] = convert(Int, defaults["enabled"]) - - - eng_obj["source_id"] = "storage.$(name)" - if import_all _import_all(eng_obj, defaults, dss_obj["prop_order"]) end From 9d7e4aaebe35e4337ceac2b576589702a9c6c182 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 17 Apr 2020 15:50:11 -0600 Subject: [PATCH 137/224] FIX: Unit tests --- src/data_model/eng2math.jl | 24 ++------------- src/io/opendss.jl | 29 ++++++++++++++---- test/data/opendss/test2_master.dss | 18 ++++++------ test/opendss.jl | 47 ++++++++++-------------------- 4 files changed, 51 insertions(+), 67 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index a3767437f..154826876 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -178,9 +178,9 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) # convert nodes _map_eng2math_load!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced) @@ -651,21 +651,7 @@ function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng: math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - f_terminals = eng_obj["f_connections"] - t_terminals = eng_obj["t_connections"] - - vnom_ln = eng_obj["kv"] - qnom = eng_obj["kvar"] ./ 1e3 - b = qnom ./ vnom_ln.^2 - - # convert to a shunt matrix - terminals, B = _calc_shunt(f_terminals, t_terminals, b) - - # if one terminal is ground (0), reduce shunt addmittance matrix - terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) - - math_obj["bs"] = B - math_obj["gs"] = zeros(size(B)) + math_obj["gs"] = zeros(size(eng_obj["bs"])) if kron_reduced filter = _kron_reduce_branch!(math_obj, @@ -703,16 +689,12 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) math_obj = _init_math_obj("shunt_reactor", name, eng_obj, length(data_math["shunt"])+1) - nphases = eng_obj["phases"] connections = eng_obj["connections"] nconductors = data_math["conductors"] - Gcap = sum(eng_obj["kvar"]) / (nphases * 1e3 * (data_math["basekv"])^2) - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gs"] = fill(0.0, nphases, nphases) - math_obj["bs"] = diagm(0=>fill(Gcap, nphases)) + math_obj["gs"] = fill(0.0, size(eng_obj["bs"])...) if kron_reduced if eng_obj["configuration"] == "wye" diff --git a/src/io/opendss.jl b/src/io/opendss.jl index d44b9ab43..05203d69b 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -221,10 +221,23 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String if defaults["phases"] in [2,3] vnom_ln = vnom_ln/sqrt(3) end - eng_obj["kv"] = fill(vnom_ln, nphases) + defaults["kv"] = fill(vnom_ln, nphases) # 'kvar' is specified for all phases at once; we want the per-phase one - eng_obj["kvar"] = fill(defaults["kvar"] / nphases, nphases) + defaults["kvar"] = fill(defaults["kvar"] / nphases, nphases) + + # TODO check unit conversion on qnom/b + vnom_ln = defaults["kv"] + qnom = defaults["kvar"] ./ 1e3 + b = qnom ./ vnom_ln.^2 + + # convert to a shunt matrix + terminals, B = _calc_shunt(f_terminals, t_terminals, b) + + # if one terminal is ground (0), reduce shunt addmittance matrix + terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) + + eng_obj["bs"] = B _add_eng_obj!(data_eng, "shunt_capacitor", name, eng_obj) else @@ -248,16 +261,16 @@ function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< _apply_like!(dss_obj, data_dss, "reactor") defaults = _apply_ordered_properties(_create_reactor(name; _to_kwargs(dss_obj)...), dss_obj) + nphases = defaults["phases"] + eng_obj = Dict{String,Any}( - "phases" => defaults["phases"], "configuration" => defaults["conn"], "bus" => _parse_busname(defaults["bus1"])[1], - "kvar" => defaults["kvar"], "status" => convert(Int, defaults["enabled"]), "source_id" => "reactor.$name", ) - connections_default = eng_obj["configuration"] == "wye" ? [collect(1:eng_obj["phases"])..., 0] : collect(1:eng_obj["phases"]) + connections_default = eng_obj["configuration"] == "wye" ? [collect(1:nphases)..., 0] : collect(1:nphases) eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], default=connections_default, check_length=false) # if the ground is used directly, register @@ -265,6 +278,11 @@ function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end + # TODO Check unit conversion on Gcap + Gcap = sum(defaults["kvar"]) / (nphases * 1e3 * (data_eng["settings"]["vbase"])^2) + + eng_obj["bs"] = diagm(0=>fill(Gcap, nphases)) + if import_all _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) end @@ -278,7 +296,6 @@ function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< eng_obj = Dict{String,Any}() nphases = defaults["phases"] - eng_obj["phases"] = nphases eng_obj["f_bus"] = _parse_busname(defaults["bus1"])[1] eng_obj["t_bus"] = _parse_busname(defaults["bus2"])[1] diff --git a/test/data/opendss/test2_master.dss b/test/data/opendss/test2_master.dss index 18299a754..0c138a407 100644 --- a/test/data/opendss/test2_master.dss +++ b/test/data/opendss/test2_master.dss @@ -10,32 +10,32 @@ Redirect test2_Linecodes.dss Compile test2_Loadshape.dss ! Lines -New Line.L1 bus1=b1 bus2=_b2 length=0.001 units=mi r1=0.001 r0=0.001 x1=0.01 x0=0.01 c1=0 c0=0 rg=0.01805 xg=0.155081 like=something +New Line.L1 like=something bus1=b1 bus2=_b2 length=0.001 units=mi r1=0.001 r0=0.001 x1=0.01 x0=0.01 c1=0 c0=0 rg=0.01805 xg=0.155081 New Line.L1-2 bus1=b3-1.2 bus2=b4.2 length=0.032175613 units=km Linecode=lc1 New Line.L2 bus1=b5 bus2=b6_check-chars length=0.013516796 units=none linecode=lc/2 New "Line.L3" phases=3 bus1=b7.1.2.3 bus2=b9.1.2.3 linecode=300 normamps=400 emergamps=600 faultrate=0.1 pctperm=20 repair=0 length=2.58 New "Line._L4" phases=3 bus1=b8.1.2.3 bus2=b10.1.2.3 linecode=300 normamps=400 emergamps=600 faultrate=0.1 pctperm=20 repair=0 length=1.73 switch=y New line.l5 phases=3 bus1=_b2.1.2.3 bus2=b7.1.2.3 linecode=lc8 New line.l6 phases=3 bus1=b1.1.2.3 bus2=b10.1.2.3 linecode=lc9 -new line.l7 bus1=_b2 bus2=b10 like=L2 linecode=lc10 test_param=100.0 +new line.l7 like=L2 bus1=_b2 bus2=b10 linecode=lc10 test_param=100.0 ! Loads New Load.ld1 phases=1 Bus1=b7.1.0 kv=0.208 status=variable model=1 conn=wye kW=3.89 pf=0.97 Vminpu=.88 New "Load.ld2" bus1=b9 phases=3 conn=Wye model=5 kV=24.9 kW=27 kvar=21 Vminpu=.85 New "Load.ld3" bus1=b10 phases=3 conn=Wye model=2 kV=24.9 kW=405 kvar=315 Vminpu=.85 yearly=(sngfile=load_profile.sng) -new load.ld4 bus1=b1 like=ld2 +new load.ld4 like=ld2 bus1=b1 New "Load.ld2" bus1=b9 phases=3 conn=Wye model=5 kV=24.9 kW=27 kvar=21 Vminpu=.85 daily=(file=load_profile.csv) ! Capacitors New "Capacitor.c1" bus1=b5 phases=3 kvar=[ 250] kv=20.0 New "Capacitor.c2" bus1=b8 phases=3 kvar=[ 500] kv=25.0 -new capacitor.c3 bus1=b1 like=c2 +new capacitor.c3 like=c2 bus1=b1 ! Reactors -New Reactor.reactor1 bus1=testsource bus2=b1 r=0 x=(1.05 0.75 0.001 5 * - - 125 15.0 / sqr *) normamps=400 emergamps=600 -new reactor.reactor2 bus1=_b2 bus2=b10 like=reactor1 +New Reactor.reactor1 bus1=testsource bus2=b1 r=0 x=(1.05 0.75 0.001 5 * - - 125 15.0 / sqr *) normamps=400 emergamps=600 +new reactor.reactor2 like=reactor1 bus1=_b2 bus2=b10 new reactor.reactor3 bus1=b9 kvar=10.0 -new reactor.reactor4 bus1=b8 like=reactor3 +new reactor.reactor4 like=reactor3 bus1=b8 ! Transformers New "Transformer.t1" phases=1 buses=[testsource, _b2, ] xfmrcode=t1 Xhl=1 @@ -51,9 +51,9 @@ New Transformer.t4 phases=3 windings=2 buses=(b8, b9.1.2.3.0) rneut=0 xneut=0 new transformer.t5 like=t4 buses=(b3-1, b5) ! Generators -New Generator.g1 Bus1=b1 kV= 150 kW=1 Model=3 Vpu=1.05 Maxkvar=70000 Minkvar=10000 ! kvar=50000 +New Generator.g1 Bus1=b1 kV= 150 kW=1 Model=3 Vpu=1.05 Maxkvar=70000 Minkvar=10000 ! kvar=50000 New Generator.g2 Bus1=testsource.1 kV= 120 kW=1 Phases=1 Model=3 Vpu=1.05 Maxkvar=70000 Minkvar=10000 ! kvar=60000 -new generator.g3 bus1=b7.1 phases=1 like=g2 +new generator.g3 like=g2 bus1=b7.1 phases=1 Set voltagebases=[115, 12.47, 0.48, 0.208] diff --git a/test/opendss.jl b/test/opendss.jl index 2b8eaaf21..17b148f74 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -113,11 +113,6 @@ eng = PMD.parse_file("../test/data/opendss/test2_master.dss"; data_model="engineering", import_all=true) pmd = PMD.parse_file("../test/data/opendss/test2_master.dss"; data_model="mathematical", import_all=true) - len = 0.013516796 - rmatrix=PMD._parse_matrix(Float64, "[1.5000 |0.200000 1.50000 |0.250000 0.25000 2.00000 ]") - xmatrix=PMD._parse_matrix(Float64, "[1.0000 |0.500000 0.50000 |0.500000 0.50000 1.000000 ]") - cmatrix = PMD._parse_matrix(Float64, "[8.0000 |-2.00000 9.000000 |-1.75000 -2.50000 8.00000 ]") - @testset "buscoords automatic parsing" begin @test all(haskey(bus, "lon") && haskey(bus, "lat") for bus in values(pmd["bus"]) if "bus_i" in 1:10) end @@ -146,12 +141,7 @@ end # TODO fix, the way we calculate voltage bases changed - # @testset "opendss parse generic branch values verification" begin - # basekv_br5 = pmd["bus"][string(pmd["branch"]["5"]["f_bus"])]["base_kv"] - # @test all(isapprox.(pmd["branch"]["5"]["br_r"], rmatrix * len / basekv_br5^2 * pmd["baseMVA"]; atol=1e-6)) - # @test all(isapprox.(pmd["branch"]["5"]["br_x"], xmatrix * len / basekv_br5^2 * pmd["baseMVA"]; atol=1e-6)) - # @test all(isapprox.(pmd["branch"]["5"]["b_fr"], diag(basekv_br5^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 * cmatrix * len / 1e9) / 2.0; atol=1e-6)) - + @testset "opendss parse like" begin # for i in [6, 3] # basekv_bri = pmd["bus"][string(pmd["branch"]["$i"]["f_bus"])]["base_kv"] # @test all(isapprox.(diag(pmd["branch"]["$i"]["b_fr"]), (3.4 * 2.0 + 1.6) / 3.0 * (basekv_bri^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 / 1e9) / 2.0; atol=1e-6)) @@ -160,20 +150,19 @@ # @test all(isapprox.(pmd["branch"]["9"]["br_r"].*(115/69)^2, diagm(0 => fill(6.3012e-8, 3)); atol=1e-12)) # @test all(isapprox.(pmd["branch"]["9"]["br_x"].*(115/69)^2, diagm(0 => fill(6.3012e-7, 3)); atol=1e-12)) - # for k in ["qd", "pd"] - # @test all(isapprox.(pmd["load"]["2"][k], pmd["load"]["2"][k]; atol=1e-12)) - # end + for k in ["pd_nom", "qd_nom"] + @test all(isapprox.(eng["load"]["ld2"][k], eng["load"]["ld4"][k]; atol=1e-12)) + end - # for k in ["gs", "bs"] - # @test all(isapprox.(pmd["shunt"]["1"][k], pmd["shunt"]["3"][k]; atol=1e-12)) - # @test all(isapprox.(pmd["shunt"]["4"][k], pmd["shunt"]["5"][k]; atol=1e-12)) - # end + # TODO fix shunt_capacitor and shunt_reactor eng parameters + # @test all(isapprox.(eng["shunt_capacitor"]["c1"]["bs"], eng["shunt_capacitor"]["c3"]["bs"]; atol=1e-12)) + # @test all(isapprox.(eng["shunt_reactor"]["reactor3"]["bs"], eng["shunt_reactor"]["reactor4"]["bs"]; atol=1e-12)) - # for k in keys(pmd["gen"]["1"]) - # if !(k in ["gen_bus", "index", "name", "source_id", "connections"]) - # @test all(isapprox.(pmd["gen"]["3"][k], pmd["gen"]["1"][k]; atol=1e-12)) - # end - # end + for (k,v) in eng["generator"]["g2"] + if !(k in ["bus", "source_id", "dss"]) + @test all(isapprox.(v, eng["generator"]["g3"][k]; atol=1e-12)) + end + end # for k in keys(pmd["branch"]["11"]) # if !(k in ["f_bus", "t_bus", "index", "name", "linecode", "source_id", "t_connections", "f_connections"]) @@ -188,14 +177,10 @@ # @test all(isapprox.(pmd["branch"]["5"][k].*mult, pmd["branch"]["2"][k]; atol=1e-12)) # end # end - # end + end @testset "opendss parse length units" begin - @test eng["line"]["l8"]["length"] == 1000.0 * len - basekv_br4 = pmd["bus"][string(pmd["branch"]["4"]["f_bus"])]["base_kv"] - @test all(isapprox.(pmd["branch"]["4"]["br_r"], rmatrix * len / basekv_br4^2 * pmd["baseMVA"]; atol=1e-6)) - @test all(isapprox.(pmd["branch"]["4"]["br_x"], xmatrix * len / basekv_br4^2 * pmd["baseMVA"]; atol=1e-6)) - @test all(isapprox.(pmd["branch"]["4"]["b_fr"], diag(basekv_br4^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 * cmatrix * len / 1e9) / 2.0 / 3; atol=1e-6)) + @test eng["line"]["l8"]["length"] == 1000.0 * 0.013516796 end @testset "opendss parse xycurve" begin @@ -252,9 +237,9 @@ @test pmd["shunt"]["2"]["source_id"] == "capacitor.c1" @test pmd["shunt"]["4"]["source_id"] == "reactor.reactor3" - @test pmd["branch"]["9"]["source_id"] == "line.l1" + @test pmd["branch"]["8"]["source_id"] == "line.l1" @test pmd["transformer"]["9"]["source_id"] == "_virtual_transformer.transformer.t4.1" # winding indicated by .1 - @test pmd["branch"]["10"]["source_id"] == "reactor.reactor1" + @test pmd["branch"]["25"]["source_id"] == "reactor.reactor1" @test pmd["gen"]["4"]["source_id"] == "_virtual_gen.vsource.source" @test pmd["gen"]["1"]["source_id"] == "generator.g2" From 99f7260cb3d012d3b58efdeb1a50772a939a427d Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Sat, 18 Apr 2020 13:32:13 +1000 Subject: [PATCH 138/224] 4w patch --- src/data_model/eng2math.jl | 1271 +++++++++++++++++------------------- src/data_model/units.jl | 26 +- src/data_model/utils.jl | 42 +- src/io/opendss.jl | 15 +- 4 files changed, 639 insertions(+), 715 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 154826876..ae3590e73 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -1,149 +1,31 @@ import LinearAlgebra: diagm -"items that are mapped one-to-one from engineering to math models" + const _1to1_maps = Dict{String,Vector{String}}( - "bus" => ["vm", "va", "dss"], - "line" => ["source_id", "dss"], - "transformer" => ["source_id", "dss"], - "switch" => ["status", "state", "source_id", "dss"], - "line_reactor" => ["source_id", "dss"], - "series_capacitor" => ["source_id", "dss"], - "shunt" => ["status", "gs", "bs", "source_id", "dss"], - "shunt_capacitor" => ["status", "bs", "source_id", "dss"], - "shunt_reactor" => ["status", "source_id", "dss"], - "load" => ["model", "configuration", "connections", "status", "source_id", "dss"], - "generator" => ["pg", "qg", "configuration", "source_id", "dss"], - "solar" => ["configuration", "source_id", "dss"], - "storage" => ["status", "energy", "ps", "qs", "source_id", "dss"], - "voltage_source" => ["source_id", "dss"], + "bus" => ["vm", "va", "vmin", "vmax", "terminals", "phases", "neutral"], + "load" => ["model", "configuration", "status", "source_id", "connections"], + "shunt_capacitor" => ["status", "source_id"], + "series_capacitor" => [], + "shunt" => ["status", "source_id"], + "shunt_reactor" => ["status", "source_id"], + "generator" => ["source_id", "configuration"], + "solar" => ["source_id", "configuration"], + "storage" => ["status", "source_id"], + "line" => ["source_id"], + "line_reactor" => ["source_id"], + "switch" => ["source_id", "state", "status"], + "line_reactor" => ["source_id"], + "transformer" => ["source_id"], + "voltage_source" => ["source_id"], ) -"list of nodal type elements in the engineering model" -const _node_elements = Vector{String}([ - "load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource" -]) - -"list of edge type elements in the engineering model" -const _edge_elements = Vector{String}([ - "line", "switch", "transformer", "line_reactor", "series_capacitor" -]) - -"list of time-series supported parameters that map one-to-one" -const _time_series_parameters = Dict{String,Dict{String,Tuple{Function, String}}}( - "switch" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "state" => (_no_conversion, "state"), - "c_rating" => (_no_conversion, "cm_ub"), - "s_rating" => (_no_conversion, "sm_ub"), - "br_r" => (_impedance_conversion, "rs"), - "br_x" => (_impedance_conversion, "xs"), - "g_fr" => (_admittance_conversion, "g_fr"), - "g_to" => (_admittance_conversion, "g_to"), - "b_fr" => (_admittance_conversion, "b_fr"), - "b_to" => (_admittance_conversion, "b_to") - ), - "fuse" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "state" => (_no_conversion, "state"), - "c_rating" => (_no_conversion, "cm_ub"), - "s_rating" => (_no_conversion, "sm_ub"), - "br_r" => (_impedance_conversion, "rs"), - "br_x" => (_impedance_conversion, "xs"), - "g_fr" => (_admittance_conversion, "g_fr"), - "g_to" => (_admittance_conversion, "g_to"), - "b_fr" => (_admittance_conversion, "b_fr"), - "b_to" => (_admittance_conversion, "b_to") - ), - "line" => Dict{String,Tuple{Function, String}}( - "br_status" => (_no_conversion, "status"), - "c_rating" => (_no_conversion, "cm_ub"), - "s_rating" => (_no_conversion, "sm_ub"), - "br_r" => (_impedance_conversion, "rs"), - "br_x" => (_impedance_conversion, "xs"), - "g_fr" => (_admittance_conversion, "g_fr"), - "g_to" => (_admittance_conversion, "g_to"), - "b_fr" => (_admittance_conversion, "b_fr"), - "b_to" => (_admittance_conversion, "b_to") - ), - "transformer" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - # TODO need to figure out how to convert values for time series for decomposed transformers - ), - "bus" => Dict{String,Tuple{Function, String}}( - "bus_type" => (_bus_type_conversion, "status"), - "vmin" => (_no_conversion, "vm_lb"), - "vmax" => (_no_conversion, "vm_ub"), - "vm" => (_no_conversion, "vm"), - "va" => (_no_conversion, "va") - ), - "shunt" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "gs" => (_no_conversion, "gs"), - "bs" => (_no_conversion, "bs"), - ), - "shunt_capacitor" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "gs" => (_no_conversion, "gs"), - "bs" => (_no_conversion, "bs"), - ), - "shunt_reactor" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "gs" => (_no_conversion, "gs"), - "bs" => (_no_conversion, "bs"), - ), - "load" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "pd_ref" => (_no_conversion, "pd_nom"), - "qd_ref" => (_no_conversion, "qd_nom"), - ), - "generator" => Dict{String,Tuple{Function, String}}( - "gen_status" => (_no_conversion, "status"), - "pg" => (_no_conversion, "pg"), - "qg" => (_no_conversion, "qg"), - "vg" => (_vnom_conversion, "vg"), - "pmin" => (_no_conversion, "pg_lb"), - "pmax" => (_no_conversion, "pg_ub"), - "qmin" => (_no_conversion, "qg_lb"), - "qmax" => (_no_conversion, "qg_ub"), - ), - "solar" => Dict{String,Tuple{Function, String}}( - "gen_status" => (_no_conversion, "status"), - "pg" => (_no_conversion, "pg"), - "qg" => (_no_conversion, "qg"), - "vg" => (_vnom_conversion, "vg"), - "pmin" => (_no_conversion, "pg_lb"), - "pmax" => (_no_conversion, "pg_ub"), - "qmin" => (_no_conversion, "qg_lb"), - "qmax" => (_no_conversion, "qg_ub"), - ), - "storage" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "energy" => (_no_conversion, "energy"), - "energy_rating" => (_no_conversion, "energy_ub"), - "charge_rating" => (_no_conversion, "charge_ub"), - "discharge_rating" => (_no_conversion, "discharge_ub"), - "charge_efficiency" => (_no_conversion, "charge_efficiency"), - "discharge_efficiency" => (_no_conversion, "discharge_efficiency"), - "thermal_rating" => (_no_conversion, "cm_ub"), - "qmin" => (_no_conversion, "qs_lb"), - "qmax" => (_no_conversion, "qs_ub"), - "r" => (_no_conversion, "rs"), - "x" => (_no_conversion, "xs"), - "p_loss" => (_no_conversion, "pex"), - "q_loss" => (_no_conversion, "qex"), - "ps" => (_no_conversion, "ps"), - "qs" => (_no_conversion, "qs"), - ), - "voltage_source" => Dict{String,Tuple{Function, String}}( - "gen_status" => (_no_conversion, "status"), - "vm" => (_no_conversion, "vm"), - "va" => (_angle_shift_conversion, "va"), - ), +const _node_elements = ["load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource"] -) +const _edge_elements = ["line", "switch", "transformer", "line_reactor", "series_capacitor"] -"base function for converting engineering model to mathematical model" -function _map_eng2math(data_eng; kron_reduced::Bool=true) + +"" +function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, adjust_bounds::Bool=true) @assert get(data_eng, "data_model", "mathematical") == "engineering" data_math = Dict{String,Any}( @@ -153,7 +35,9 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) "settings" => data_eng["settings"], ) - data_math["conductors"] = kron_reduced ? 3 : 4 + #TODO the PM tests break for branches which are not of the size indicated by conductors; + # for now, set to 1 to prevent this from breaking when not kron-reduced + data_math["conductors"] = kron_reduced ? 3 : 1 data_math["basekv"] = data_eng["settings"]["vbase"] data_math["baseMVA"] = data_eng["settings"]["sbase"]*data_eng["settings"]["v_var_scalar"]/1E6 @@ -165,56 +49,103 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _init_base_components!(data_math) - # convert buses _map_eng2math_bus!(data_math, data_eng; kron_reduced=kron_reduced) - # convert edges - _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) - # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO build conversion for series capacitors - - # convert nodes _map_eng2math_load!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) + + _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + + _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) + # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO + _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) + + _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced) + if !adjust_bounds + # _find_new_bounds(data_math) # TODO + end return data_math end -"converts engineering bus components into mathematical bus components" +"" +function _init_math_obj(obj_type::String, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} + math_obj = Dict{String,Any}() + + for key in _1to1_maps[obj_type] + if haskey(eng_obj, key) + math_obj[key] = eng_obj[key] + end + end + + math_obj["index"] = index + + return math_obj +end + + +"" +function _init_base_components!(data_math::Dict{String,<:Any}) + for key in ["bus", "load", "shunt", "gen", "branch", "switch", "transformer", "storage", "dcline"] + if !haskey(data_math, key) + data_math[key] = Dict{String,Any}() + end + end +end + + +"" +function _init_lookup!(data_math::Dict{String,<:Any}) + + + for key in keys(_1to1_maps) + if !haskey(data_math["lookup"], key) + data_math["lookup"][key] = Dict{Any,Int}() + end + end +end + + +"" function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) - phases = get(eng_obj, "phases", [1, 2, 3]) - neutral = get(eng_obj, "neutral", 4) + # TODO fix vnom terminals = eng_obj["terminals"] nconductors = data_math["conductors"] - @assert all(t in [phases..., neutral] for t in terminals) - - math_obj = _init_math_obj("bus", name, eng_obj, length(data_math["bus"])+1) + math_obj = _init_math_obj("bus", eng_obj, length(data_math["bus"])+1) + math_obj["name"] = name math_obj["bus_i"] = math_obj["index"] - math_obj["bus_type"] = _bus_type_conversion(data_eng, eng_obj, "status") - - if haskey(eng_obj, "vm") - math_obj["vm"] = eng_obj["vm"] - end - if haskey(eng_obj, "va") - math_obj["va"] = eng_obj["va"] + math_obj["bus_type"] = eng_obj["status"] == 1 ? 1 : 4 + + # take care of grounding; convert to shunt if lossy + grounded_perfect, shunts = _convert_grounding(eng_obj["terminals"], eng_obj["grounded"], eng_obj["rg"], eng_obj["xg"]) + math_obj["grounded"] = grounded_perfect + to_sh = [] + for (sh_connections, sh_y) in shunts + sh_index = length(data_math["shunt"])+1 + data_math["shunt"]["$sh_index"] = Dict( + "index" => sh_index, + "shunt_bus" => bus_index, + "connections" => sh_connections, + "gs" => real.(sh_y), + "bs" => real.(sh_y), + ) + push!(to_sh, "shunt.$sh_index") end - math_obj["vmin"] = get(eng_obj, "vm_lb", fill(0.0, length(terminals))) - math_obj["vmax"] = get(eng_obj, "vm_ub", fill(Inf, length(terminals))) + math_obj["vmin"] = get(eng_obj, "vmin", fill(0.0, length(terminals))) + math_obj["vmax"] = get(eng_obj, "vmax", fill(Inf, length(terminals))) math_obj["base_kv"] = data_eng["settings"]["vbase"] @@ -240,305 +171,318 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, :to => "bus.$(math_obj["index"])", :unmap_function => :_map_math2eng_bus!, ) + end +end + + +"" +function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("load", eng_obj, length(data_math["load"])+1) + math_obj["name"] = name + + connections = eng_obj["connections"] + nconductors = data_math["conductors"] + + math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + + math_obj["status"] = eng_obj["status"] + + math_obj["pd"] = eng_obj["pd_nom"] + math_obj["qd"] = eng_obj["qd_nom"] + + math_obj["configuration"] = eng_obj["configuration"] - # time series - for (fr, (f, to)) in _time_series_parameters["bus"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj, "bus", "$(math_obj["index"])", fr, to, f) + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pd", "qd"], connections[connections.!=kr_neutral], kr_phases) + else + _pad_properties_delta!(math_obj, ["pd", "qd"], connections, kr_phases) end + else + math_obj["connections"] = connections end - end -end + math_obj["vnom_kv"] = eng_obj["vnom"] -"converts engineering lines into mathematical branches" -function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) - _apply_linecode!(eng_obj, data_eng) + data_math["load"]["$(math_obj["index"])"] = math_obj - math_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "load.$(math_obj["index"])", + :unmap_function => :_map_math2eng_load!, + ) + end +end - nphases = length(eng_obj["f_connections"]) - nconductors = data_math["conductors"] - math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] +"" +function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "shunt_capacitor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt_capacitor", eng_obj, length(data_math["shunt"])+1) - math_obj["br_r"] = _impedance_conversion(data_eng, eng_obj, "rs") - math_obj["br_x"] = _impedance_conversion(data_eng, eng_obj, "xs") + # TODO change to new capacitor shunt calc logic + math_obj["name"] = name + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["g_fr"] = _admittance_conversion(data_eng, eng_obj, "g_fr") - math_obj["g_to"] = _admittance_conversion(data_eng, eng_obj, "g_to") + f_terminals = eng_obj["f_connections"] + t_terminals = eng_obj["t_connections"] - math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") - math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") + # TODO change in OpenDS parser + b = diag(eng_obj["bs"]) - math_obj["angmin"] = fill(-60.0, nphases) - math_obj["angmax"] = fill( 60.0, nphases) + # convert to a shunt matrix + terminals, B = _calc_shunt(f_terminals, t_terminals, b) - math_obj["transformer"] = false - math_obj["shift"] = zeros(nphases) - math_obj["tap"] = ones(nphases) + # if one terminal is ground (0), reduce shunt addmittance matrix + terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) - f_bus = data_eng["bus"][eng_obj["f_bus"]] - t_bus = data_eng["bus"][eng_obj["t_bus"]] + math_obj["bs"] = B + math_obj["gs"] = zeros(size(B)) if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") filter = _kron_reduce_branch!(math_obj, - ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], + Vector{String}([]), ["gs", "bs"], eng_obj["f_connections"], kr_neutral ) - _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) connections = eng_obj["f_connections"][filter] - _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) else - math_obj["f_connections"] = eng_obj["f_connections"] - math_obj["f_connections"] = eng_obj["t_connections"] + math_obj["connections"] = terminals end - math_obj["switch"] = false - - math_obj["br_status"] = eng_obj["status"] - - data_math["branch"]["$(math_obj["index"])"] = math_obj + data_math["shunt"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => "branch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_line!, + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_shunt_capacitor!, ) - - # time series - # TODO - for (fr, to) in zip(["status"], ["status"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) - end - end end end -"converts engineering n-winding transformers into mathematical ideal 2-winding lossless transformer branches and impedance branches to represent the loss model" -function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "transformer", Dict{Any,Dict{String,Any}}()) - # Build map first, so we can update it as we decompose the transformer - map_idx = length(data_math["map"])+1 - data_math["map"][map_idx] = Dict{Symbol,Any}( - :from => name, - :to => Vector{String}([]), - :unmap_function => :_map_math2eng_transformer!, - ) - - to_map = data_math["map"][map_idx][:to] - - _apply_xfmrcode!(eng_obj, data_eng) - - vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] - snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] +"" +function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "shunt", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt", eng_obj, length(data_math["shunt"])+1) - nrw = length(eng_obj["bus"]) + # TODO change to new capacitor shunt calc logic + math_obj["name"] = name + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - # calculate zbase in which the data is specified, and convert to SI - zbase = (vnom.^2) ./ snom + math_obj["gs"] = eng_obj["g_sh"] + math_obj["bs"] = eng_obj["b_sh"] - # x_sc is specified with respect to first winding - x_sc = eng_obj["xsc"] .* zbase[1] + if kron_reduced + filter = _kron_reduce_branch!(math_obj, + Vector{String}([]), ["gs", "bs"], + eng_obj["connections"], kr_neutral + ) + connections = eng_obj["connections"][filter] + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + else + math_obj["connections"] = eng_obj["connections"] + end - # rs is specified with respect to each winding - r_s = eng_obj["rs"] .* zbase + data_math["shunt"]["$(math_obj["index"])"] = math_obj - g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 - b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_capacitor!, + ) + end +end - # data is measured externally, but we now refer it to the internal side - ratios = vnom/data_eng["settings"]["v_var_scalar"] - x_sc = x_sc./ratios[1]^2 - r_s = r_s./ratios.^2 - g_sh = g_sh*ratios[1]^2 - b_sh = b_sh*ratios[1]^2 - # convert x_sc from list of upper triangle elements to an explicit dict - y_sh = g_sh + im*b_sh - z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) +"" +function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt_reactor", eng_obj, length(data_math["shunt"])+1) - transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh) + nphases = eng_obj["phases"] + connections = eng_obj["connections"] + nconductors = data_math["conductors"] - for w in 1:nrw - # 2-WINDING TRANSFORMER - # make virtual bus and mark it for reduction - tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] - transformer_2wa_obj = Dict{String,Any}( - "name" => "_virtual_transformer.$name.$w", - "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", - "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], - "t_bus" => transformer_t_bus_w[w], - "tm_nom" => tm_nom, - "f_connections" => eng_obj["connections"][w], - "t_connections" => collect(1:4), - "configuration" => eng_obj["configuration"][w], - "polarity" => eng_obj["polarity"][w], - "tm" => eng_obj["tm"][w], - "fixed" => eng_obj["fixed"][w], - "index" => length(data_math["transformer"])+1 - ) + Gcap = sum(eng_obj["kvar"]) / (nphases * 1e3 * (data_math["basekv"])^2) - for prop in ["tm_min", "tm_max", "tm_step"] - if haskey(eng_obj, prop) - transformer_2wa_obj[prop] = eng_obj[prop][w] - end - end + math_obj["name"] = name + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - if kron_reduced - # TODO fix how padding works, this is a workaround to get bank working - if all(eng_obj["configuration"] .== "wye") - f_connections = transformer_2wa_obj["f_connections"] - _pad_properties!(transformer_2wa_obj, ["tm_min", "tm_max", "tm", "fixed"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) + math_obj["gs"] = fill(0.0, nphases, nphases) + math_obj["bs"] = diagm(0=>fill(Gcap, nphases)) - transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] - end + if kron_reduced + if eng_obj["configuration"] == "wye" + _pad_properties!(math_obj, ["gs", "bs"], connections[1:end-1], kr_phases) + else + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) end + end - data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj + data_math["shunt"]["$(math_obj["index"])"] = math_obj - push!(to_map, "transformer.$(transformer_2wa_obj["index"])") - end + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_shunt_reactor!, + ) end end -"converts engineering switches into mathematical switches and (if neeed) impedance branches to represent loss model" -function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - # TODO enable real switches (right now only using vitual lines) - for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) - nphases = length(eng_obj["f_connections"]) +"" +function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) + for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) + math_obj = _init_math_obj("generator", eng_obj, length(data_math["gen"])+1) + + phases = eng_obj["phases"] + connections = eng_obj["connections"] nconductors = data_math["conductors"] - math_obj = _init_math_obj("switch", name, eng_obj, length(data_math["switch"])+1) + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["name"] = name + math_obj["gen_status"] = eng_obj["status"] - math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["pg"] = eng_obj["kw"] + math_obj["qg"] = eng_obj["kvar"] + math_obj["vg"] = eng_obj["kv"] ./ data_math["basekv"] - # OPF bounds - for (fr_key, to_key) in zip(["cm_ub"], ["c_rating"]) - if haskey(eng_obj, fr_key) - math_obj[to_key] = eng_obj[fr_key] - end - end + math_obj["qmin"] = eng_obj["kvar_min"] + math_obj["qmax"] = eng_obj["kvar_max"] - map_to = "switch.$(math_obj["index"])" + math_obj["pmax"] = eng_obj["kw"] + math_obj["pmin"] = zeros(phases) + + _add_gen_cost_model!(math_obj, eng_obj) + + math_obj["configuration"] = eng_obj["configuration"] - # time series - # TODO switch time series - for (fr, to) in zip(["status", "state"], ["status", "state"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "switch", "$(math_obj["index"])", to) + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) + else + _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) end + else + math_obj["connections"] = connections end - if any(haskey(eng_obj, k) for k in ["rs", "xs", "linecode"]) - # build virtual bus + # if PV generator mode convert attached bus to PV bus + if eng_obj["control_model"] == 3 + data_math["bus"]["$(data_math["bus_lookup"][eng_obj["bus"]])"]["bus_type"] = 2 + end - f_bus = data_math["bus"]["$(math_obj["f_bus"])"] + data_math["gen"]["$(math_obj["index"])"] = math_obj - bus_obj = Dict{String,Any}( - "name" => "_virtual_bus.switch.$name", - "bus_i" => length(data_math["bus"])+1, - "bus_type" => 1, - "vmin" => f_bus["vmin"], - "vmax" => f_bus["vmax"], - "base_kv" => f_bus["base_kv"], - "status" => 1, - "index" => length(data_math["bus"])+1, - ) + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "gen.$(math_obj["index"])", + :unmap_function => :_map_math2eng_generator!, + ) + end +end - #= TODO enable real switches - # math_obj["t_bus"] = bus_obj["bus_i"] - # data_math["bus"]["$(bus_obj["index"])"] = bus_obj - =# - # build virtual branch - _apply_linecode!(eng_obj, data_eng) +"" +function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) + for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("solar", eng_obj, length(data_math["gen"])+1) - branch_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) + connections = eng_obj["connections"] + nconductors = data_math["conductors"] - _branch_obj = Dict{String,Any}( - "name" => "_virtual_branch.switch.$name", - "source_id" => "_virtual_branch.switch.$name", - # "f_bus" => bus_obj["bus_i"], # TODO enable real switches - "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], - "br_r" => _impedance_conversion(data_eng, eng_obj, "rs"), - "br_x" => _impedance_conversion(data_eng, eng_obj, "xs"), - "g_fr" => _admittance_conversion(data_eng, eng_obj, "g_fr"), - "g_to" => _admittance_conversion(data_eng, eng_obj, "g_to"), - "b_fr" => _admittance_conversion(data_eng, eng_obj, "b_fr"), - "b_to" => _admittance_conversion(data_eng, eng_obj, "b_to"), - "angmin" => fill(-60.0, nphases), - "angmax" => fill( 60.0, nphases), - "transformer" => false, - "shift" => zeros(nphases), - "tap" => ones(nphases), - "switch" => false, - "br_status" => 1, - ) + math_obj["name"] = name + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] - merge!(branch_obj, _branch_obj) + math_obj["pg"] = eng_obj["kva"] + math_obj["qg"] = eng_obj["kvar"] + math_obj["vg"] = eng_obj["kv"] - if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") - filter = _kron_reduce_branch!(branch_obj, - ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], - eng_obj["f_connections"], kr_neutral - ) - _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) - connections = eng_obj["f_connections"][filter] - _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + math_obj["pmin"] = get(eng_obj, "minkva", zeros(size(eng_obj["kva"]))) + math_obj["pmax"] = get(eng_obj, "maxkva", eng_obj["kva"]) + + math_obj["qmin"] = get(eng_obj, "minkvar", -eng_obj["kvar"]) + math_obj["qmax"] = get(eng_obj, "maxkvar", eng_obj["kvar"]) + + _add_gen_cost_model!(math_obj, eng_obj) + + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) else - branch_obj["f_connections"] = eng_obj["f_connections"] - branch_obj["f_connections"] = eng_obj["t_connections"] + _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) end + else + math_obj["connections"] = connections + end - data_math["branch"]["$(branch_obj["index"])"] = branch_obj + data_math["gen"]["$(math_obj["index"])"] = math_obj - # build switch - switch_obj = Dict{String,Any}( - "name" => name, - "source_id" => eng_obj["source_id"], - "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => bus_obj["bus_i"], - "status" => eng_obj["status"], - "index" => length(data_math["switch"])+1 - ) + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => "solar.$name", + :to => "gen.$(math_obj["index"])", + :unmap_function => :_map_math2eng_solar!, + ) + end +end - # data_math["switch"]["$(switch_obj["index"])"] = switch_obj - map_to = ["branch.$(branch_obj["index"])"] - # map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] # TODO enable real switches +"" +function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) + for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("storage", eng_obj, length(data_math["storage"])+1) + + connections = eng_obj["connections"] + nconductors = data_math["conductors"] + + math_obj["name"] = name + math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + + math_obj["energy"] = eng_obj["kwhstored"] / 1e3 + math_obj["energy_rating"] = eng_obj["kwhrated"] / 1e3 + math_obj["charge_rating"] = eng_obj["%charge"] * eng_obj["kwrated"] / 1e3 / 100.0 + math_obj["discharge_rating"] = eng_obj["%discharge"] * eng_obj["kwrated"] / 1e3 / 100.0 + math_obj["charge_efficiency"] = eng_obj["%effcharge"] / 100.0 + math_obj["discharge_efficiency"] = eng_obj["%effdischarge"] / 100.0 + math_obj["thermal_rating"] = eng_obj["kva"] ./ 1e3 + math_obj["qmin"] = -eng_obj["kvar"] ./ 1e3 + math_obj["qmax"] = eng_obj["kvar"] ./ 1e3 + math_obj["r"] = eng_obj["%r"] ./ 100.0 + math_obj["x"] = eng_obj["%x"] ./ 100.0 + math_obj["p_loss"] = eng_obj["%idlingkw"] .* eng_obj["kwrated"] ./ 1e3 + math_obj["q_loss"] = eng_obj["%idlingkvar"] * sum(eng_obj["kvar"]) / 1e3 + + math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["kva"]))) + math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["kva"]))) + + if kron_reduced + _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, kr_phases) end - # data_math["switch"]["$(math_obj["index"])"] = math_obj # TODO enable real switches + data_math["storage"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => map_to, - :unmap_function => :_map_math2eng_switch!, + :to => "storage.$(math_obj["index"])", + :unmap_function => :_map_math2eng_storage!, ) - end end -"converts engineering line reactors into mathematical branches" -function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - # TODO support line reactors natively, currently treated like branches - for (name, eng_obj) in get(data_eng, "line_reactor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) - math_obj["name"] = "_virtual_branch.$(eng_obj["source_id"])" +"" +function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) + _apply_linecode!(eng_obj, data_eng) + + math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) + math_obj["name"] = name nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] @@ -546,14 +490,14 @@ function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Di math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - math_obj["br_r"] = _impedance_conversion(data_eng, eng_obj, "rs") - math_obj["br_x"] = _impedance_conversion(data_eng, eng_obj, "xs") + math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] + math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] - math_obj["g_fr"] = _admittance_conversion(data_eng, eng_obj, "g_fr") - math_obj["g_to"] = _admittance_conversion(data_eng, eng_obj, "g_to") + math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) + math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) - math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") - math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") + math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) + math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) math_obj["angmin"] = fill(-60.0, nphases) math_obj["angmax"] = fill( 60.0, nphases) @@ -576,7 +520,7 @@ function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Di _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) else math_obj["f_connections"] = eng_obj["f_connections"] - math_obj["f_connections"] = eng_obj["t_connections"] + math_obj["t_connections"] = eng_obj["t_connections"] end math_obj["switch"] = false @@ -588,393 +532,346 @@ function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Di data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, :to => "branch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_line_reactor!, + :unmap_function => :_map_math2eng_line!, ) - - # time series - # TODO - for (fr, to) in zip(["status"], ["status"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) - end - end end end -"converts engineering generic shunt components into mathematical shunt components" -function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt", name, eng_obj, length(data_math["shunt"])+1) - - # TODO change to new capacitor shunt calc logic - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - - math_obj["gs"] = eng_obj["gs"] - math_obj["bs"] = eng_obj["bs"] +"" +function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + # TODO support line reactors natively, currently treated like branches + for (name, eng_obj) in get(data_eng, "line_reactor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) + math_obj["name"] = "_virtual_branch.$(eng_obj["source_id"])" - if kron_reduced - filter = _kron_reduce_branch!(math_obj, - Vector{String}([]), ["gs", "bs"], - eng_obj["connections"], kr_neutral - ) - connections = eng_obj["connections"][filter] - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) - else - math_obj["connections"] = eng_obj["connections"] - end + nphases = length(eng_obj["f_connections"]) + nconductors = data_math["conductors"] - data_math["shunt"]["$(math_obj["index"])"] = math_obj + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_capacitor!, - ) + math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] + math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] - # time series - for (fr, (f, to)) in _time_series_parameters["shunt"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) - end - end - end -end + math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) + math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) + math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) + math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) -"converts engineering shunt capacitors into mathematical shunts" -function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt_capacitor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt_capacitor", name, eng_obj, length(data_math["shunt"])+1) + math_obj["angmin"] = fill(-60.0, nphases) + math_obj["angmax"] = fill( 60.0, nphases) - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["transformer"] = false + math_obj["shift"] = zeros(nphases) + math_obj["tap"] = ones(nphases) - math_obj["gs"] = zeros(size(eng_obj["bs"])) + f_bus = data_eng["bus"][eng_obj["f_bus"]] + t_bus = data_eng["bus"][eng_obj["t_bus"]] if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") filter = _kron_reduce_branch!(math_obj, - Vector{String}([]), ["gs", "bs"], + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], eng_obj["f_connections"], kr_neutral ) + _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) connections = eng_obj["f_connections"][filter] - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) else - math_obj["connections"] = eng_obj["connections"] - end - - data_math["shunt"]["$(math_obj["index"])"] = math_obj - - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_shunt_capacitor!, - ) - - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["shunt_capacitor"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) - end + math_obj["f_connections"] = eng_obj["f_connections"] + math_obj["t_connections"] = eng_obj["t_connections"] end - end -end - - -"converts engineering shunt reactors into mathematical shunts" -function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt_reactor", name, eng_obj, length(data_math["shunt"])+1) - - connections = eng_obj["connections"] - nconductors = data_math["conductors"] - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - - math_obj["gs"] = fill(0.0, size(eng_obj["bs"])...) + math_obj["switch"] = false - if kron_reduced - if eng_obj["configuration"] == "wye" - _pad_properties!(math_obj, ["gs", "bs"], connections[1:end-1], kr_phases) - else - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) - end - end + math_obj["br_status"] = eng_obj["status"] - data_math["shunt"]["$(math_obj["index"])"] = math_obj + data_math["branch"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_shunt_reactor!, + :to => "branch.$(math_obj["index"])", + :unmap_function => :_map_math2eng_line_reactor!, ) - - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["shunt_reactor"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) - end - end end end -"converts engineering load components into mathematical load components" -function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("load", name, eng_obj, length(data_math["load"])+1) +"" +function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) + # TODO enable real switches (right now only using vitual lines) + for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) + nphases = length(eng_obj["f_connections"]) + nconductors = data_math["conductors"] - connections = eng_obj["connections"] + if lossless + math_obj = _init_math_obj("switch", eng_obj, length(data_math["switch"])+1) + math_obj["name"] = name - math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["pd"] = eng_obj["pd_nom"] - math_obj["qd"] = eng_obj["qd_nom"] + data_math["switch"]["$(math_obj["index"])"] = math_obj - if kron_reduced - if math_obj["configuration"]=="wye" - @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pd", "qd"], connections[connections.!=kr_neutral], kr_phases) - else - _pad_properties_delta!(math_obj, ["pd", "qd"], connections, kr_phases) - end + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "switch.$(math_obj["index"])", + :unmap_function => :_map_math2eng_switch!, + ) else - math_obj["connections"] = connections - end + # build virtual bus + f_bus = data_math["bus"]["$(data_math["bus_lookup"][eng_obj["f_bus"]])"] - math_obj["vnom_kv"] = eng_obj["vnom"] + bus_obj = Dict{String,Any}( + "name" => "_virtual_bus.switch.$name", + "bus_i" => length(data_math["bus"])+1, + "bus_type" => 1, + "vmin" => f_bus["vmin"], + "vmax" => f_bus["vmax"], + "base_kv" => f_bus["base_kv"], + "status" => 1, + "index" => length(data_math["bus"])+1, + ) - data_math["load"]["$(math_obj["index"])"] = math_obj + # data_math["bus"]["$(bus_obj["index"])"] = bus_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "load.$(math_obj["index"])", - :unmap_function => :_map_math2eng_load!, - ) + # build virtual branch + if haskey(eng_obj, "linecode") + linecode = data_eng["linecode"][eng_obj["linecode"]] - # time series - for (fr, (f, to)) in _time_series_parameters["load"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "load", "$(math_obj["index"])", to) + for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] + if !haskey(eng_obj, property) && haskey(linecode, property) + eng_obj[property] = linecode[property] + end + end end - end - end -end - - -"converts engineering generators into mathematical generators" -function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) - math_obj = _init_math_obj("generator", name, eng_obj, length(data_math["gen"])+1) - - phases = eng_obj["phases"] - connections = eng_obj["connections"] - nconductors = data_math["conductors"] - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] + branch_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) - math_obj["vg"] = eng_obj["vg"] ./ data_math["basekv"] - - math_obj["qmin"] = eng_obj["qg_lb"] - math_obj["qmax"] = eng_obj["qg_ub"] - - math_obj["pmax"] = eng_obj["pg"] - math_obj["pmin"] = zeros(phases) + Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) + Zbase = 1 - _add_gen_cost_model!(math_obj, eng_obj) + _branch_obj = Dict{String,Any}( + "name" => "_virtual_branch.switch.$name", + "source_id" => "_virtual_branch.switch.$name", + # "f_bus" => bus_obj["bus_i"], # TODO enable real switches + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], + "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, + "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, + "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9), + "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9), + "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9), + "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9), + "angmin" => fill(-60.0, nphases), + "angmax" => fill( 60.0, nphases), + "transformer" => false, + "shift" => zeros(nphases), + "tap" => ones(nphases), + "switch" => false, + "br_status" => 1, + ) - math_obj["configuration"] = eng_obj["configuration"] + merge!(branch_obj, _branch_obj) - if kron_reduced - if math_obj["configuration"]=="wye" - @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) + if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") + filter = _kron_reduce_branch!(branch_obj, + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], + eng_obj["f_connections"], kr_neutral + ) + _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) + connections = eng_obj["f_connections"][filter] + _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) else - _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) + branch_obj["f_connections"] = eng_obj["f_connections"] + branch_obj["f_connections"] = eng_obj["t_connections"] end - else - math_obj["connections"] = connections - end - # if PV generator mode convert attached bus to PV bus - if eng_obj["control_mode"] == 3 - data_math["bus"]["$(data_math["bus_lookup"][eng_obj["bus"]])"]["bus_type"] = 2 - end + data_math["branch"]["$(branch_obj["index"])"] = branch_obj - data_math["gen"]["$(math_obj["index"])"] = math_obj + # build switch + switch_obj = Dict{String,Any}( + "name" => name, + "source_id" => eng_obj["source_id"], + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => bus_obj["bus_i"], + "status" => eng_obj["status"], + "index" => length(data_math["switch"])+1 + ) - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_generator!, - ) + # data_math["switch"]["$(switch_obj["index"])"] = switch_obj - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["generator"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "gen", "$(math_obj["index"])", to) - end + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + # :to_id => ["switch.$(switch_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], # TODO enable real switches + :to => ["branch.$(branch_obj["index"])"], + :unmap_function => :_map_math2eng_switch!, + ) end end end -"converts engineering solar components into mathematical generators" -function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("solar", name, eng_obj, length(data_math["gen"])+1) - - connections = eng_obj["connections"] - nconductors = data_math["conductors"] - - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] +"" +function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "transformer", Dict{Any,Dict{String,Any}}()) + # Build map first, so we can update it as we decompose the transformer + map_idx = length(data_math["map"])+1 + data_math["map"][map_idx] = Dict{Symbol,Any}( + :from => name, + :to => Vector{String}([]), + :unmap_function => :_map_math2eng_transformer!, + ) - math_obj["pg"] = eng_obj["kva"] - math_obj["qg"] = eng_obj["kvar"] - math_obj["vg"] = eng_obj["kv"] + to_map = data_math["map"][map_idx][:to] - math_obj["pmin"] = get(eng_obj, "minkva", zeros(size(eng_obj["kva"]))) - math_obj["pmax"] = get(eng_obj, "maxkva", eng_obj["kva"]) + _apply_xfmrcode!(eng_obj, data_eng) - math_obj["qmin"] = get(eng_obj, "minkvar", -eng_obj["kvar"]) - math_obj["qmax"] = get(eng_obj, "maxkvar", eng_obj["kvar"]) + vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] + snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] - _add_gen_cost_model!(math_obj, eng_obj) + nrw = length(eng_obj["bus"]) - if kron_reduced - if math_obj["configuration"]=="wye" - @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) - else - _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) - end - else - math_obj["connections"] = connections - end + # calculate zbase in which the data is specified, and convert to SI + zbase = (vnom.^2) ./ snom - data_math["gen"]["$(math_obj["index"])"] = math_obj + # x_sc is specified with respect to first winding + x_sc = eng_obj["xsc"] .* zbase[1] - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "solar.$name", - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_solar!, - ) + # rs is specified with respect to each winding + r_s = eng_obj["rs"] .* zbase - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["solar"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj, "gen", "$(math_obj["index"])", fr, to, f) - end - end - end -end + g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 + b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 + # data is measured externally, but we now refer it to the internal side + ratios = vnom/data_eng["settings"]["v_var_scalar"] + x_sc = x_sc./ratios[1]^2 + r_s = r_s./ratios.^2 + g_sh = g_sh*ratios[1]^2 + b_sh = b_sh*ratios[1]^2 -"converts engineering storage into mathematical storage" -function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("storage", name, eng_obj, length(data_math["storage"])+1) + # convert x_sc from list of upper triangle elements to an explicit dict + y_sh = g_sh + im*b_sh + z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) - connections = eng_obj["connections"] - nconductors = data_math["conductors"] + nphases = length(eng_obj["fixed"][1]) + transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh, nphases=nphases, kron_reduced=kron_reduced) - math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + for w in 1:nrw + # 2-WINDING TRANSFORMER + # make virtual bus and mark it for reduction + tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] + transformer_2wa_obj = Dict{String,Any}( + "name" => "_virtual_transformer.$name.$w", + "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", + "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], + "t_bus" => transformer_t_bus_w[w], + "tm_nom" => tm_nom, + "f_connections" => eng_obj["connections"][w], + "t_connections" => collect(1:nphases+1), + "configuration" => eng_obj["configuration"][w], + "polarity" => eng_obj["polarity"][w], + "tm" => eng_obj["tm"][w], + "fixed" => eng_obj["fixed"][w], + "index" => length(data_math["transformer"])+1 + ) - # needs to be in units MW - math_obj["energy"] = eng_obj["energy"] * data_eng["settings"]["v_var_scalar"] / 1e6 - math_obj["energy_rating"] = eng_obj["energy_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 - math_obj["charge_rating"] = eng_obj["charge_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 - math_obj["discharge_rating"] = eng_obj["discharge_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 - math_obj["charge_efficiency"] = eng_obj["charge_efficiency"] / 100.0 - math_obj["discharge_efficiency"] = eng_obj["discharge_efficiency"] / 100.0 - math_obj["thermal_rating"] = eng_obj["cm_ub"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 - math_obj["qmin"] = eng_obj["qs_lb"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 - math_obj["qmax"] = eng_obj["qs_ub"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 - math_obj["r"] = eng_obj["rs"] - math_obj["x"] = eng_obj["xs"] - math_obj["p_loss"] = eng_obj["pex"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 - math_obj["q_loss"] = eng_obj["qex"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 - - math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["cm_ub"]))) - math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["cm_ub"]))) + for prop in ["tm_min", "tm_max", "tm_step"] + if haskey(eng_obj, prop) + transformer_2wa_obj[prop] = eng_obj[prop][w] + end + end - if kron_reduced - _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, kr_phases) - end + if kron_reduced + # TODO fix how padding works, this is a workaround to get bank working + if all(eng_obj["configuration"] .== "wye") + f_connections = transformer_2wa_obj["f_connections"] + _pad_properties!(transformer_2wa_obj, ["tm_min", "tm_max", "tm", "fixed"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) - data_math["storage"]["$(math_obj["index"])"] = math_obj + transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] + end + end - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "storage.$(math_obj["index"])", - :unmap_function => :_map_math2eng_storage!, - ) + data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["storage"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "storage", "$(math_obj["index"])", to) - end + push!(to_map, "transformer.$(transformer_2wa_obj["index"])") end end end -"converts engineering voltage sources into mathematical generators and (if needed) impedance branches to represent the loss model" -function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) +"" +function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4, lossless::Bool=false) for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Any}()) - nconductors = data_math["conductors"] + nconductors = length(eng_obj["vm"]) - math_obj = _init_math_obj("voltage_source", name, eng_obj, length(data_math["gen"])+1) + if lossless + #TODO add unreduced logic here + math_obj = _init_math_obj("voltage_source", eng_obj, length(data_math["gen"])+1) - math_obj["name"] = "_virtual_gen.voltage_source.$name" - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] - math_obj["pg"] = fill(0.0, nconductors) - math_obj["qg"] = fill(0.0, nconductors) - math_obj["configuration"] = "wye" - math_obj["source_id"] = "_virtual_gen.$(eng_obj["source_id"])" + math_obj["name"] = name + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] + math_obj["pg"] = fill(0.0, nconductors) + math_obj["qg"] = fill(0.0, nconductors) + math_obj["configuration"] = "wye" - _add_gen_cost_model!(math_obj, eng_obj) + _add_gen_cost_model!(math_obj, eng_obj) - map_to = "gen.$(math_obj["index"])" + data_math["gen"]["$(math_obj["index"])"] = math_obj - if haskey(eng_obj, "rs") && haskey(eng_obj, "xs") + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "gen.$(math_obj["index"])", + :unmap_function => :_map_math2eng_voltage_source!, + ) + else bus_obj = Dict{String,Any}( "bus_i" => length(data_math["bus"])+1, "index" => length(data_math["bus"])+1, + "terminals" => collect(1:nconductors+1), + "grounded" => [fill(false, nconductors)..., true], "name" => "_virtual_bus.voltage_source.$name", "bus_type" => 3, - "vm" => eng_obj["vm"], - "va" => eng_obj["va"], - "vmin" => eng_obj["vm"], - "vmax" => eng_obj["vm"], + "vm" => [eng_obj["vm"]..., 0.0], + "va" => [eng_obj["va"]..., 0.0], + "vmin" => [eng_obj["vm"]..., 0.0], + "vmax" => [eng_obj["vm"]..., 0.0], "basekv" => data_math["basekv"] ) - math_obj["gen_bus"] = bus_obj["bus_i"] - data_math["bus"]["$(bus_obj["index"])"] = bus_obj + gen_obj = Dict{String,Any}( + "gen_bus" => bus_obj["bus_i"], + "connections" => collect(1:nconductors+1), + "name" => "_virtual_gen.voltage_source.$name", + "gen_status" => eng_obj["status"], + "pg" => fill(0.0, nconductors), + "qg" => fill(0.0, nconductors), + "model" => 2, + "startup" => 0.0, + "shutdown" => 0.0, + "ncost" => 3, + "cost" => [0.0, 1.0, 0.0], + "configuration" => "wye", + "index" => length(data_math["gen"]) + 1, + "source_id" => "_virtual_gen.$(eng_obj["source_id"])" + ) + + data_math["gen"]["$(gen_obj["index"])"] = gen_obj + branch_obj = Dict{String,Any}( "name" => "_virtual_branch.voltage_source.$name", "source_id" => "_virtual_branch.$(eng_obj["source_id"])", "f_bus" => bus_obj["bus_i"], "t_bus" => data_math["bus_lookup"][eng_obj["bus"]], + "f_connections" => collect(1:nconductors), + "t_connections" => eng_obj["connections"], "angmin" => fill(-60.0, nconductors), "angmax" => fill( 60.0, nconductors), "shift" => fill(0.0, nconductors), @@ -982,8 +879,8 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "tranformer" => false, "switch" => false, "br_status" => 1, - "br_r" => _impedance_conversion(data_eng, eng_obj, "rs"), - "br_x" => _impedance_conversion(data_eng, eng_obj, "xs"), + "br_r" => eng_obj["rs"], + "br_x" => eng_obj["xs"], "g_fr" => zeros(nconductors, nconductors), "g_to" => zeros(nconductors, nconductors), "b_fr" => zeros(nconductors, nconductors), @@ -993,15 +890,11 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: data_math["branch"]["$(branch_obj["index"])"] = branch_obj - map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], + :unmap_function => :_map_math2eng_voltage_source!, + ) end - - data_math["gen"]["$(math_obj["index"])"] = math_obj - - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => map_to, - :unmap_function => :_map_math2eng_voltage_source!, - ) end end diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 2092c4083..216eea9c5 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -353,19 +353,11 @@ function _scale_props!(comp::Dict{String,<:Any}, prop_names::Vector{String}, sca end -"adds big M to data model" -function add_big_M!(data_model::Dict{String,<:Any}; kwargs...) - data_model["big_M"] = Dict{String, Any}( - "v_phase_pu_lb" => get(kwargs, :v_phase_pu_lb, 0.5), - "v_phase_pu_ub" => get(kwargs, :v_phase_pu_ub, 2.0), - "v_neutral_pu_lb" => get(kwargs, :v_neutral_pu_lb, 0.0), - "v_neutral_pu_ub" => get(kwargs, :v_neutral_pu_ub, 0.5) - ) -end +_apply_func_vals(x, f) = isa(x, Dict) ? Dict(k=>f(v) for (k,v) in x) : f.(x) -"converts solution to si units" -function solution_make_si(solution::Dict{String,<:Any}, math_model::Dict{String,<:Any}; mult_sbase::Bool=true, mult_vbase::Bool=true, convert_rad2deg::Bool=true)::Dict{String,Any} +"" +function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true, mult_ibase=true, convert_rad2deg=true) solution_si = deepcopy(solution) sbase = math_model["sbase"] @@ -374,20 +366,24 @@ function solution_make_si(solution::Dict{String,<:Any}, math_model::Dict{String, dimensionalize_math_comp = get(_dimensionalize_math, comp_type, Dict()) vbase_props = mult_vbase ? get(dimensionalize_math_comp, "vbase", []) : [] sbase_props = mult_sbase ? get(dimensionalize_math_comp, "sbase", []) : [] + ibase_props = mult_ibase ? get(dimensionalize_math_comp, "ibase", []) : [] rad2deg_props = convert_rad2deg ? get(dimensionalize_math_comp, "rad2deg", []) : [] for (id, comp) in comp_dict - if !isempty(vbase_props) + if !isempty(vbase_props) || !isempty(ibase_props) vbase = math_model[comp_type][id]["vbase"] + ibase = sbase/vbase end for (prop, val) in comp if prop in vbase_props - comp[prop] = val*vbase + comp[prop] = _apply_func_vals(comp[prop], x->x*vbase) elseif prop in sbase_props - comp[prop] = val*sbase + comp[prop] = _apply_func_vals(comp[prop], x->x*sbase) + elseif prop in ibase_props + comp[prop] = _apply_func_vals(comp[prop], x->x*ibase) elseif prop in rad2deg_props - comp[prop] = _wrap_to_180(rad2deg.(val)) + comp[prop] = _apply_func_vals(comp[prop], x->x*_wrap_to_180(rad2deg(x))) end end end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index bc0104aa3..a518b5b82 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -135,7 +135,7 @@ end "loss model builder for transformer decomposition" -function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::String, to_map::Vector{String}, r_s::Vector{Float64}, zsc::Dict{Tuple{Int,Int},Complex{Float64}}, ysh::Complex{Float64}; nphases::Int=3)::Vector{Int} +function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::String, to_map::Vector{String}, r_s::Vector{Float64}, zsc::Dict{Tuple{Int,Int},Complex{Float64}}, ysh::Complex{Float64}; nphases::Int=3, kron_reduced=false)::Vector{Int} # precompute the minimal set of buses and lines N = length(r_s) tr_t_bus = collect(1:N) @@ -209,16 +209,30 @@ function _build_loss_model!(data_math::Dict{String,<:Any}, transformer_name::Str bus_obj = Dict{String,Any}( "name" => "_virtual_bus.transformer.$(transformer_name)_$(bus)", "bus_i" => length(data_math["bus"])+1, - "vm" => fill(1.0, nphases), - "va" => fill(0.0, nphases), "vmin" => fill(0.0, nphases), "vmax" => fill(Inf, nphases), + "grounded" => fill(false, nphases), "base_kv" => 1.0, "bus_type" => 1, "status" => 1, "index" => length(data_math["bus"])+1, ) + if !kron_reduced + if bus in tr_t_bus + bus_obj["terminals"] = collect(1:nphases+1) + bus_obj["vmin"] = fill(0.0, nphases+1) + bus_obj["vmax"] = fill(Inf, nphases+1) + bus_obj["grounded"] = [fill(false, nphases)..., true] + bus_obj["rg"] = [0.0] + bus_obj["xg"] = [0.0] + else + bus_obj["terminals"] = collect(1:nphases) + bus_obj["vmin"] = fill(0.0, nphases) + bus_obj["vmax"] = fill(Inf, nphases) + end + end + data_math["bus"]["$(bus_obj["index"])"] = bus_obj bus_ids[bus] = bus_obj["bus_i"] @@ -548,3 +562,25 @@ end function _angle_shift_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) _wrap_to_180([120.0 * i for i in 1:3] .+ eng_obj[key]) end + + +"lossy grounding to perfect grounding and shunts" +function _convert_grounding(terminals, grounded, rg, xg) + grouped = Dict(t=>[] for t in unique(grounded)) + for (i,t) in enumerate(grounded) + push!(grouped[t], rg[i]+im*xg[i]) + end + t_lookup = Dict(t=>i for (i,t) in enumerate(terminals)) + grounded_lossless = fill(false, length(terminals)) + shunts = [] + for (t, zgs) in grouped + if any(iszero.(zgs)) + grounded_lossless[t_lookup[t]] = true + else + ygs = 1 ./zgs + yg = sum(ygs) + push!(shunts, ([t], [yg])) + end + end + return grounded_lossless, shunts +end diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 05203d69b..d471893a7 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -519,7 +519,7 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, "tm_max" => Vector{Vector{Float64}}(fill(fill(defaults["maxtap"], nphases), nrw)), "tm_fix" => Vector{Vector{Int}}([fill(1, nphases) for w in 1:nrw]), "tm_step" => Vector{Vector{Float64}}(fill(fill(1/32, nphases), nrw)), - "fixed" => Vector{Int}(fill(1, nrw)), + "fixed" => Vector{Int}(fill(fill(true, nphases), nrw)), "vnom" => Vector{Float64}(defaults["kvs"]), "snom" => Vector{Float64}(defaults["kvas"]), "configuration" => Vector{String}(defaults["conns"]), @@ -653,7 +653,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri if isempty(defaults["xfmrcode"]) eng_obj["tm_fix"] = [fill(1, nphases) for w in 1:nrw] eng_obj["tm_step"] = fill(fill(1/32, nphases), nrw) - eng_obj["fixed"] = fill(1, nrw) + eng_obj["fixed"] = fill(fill(true, nphases), nrw) end # always required @@ -683,12 +683,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri terminals_default = conf=="wye" ? [1:nphases..., 0] : collect(1:nphases) # append ground if connections one too short - terminals_w = _get_conductors_ordered(defaults["buses"][w], default=terminals_default, pad_ground=(conf=="wye")) - eng_obj["connections"][w] = terminals_w - - if 0 in terminals_w - _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"][w]], eng_obj["connections"][w]) - end + eng_obj["connections"][w] = _get_conductors_ordered(defaults["buses"][w], default=terminals_default, pad_ground=(conf=="wye")) if w>1 prim_conf = confs[1] @@ -704,6 +699,10 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end end end + + if 0 in eng_obj["connections"][w] + _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"][w]], eng_obj["connections"][w]) + end end for key in ["bank", "xfmrcode"] From 3d17abb5b035bfad266151047198fb6963822ffb Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Sat, 18 Apr 2020 23:20:35 +1000 Subject: [PATCH 139/224] 4w solution extension --- src/core/data.jl | 14 +++++++++----- src/data_model/units.jl | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index ed2ed1e1a..0268bdf37 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -696,11 +696,15 @@ function sol_polar_voltage!(pm::_PM.AbstractPowerModel, solution::Dict) if haskey(nw_data, "bus") for (i,bus) in nw_data["bus"] if haskey(bus, "vr") && haskey(bus, "vi") - bus["vm"] = sqrt.(bus["vr"].^2 + bus["vi"].^2) - bus["va"] = _wrap_to_pi(atan.(bus["vi"], bus["vr"])) - - delete!(bus, "vr") - delete!(bus, "vi") + vr = bus["vr"] + vi = bus["vi"] + if isa(vr, Dict) + bus["vm"] = Dict(t=>abs(vr[t]+im*vi[t]) for t in keys(vr)) + bus["va"] = Dict(t=>_wrap_to_pi(atan(vi[t], vr[t])) for t in keys(vr)) + else + bus["vm"] = abs.(vr .+ im*vi) + bus["va"] = _wrap_to_pi(atan.(vi, vr)) + end end end end diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 216eea9c5..6fedde816 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -1,8 +1,8 @@ "lists of scaling factors and what they apply to" const _dimensionalize_math = Dict{String,Dict{String,Vector{String}}}( "bus" => Dict{String,Vector{String}}( - "rad2deg"=>Vector{String}(["va"]), - "vbase"=>Vector{String}(["vm", "vr", "vi"]) + "rad2deg"=>Vector{String}(["va", "va_pp", "va_pn"]), + "vbase"=>Vector{String}(["vm", "vr", "vi", "vm_pp", "vm_pn"]) ), "gen" => Dict{String,Vector{String}}( "sbase"=>Vector{String}(["pg", "qg", "pg_bus", "qg_bus"]) From 9f7d6478b72675939a6864096073a3cacc7ac41d Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Mon, 20 Apr 2020 14:26:24 +1000 Subject: [PATCH 140/224] 2-phase delta transformers --- src/data_model/eng2math.jl | 9 ++++++++- src/io/opendss.jl | 19 +++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index ae3590e73..a5bc5c2b9 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -764,7 +764,14 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic for w in 1:nrw # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction - tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] + tm_nom = eng_obj["vnom"][w] + # three-phase delta transformers need a correction of sqrt(3) + if eng_obj["configuration"][w]=="delta" && length(eng_obj["connections"][w])==3 + tm_nom = eng_obj["vnom"][w]*sqrt(3) + end + # tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] + + transformer_2wa_obj = Dict{String,Any}( "name" => "_virtual_transformer.$name.$w", "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", diff --git a/src/io/opendss.jl b/src/io/opendss.jl index d471893a7..3adb02b43 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -575,16 +575,23 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri nphases = _shared["phases"] nrw = _shared["windings"] + # two-phase delta transformers have single coil + if all(confs.=="delta") && nphases==2 + ncoils = 1 + else + ncoils = nphases + end + # taps if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, "taps") && _is_after_xfmrcode(dss_obj["prop_order"], "taps")) || all(haskey(dss_obj, k) && _is_after_xfmrcode(dss_obj["prop_order"], k) for k in ["tap", "tap_2", "tap_3"]) - eng_obj["tm"] = [fill(defaults["taps"][w], nphases) for w in 1:nrw] + eng_obj["tm"] = [fill(defaults["taps"][w], ncoils) for w in 1:nrw] else for (w, key_suffix) in enumerate(["", "_2", "_3"]) if haskey(dss_obj, "tap$(key_suffix)") && _is_after_xfmrcode(dss_obj["prop_order"], "tap$(key_suffix)") if !haskey(eng_obj, "tm") eng_obj["tm"] = Vector{Any}(missing, nrw) end - eng_obj["tm"][w] = fill(defaults["taps"][defaults["wdg$(key_suffix)"]], nphases) + eng_obj["tm"][w] = fill(defaults["taps"][defaults["wdg$(key_suffix)"]], ncoils) end end end @@ -651,9 +658,9 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri # tm_fix, tm_step don't appear in opendss if isempty(defaults["xfmrcode"]) - eng_obj["tm_fix"] = [fill(1, nphases) for w in 1:nrw] - eng_obj["tm_step"] = fill(fill(1/32, nphases), nrw) - eng_obj["fixed"] = fill(fill(true, nphases), nrw) + eng_obj["tm_fix"] = [fill(1, ncoils) for w in 1:nrw] + eng_obj["tm_step"] = fill(fill(1/32, ncoils), nrw) + eng_obj["fixed"] = fill(fill(true, ncoils), nrw) end # always required @@ -668,7 +675,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri # test if this transformer conforms with limitations if nphases<3 && "delta" in confs - Memento.error(_LOGGER, "Transformers with delta windings should have at least 3 phases to be well-defined: $name.") + # Memento.error(_LOGGER, "Transformers with delta windings should have at least 3 phases to be well-defined: $name.") end if nrw>3 # All of the code is compatible with any number of windings, From 31b5da581f9f1426b03350d6c5c2cb5b2530450e Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Mon, 20 Apr 2020 14:26:51 +1000 Subject: [PATCH 141/224] various patches for ieee8500 --- src/data_model/eng2math.jl | 39 ++++++++++++++++++++++++++------------ src/io/common.jl | 2 +- src/io/opendss.jl | 11 +++++++---- src/io/utils.jl | 20 +++++++++++++++++++ 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index a5bc5c2b9..38f3af585 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -69,6 +69,11 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) + # post fix + #TODO fix this in place / throw error instead? IEEE8500 leads to switches + # with 3x3 R matrices but only 1 phase + _slice_branches!(data_math) + if !adjust_bounds # _find_new_bounds(data_math) # TODO end @@ -611,7 +616,9 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A math_obj["name"] = name math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] + math_obj["f_connections"] = eng_obj["f_connections"] + math_obj["t_connections"] = eng_obj["t_connections"] data_math["switch"]["$(math_obj["index"])"] = math_obj @@ -622,15 +629,16 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A ) else # build virtual bus - f_bus = data_math["bus"]["$(data_math["bus_lookup"][eng_obj["f_bus"]])"] + t_bus = data_math["bus"]["$(data_math["bus_lookup"][eng_obj["t_bus"]])"] bus_obj = Dict{String,Any}( "name" => "_virtual_bus.switch.$name", "bus_i" => length(data_math["bus"])+1, + "terminals" => collect(1:nphases), "bus_type" => 1, - "vmin" => f_bus["vmin"], - "vmax" => f_bus["vmax"], - "base_kv" => f_bus["base_kv"], + "vmin" => t_bus["vmin"], #TODO handle slicing correctly if switch size <= to-side bus size + "vmax" => t_bus["vmax"], #TODO handle slicing correctly if switch size <= to-side bus size + "base_kv" => t_bus["base_kv"], "status" => 1, "index" => length(data_math["bus"])+1, ) @@ -658,7 +666,9 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A "source_id" => "_virtual_branch.switch.$name", # "f_bus" => bus_obj["bus_i"], # TODO enable real switches "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], + "t_bus" => bus_obj["bus_i"], + "f_connections" => eng_obj["f_connections"], + "t_connections" => collect(1:nphases), "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9), @@ -685,9 +695,6 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) connections = eng_obj["f_connections"][filter] _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) - else - branch_obj["f_connections"] = eng_obj["f_connections"] - branch_obj["f_connections"] = eng_obj["t_connections"] end data_math["branch"]["$(branch_obj["index"])"] = branch_obj @@ -696,13 +703,21 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A switch_obj = Dict{String,Any}( "name" => name, "source_id" => eng_obj["source_id"], - "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => bus_obj["bus_i"], + "f_bus" => bus_obj["bus_i"], + "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], + "f_connections" => collect(1:nphases), + "t_connections" => eng_obj["t_connections"], "status" => eng_obj["status"], "index" => length(data_math["switch"])+1 ) - # data_math["switch"]["$(switch_obj["index"])"] = switch_obj + ommit_switch = true + if ommit_switch + branch_obj["t_bus"] = switch_obj["t_bus"] + branch_obj["t_connections"] = switch_obj["t_connections"] + else + data_math["switch"]["$(switch_obj["index"])"] = switch_obj + end data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, diff --git a/src/io/common.jl b/src/io/common.jl index e1a9f0e00..8741777ec 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -69,7 +69,7 @@ function correct_network_data!(data::Dict{String,Any}; make_pu::Bool=true) _PM.correct_voltage_angle_differences!(data) _PM.correct_thermal_limits!(data) _PM.correct_branch_directions!(data) - _PM.check_branch_loops(data) + # _PM.check_branch_loops(data) _PM.correct_bus_types!(data) _PM.correct_dcline_limits!(data) _PM.correct_cost_functions!(data) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 3adb02b43..ed80dc043 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -455,12 +455,15 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An _like_is_switch = haskey(dss_obj, "like") && get(get(data_dss["line"], dss_obj["like"], Dict{String,Any}()), "switch", false) nphases = defaults["phases"] + f_connections = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) + t_connections = _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)) + eng_obj = Dict{String,Any}( "f_bus" => _parse_busname(defaults["bus1"])[1], "t_bus" => _parse_busname(defaults["bus2"])[1], "length" => defaults["switch"] || _like_is_switch ? 0.001 : defaults["length"], - "f_connections" => _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)), - "t_connections" => _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)), + "f_connections" => f_connections, + "t_connections" => t_connections, "status" => convert(Int, defaults["enabled"]), "source_id" => "line.$name" ) @@ -519,7 +522,7 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, "tm_max" => Vector{Vector{Float64}}(fill(fill(defaults["maxtap"], nphases), nrw)), "tm_fix" => Vector{Vector{Int}}([fill(1, nphases) for w in 1:nrw]), "tm_step" => Vector{Vector{Float64}}(fill(fill(1/32, nphases), nrw)), - "fixed" => Vector{Int}(fill(fill(true, nphases), nrw)), + "fixed" => Vector{Vector{Int}}(fill(fill(1, nphases), nrw)), "vnom" => Vector{Float64}(defaults["kvs"]), "snom" => Vector{Float64}(defaults["kvas"]), "configuration" => Vector{String}(defaults["conns"]), @@ -615,7 +618,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri # mintap, maxtap for (fr_key, to_key) in zip(["mintap", "maxtap"], ["tm_min", "tm_max"]) if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, fr_key) && _is_after_xfmrcode(dss_obj["prop_order"], fr_key)) - eng_obj[to_key] = fill(fill(defaults[fr_key], nphases), nrw) + eng_obj[to_key] = fill(fill(defaults[fr_key], ncoils), nrw) end end diff --git a/src/io/utils.jl b/src/io/utils.jl index 411c7b469..54000be34 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -366,6 +366,14 @@ function _discover_terminals!(data_eng::Dict{String,<:Any}) end end + if haskey(data_eng, "switch") + for (_,eng_obj) in data_eng["switch"] + # ignore 0 terminal + push!(terminals[eng_obj["f_bus"]], setdiff(eng_obj["f_connections"], [0])...) + push!(terminals[eng_obj["t_bus"]], setdiff(eng_obj["t_connections"], [0])...) + end + end + if haskey(data_eng, "transformer") for (_,tr) in data_eng["transformer"] for w in 1:length(tr["bus"]) @@ -816,3 +824,15 @@ function _guess_dtype(value::AbstractString)::Type return String end end + + +function _slice_branches!(data_math) + for (_, branch) in data_math["branch"] + if haskey(branch, "f_connections") + N = length(branch["f_connections"]) + for prop in ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to"] + branch[prop] = branch[prop][1:N,1:N] + end + end + end +end From 1ea3fd60fb32958b04e6c748fad94af7556853f9 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 20 Apr 2020 16:28:31 -0600 Subject: [PATCH 142/224] WIP: FIX: dropped eng2math code Restored a lot of the newer eng2math code that was accidently removed Moved _slice_branches! to data_model/utils.jl Still failing tests, (phase drop cases) --- src/data_model/eng2math.jl | 1256 ++++++++++++++++++++---------------- src/data_model/utils.jl | 13 + src/io/common.jl | 2 +- src/io/utils.jl | 12 - 4 files changed, 701 insertions(+), 582 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 38f3af585..00a37e0b3 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -1,31 +1,149 @@ import LinearAlgebra: diagm - +"items that are mapped one-to-one from engineering to math models" const _1to1_maps = Dict{String,Vector{String}}( - "bus" => ["vm", "va", "vmin", "vmax", "terminals", "phases", "neutral"], - "load" => ["model", "configuration", "status", "source_id", "connections"], - "shunt_capacitor" => ["status", "source_id"], - "series_capacitor" => [], - "shunt" => ["status", "source_id"], - "shunt_reactor" => ["status", "source_id"], - "generator" => ["source_id", "configuration"], - "solar" => ["source_id", "configuration"], - "storage" => ["status", "source_id"], - "line" => ["source_id"], - "line_reactor" => ["source_id"], - "switch" => ["source_id", "state", "status"], - "line_reactor" => ["source_id"], - "transformer" => ["source_id"], - "voltage_source" => ["source_id"], + "bus" => ["vm", "va", "terminals", "phases", "neutral", "dss"], + "line" => ["f_connections", "t_connections", "source_id", "dss"], + "transformer" => ["f_connections", "t_connections", "source_id", "dss"], + "switch" => ["status", "state", "f_connections", "t_connections", "source_id", "dss"], + "line_reactor" => ["f_connections", "t_connections", "source_id", "dss"], + "series_capacitor" => ["f_connections", "t_connections", "source_id", "dss"], + "shunt" => ["status", "gs", "bs", "connections", "source_id", "dss"], + "shunt_capacitor" => ["status", "bs", "connections", "source_id", "dss"], + "shunt_reactor" => ["status", "connections", "source_id", "dss"], + "load" => ["model", "configuration", "connections", "status", "source_id", "dss"], + "generator" => ["pg", "qg", "configuration", "connections", "source_id", "dss"], + "solar" => ["configuration", "connections", "source_id", "dss"], + "storage" => ["status", "energy", "ps", "qs", "connections", "source_id", "dss"], + "voltage_source" => ["connections", "source_id", "dss"], ) -const _node_elements = ["load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource"] - -const _edge_elements = ["line", "switch", "transformer", "line_reactor", "series_capacitor"] +"list of nodal type elements in the engineering model" +const _node_elements = Vector{String}([ + "load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource" +]) + +"list of edge type elements in the engineering model" +const _edge_elements = Vector{String}([ + "line", "switch", "transformer", "line_reactor", "series_capacitor" +]) + +"list of time-series supported parameters that map one-to-one" +const _time_series_parameters = Dict{String,Dict{String,Tuple{Function, String}}}( + "switch" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "state" => (_no_conversion, "state"), + "c_rating" => (_no_conversion, "cm_ub"), + "s_rating" => (_no_conversion, "sm_ub"), + "br_r" => (_impedance_conversion, "rs"), + "br_x" => (_impedance_conversion, "xs"), + "g_fr" => (_admittance_conversion, "g_fr"), + "g_to" => (_admittance_conversion, "g_to"), + "b_fr" => (_admittance_conversion, "b_fr"), + "b_to" => (_admittance_conversion, "b_to") + ), + "fuse" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "state" => (_no_conversion, "state"), + "c_rating" => (_no_conversion, "cm_ub"), + "s_rating" => (_no_conversion, "sm_ub"), + "br_r" => (_impedance_conversion, "rs"), + "br_x" => (_impedance_conversion, "xs"), + "g_fr" => (_admittance_conversion, "g_fr"), + "g_to" => (_admittance_conversion, "g_to"), + "b_fr" => (_admittance_conversion, "b_fr"), + "b_to" => (_admittance_conversion, "b_to") + ), + "line" => Dict{String,Tuple{Function, String}}( + "br_status" => (_no_conversion, "status"), + "c_rating" => (_no_conversion, "cm_ub"), + "s_rating" => (_no_conversion, "sm_ub"), + "br_r" => (_impedance_conversion, "rs"), + "br_x" => (_impedance_conversion, "xs"), + "g_fr" => (_admittance_conversion, "g_fr"), + "g_to" => (_admittance_conversion, "g_to"), + "b_fr" => (_admittance_conversion, "b_fr"), + "b_to" => (_admittance_conversion, "b_to") + ), + "transformer" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + # TODO need to figure out how to convert values for time series for decomposed transformers + ), + "bus" => Dict{String,Tuple{Function, String}}( + "bus_type" => (_bus_type_conversion, "status"), + "vmin" => (_no_conversion, "vm_lb"), + "vmax" => (_no_conversion, "vm_ub"), + "vm" => (_no_conversion, "vm"), + "va" => (_no_conversion, "va") + ), + "shunt" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "gs" => (_no_conversion, "gs"), + "bs" => (_no_conversion, "bs"), + ), + "shunt_capacitor" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "gs" => (_no_conversion, "gs"), + "bs" => (_no_conversion, "bs"), + ), + "shunt_reactor" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "gs" => (_no_conversion, "gs"), + "bs" => (_no_conversion, "bs"), + ), + "load" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "pd_ref" => (_no_conversion, "pd_nom"), + "qd_ref" => (_no_conversion, "qd_nom"), + ), + "generator" => Dict{String,Tuple{Function, String}}( + "gen_status" => (_no_conversion, "status"), + "pg" => (_no_conversion, "pg"), + "qg" => (_no_conversion, "qg"), + "vg" => (_vnom_conversion, "vg"), + "pmin" => (_no_conversion, "pg_lb"), + "pmax" => (_no_conversion, "pg_ub"), + "qmin" => (_no_conversion, "qg_lb"), + "qmax" => (_no_conversion, "qg_ub"), + ), + "solar" => Dict{String,Tuple{Function, String}}( + "gen_status" => (_no_conversion, "status"), + "pg" => (_no_conversion, "pg"), + "qg" => (_no_conversion, "qg"), + "vg" => (_vnom_conversion, "vg"), + "pmin" => (_no_conversion, "pg_lb"), + "pmax" => (_no_conversion, "pg_ub"), + "qmin" => (_no_conversion, "qg_lb"), + "qmax" => (_no_conversion, "qg_ub"), + ), + "storage" => Dict{String,Tuple{Function, String}}( + "status" => (_no_conversion, "status"), + "energy" => (_no_conversion, "energy"), + "energy_rating" => (_no_conversion, "energy_ub"), + "charge_rating" => (_no_conversion, "charge_ub"), + "discharge_rating" => (_no_conversion, "discharge_ub"), + "charge_efficiency" => (_no_conversion, "charge_efficiency"), + "discharge_efficiency" => (_no_conversion, "discharge_efficiency"), + "thermal_rating" => (_no_conversion, "cm_ub"), + "qmin" => (_no_conversion, "qs_lb"), + "qmax" => (_no_conversion, "qs_ub"), + "r" => (_no_conversion, "rs"), + "x" => (_no_conversion, "xs"), + "p_loss" => (_no_conversion, "pex"), + "q_loss" => (_no_conversion, "qex"), + "ps" => (_no_conversion, "ps"), + "qs" => (_no_conversion, "qs"), + ), + "voltage_source" => Dict{String,Tuple{Function, String}}( + "gen_status" => (_no_conversion, "status"), + "vm" => (_no_conversion, "vm"), + "va" => (_angle_shift_conversion, "va"), + ), +) -"" -function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, adjust_bounds::Bool=true) +"base function for converting engineering model to mathematical model" +function _map_eng2math(data_eng; kron_reduced::Bool=true) @assert get(data_eng, "data_model", "mathematical") == "engineering" data_math = Dict{String,Any}( @@ -49,99 +167,58 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true, lossless::Bool=false, _init_base_components!(data_math) + # convert buses _map_eng2math_bus!(data_math, data_eng; kron_reduced=kron_reduced) + # convert edges + _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) + # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO build conversion for series capacitors + + # convert nodes _map_eng2math_load!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) - - _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) - _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) - _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) - _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) - - _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) - # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO - _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced, lossless=lossless) - _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_storage!(data_math, data_eng; kron_reduced=kron_reduced) + _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced) # post fix #TODO fix this in place / throw error instead? IEEE8500 leads to switches # with 3x3 R matrices but only 1 phase _slice_branches!(data_math) - if !adjust_bounds - # _find_new_bounds(data_math) # TODO - end - return data_math end -"" -function _init_math_obj(obj_type::String, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} - math_obj = Dict{String,Any}() - - for key in _1to1_maps[obj_type] - if haskey(eng_obj, key) - math_obj[key] = eng_obj[key] - end - end - - math_obj["index"] = index - - return math_obj -end - - -"" -function _init_base_components!(data_math::Dict{String,<:Any}) - for key in ["bus", "load", "shunt", "gen", "branch", "switch", "transformer", "storage", "dcline"] - if !haskey(data_math, key) - data_math[key] = Dict{String,Any}() - end - end -end - - -"" -function _init_lookup!(data_math::Dict{String,<:Any}) - - - for key in keys(_1to1_maps) - if !haskey(data_math["lookup"], key) - data_math["lookup"][key] = Dict{Any,Int}() - end - end -end - - -"" +"converts engineering bus components into mathematical bus components" function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "bus", Dict{String,Any}()) - # TODO fix vnom terminals = eng_obj["terminals"] nconductors = data_math["conductors"] - math_obj = _init_math_obj("bus", eng_obj, length(data_math["bus"])+1) - math_obj["name"] = name + math_obj = _init_math_obj("bus", name, eng_obj, length(data_math["bus"])+1) math_obj["bus_i"] = math_obj["index"] - math_obj["bus_type"] = eng_obj["status"] == 1 ? 1 : 4 + math_obj["bus_type"] = _bus_type_conversion(data_eng, eng_obj, "status") # take care of grounding; convert to shunt if lossy grounded_perfect, shunts = _convert_grounding(eng_obj["terminals"], eng_obj["grounded"], eng_obj["rg"], eng_obj["xg"]) + math_obj["grounded"] = grounded_perfect to_sh = [] for (sh_connections, sh_y) in shunts - sh_index = length(data_math["shunt"])+1 + sh_index = length(data_math["shunt"]) + 1 data_math["shunt"]["$sh_index"] = Dict( "index" => sh_index, - "shunt_bus" => bus_index, + "shunt_bus" => math_obj["bus_i"], "connections" => sh_connections, "gs" => real.(sh_y), "bs" => real.(sh_y), @@ -149,8 +226,15 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, push!(to_sh, "shunt.$sh_index") end - math_obj["vmin"] = get(eng_obj, "vmin", fill(0.0, length(terminals))) - math_obj["vmax"] = get(eng_obj, "vmax", fill(Inf, length(terminals))) + if haskey(eng_obj, "vm") + math_obj["vm"] = eng_obj["vm"] + end + if haskey(eng_obj, "va") + math_obj["va"] = eng_obj["va"] + end + + math_obj["vmin"] = get(eng_obj, "vm_lb", fill(0.0, length(terminals))) + math_obj["vmax"] = get(eng_obj, "vm_ub", fill(Inf, length(terminals))) math_obj["base_kv"] = data_eng["settings"]["vbase"] @@ -176,318 +260,305 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, :to => "bus.$(math_obj["index"])", :unmap_function => :_map_math2eng_bus!, ) - end -end - - -"" -function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("load", eng_obj, length(data_math["load"])+1) - math_obj["name"] = name - - connections = eng_obj["connections"] - nconductors = data_math["conductors"] - - math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - - math_obj["status"] = eng_obj["status"] - - math_obj["pd"] = eng_obj["pd_nom"] - math_obj["qd"] = eng_obj["qd_nom"] - - math_obj["configuration"] = eng_obj["configuration"] - if kron_reduced - if math_obj["configuration"]=="wye" - @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pd", "qd"], connections[connections.!=kr_neutral], kr_phases) - else - _pad_properties_delta!(math_obj, ["pd", "qd"], connections, kr_phases) + # time series + for (fr, (f, to)) in _time_series_parameters["bus"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj, "bus", "$(math_obj["index"])", fr, to, f) end - else - math_obj["connections"] = connections end + end +end - math_obj["vnom_kv"] = eng_obj["vnom"] - data_math["load"]["$(math_obj["index"])"] = math_obj +"converts engineering lines into mathematical branches" +function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) + _apply_linecode!(eng_obj, data_eng) - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "load.$(math_obj["index"])", - :unmap_function => :_map_math2eng_load!, - ) - end -end + math_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) + nphases = length(eng_obj["f_connections"]) + nconductors = data_math["conductors"] -"" -function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt_capacitor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt_capacitor", eng_obj, length(data_math["shunt"])+1) + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - # TODO change to new capacitor shunt calc logic - math_obj["name"] = name - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["br_r"] = _impedance_conversion(data_eng, eng_obj, "rs") + math_obj["br_x"] = _impedance_conversion(data_eng, eng_obj, "xs") - f_terminals = eng_obj["f_connections"] - t_terminals = eng_obj["t_connections"] + math_obj["g_fr"] = _admittance_conversion(data_eng, eng_obj, "g_fr") + math_obj["g_to"] = _admittance_conversion(data_eng, eng_obj, "g_to") - # TODO change in OpenDS parser - b = diag(eng_obj["bs"]) + math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") + math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") - # convert to a shunt matrix - terminals, B = _calc_shunt(f_terminals, t_terminals, b) + math_obj["angmin"] = fill(-60.0, nphases) + math_obj["angmax"] = fill( 60.0, nphases) - # if one terminal is ground (0), reduce shunt addmittance matrix - terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) + math_obj["transformer"] = false + math_obj["shift"] = zeros(nphases) + math_obj["tap"] = ones(nphases) - math_obj["bs"] = B - math_obj["gs"] = zeros(size(B)) + f_bus = data_eng["bus"][eng_obj["f_bus"]] + t_bus = data_eng["bus"][eng_obj["t_bus"]] if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") filter = _kron_reduce_branch!(math_obj, - Vector{String}([]), ["gs", "bs"], + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], eng_obj["f_connections"], kr_neutral ) + _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) connections = eng_obj["f_connections"][filter] - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) else - math_obj["connections"] = terminals + math_obj["f_connections"] = eng_obj["f_connections"] + math_obj["t_connections"] = eng_obj["t_connections"] end - data_math["shunt"]["$(math_obj["index"])"] = math_obj + math_obj["switch"] = false + + math_obj["br_status"] = eng_obj["status"] + + data_math["branch"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_shunt_capacitor!, + :to => "branch.$(math_obj["index"])", + :unmap_function => :_map_math2eng_line!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) + end + end end end -"" -function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt", eng_obj, length(data_math["shunt"])+1) +"converts engineering n-winding transformers into mathematical ideal 2-winding lossless transformer branches and impedance branches to represent the loss model" +function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "transformer", Dict{Any,Dict{String,Any}}()) + # Build map first, so we can update it as we decompose the transformer + map_idx = length(data_math["map"])+1 + data_math["map"][map_idx] = Dict{Symbol,Any}( + :from => name, + :to => Vector{String}([]), + :unmap_function => :_map_math2eng_transformer!, + ) - # TODO change to new capacitor shunt calc logic - math_obj["name"] = name - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + to_map = data_math["map"][map_idx][:to] - math_obj["gs"] = eng_obj["g_sh"] - math_obj["bs"] = eng_obj["b_sh"] + _apply_xfmrcode!(eng_obj, data_eng) - if kron_reduced - filter = _kron_reduce_branch!(math_obj, - Vector{String}([]), ["gs", "bs"], - eng_obj["connections"], kr_neutral - ) - connections = eng_obj["connections"][filter] - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) - else - math_obj["connections"] = eng_obj["connections"] - end + vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] + snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] - data_math["shunt"]["$(math_obj["index"])"] = math_obj + nrw = length(eng_obj["bus"]) - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_capacitor!, - ) - end -end + # calculate zbase in which the data is specified, and convert to SI + zbase = (vnom.^2) ./ snom + # x_sc is specified with respect to first winding + x_sc = eng_obj["xsc"] .* zbase[1] -"" -function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt_reactor", eng_obj, length(data_math["shunt"])+1) + # rs is specified with respect to each winding + r_s = eng_obj["rs"] .* zbase - nphases = eng_obj["phases"] - connections = eng_obj["connections"] - nconductors = data_math["conductors"] + g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 + b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 - Gcap = sum(eng_obj["kvar"]) / (nphases * 1e3 * (data_math["basekv"])^2) + # data is measured externally, but we now refer it to the internal side + ratios = vnom/data_eng["settings"]["v_var_scalar"] + x_sc = x_sc./ratios[1]^2 + r_s = r_s./ratios.^2 + g_sh = g_sh*ratios[1]^2 + b_sh = b_sh*ratios[1]^2 - math_obj["name"] = name - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + # convert x_sc from list of upper triangle elements to an explicit dict + y_sh = g_sh + im*b_sh + z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) - math_obj["gs"] = fill(0.0, nphases, nphases) - math_obj["bs"] = diagm(0=>fill(Gcap, nphases)) + transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh) - if kron_reduced - if eng_obj["configuration"] == "wye" - _pad_properties!(math_obj, ["gs", "bs"], connections[1:end-1], kr_phases) - else - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + for w in 1:nrw + # 2-WINDING TRANSFORMER + # make virtual bus and mark it for reduction + tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] + transformer_2wa_obj = Dict{String,Any}( + "name" => "_virtual_transformer.$name.$w", + "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", + "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], + "t_bus" => transformer_t_bus_w[w], + "tm_nom" => tm_nom, + "f_connections" => eng_obj["connections"][w], + "t_connections" => collect(1:4), + "configuration" => eng_obj["configuration"][w], + "polarity" => eng_obj["polarity"][w], + "tm" => eng_obj["tm"][w], + "fixed" => eng_obj["fixed"][w], + "index" => length(data_math["transformer"])+1 + ) + + for prop in ["tm_min", "tm_max", "tm_step"] + if haskey(eng_obj, prop) + transformer_2wa_obj[prop] = eng_obj[prop][w] + end end - end - data_math["shunt"]["$(math_obj["index"])"] = math_obj + if kron_reduced + # TODO fix how padding works, this is a workaround to get bank working + if all(eng_obj["configuration"] .== "wye") + f_connections = transformer_2wa_obj["f_connections"] + _pad_properties!(transformer_2wa_obj, ["tm_min", "tm_max", "tm", "fixed"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_shunt_reactor!, - ) + transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] + end + end + + data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj + + push!(to_map, "transformer.$(transformer_2wa_obj["index"])") + end end end -"" -function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) - for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) - math_obj = _init_math_obj("generator", eng_obj, length(data_math["gen"])+1) - - phases = eng_obj["phases"] - connections = eng_obj["connections"] +"converts engineering switches into mathematical switches and (if neeed) impedance branches to represent loss model" +function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + # TODO enable real switches (right now only using vitual lines) + for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) + nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["name"] = name - math_obj["gen_status"] = eng_obj["status"] - - math_obj["pg"] = eng_obj["kw"] - math_obj["qg"] = eng_obj["kvar"] - math_obj["vg"] = eng_obj["kv"] ./ data_math["basekv"] - - math_obj["qmin"] = eng_obj["kvar_min"] - math_obj["qmax"] = eng_obj["kvar_max"] + math_obj = _init_math_obj("switch", name, eng_obj, length(data_math["switch"])+1) - math_obj["pmax"] = eng_obj["kw"] - math_obj["pmin"] = zeros(phases) + math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - _add_gen_cost_model!(math_obj, eng_obj) + # OPF bounds + for (fr_key, to_key) in zip(["cm_ub"], ["c_rating"]) + if haskey(eng_obj, fr_key) + math_obj[to_key] = eng_obj[fr_key] + end + end - math_obj["configuration"] = eng_obj["configuration"] + map_to = "switch.$(math_obj["index"])" - if kron_reduced - if math_obj["configuration"]=="wye" - @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) - else - _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) + # time series + # TODO switch time series + for (fr, to) in zip(["status", "state"], ["status", "state"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "switch", "$(math_obj["index"])", to) end - else - math_obj["connections"] = connections end - # if PV generator mode convert attached bus to PV bus - if eng_obj["control_model"] == 3 - data_math["bus"]["$(data_math["bus_lookup"][eng_obj["bus"]])"]["bus_type"] = 2 - end + if any(haskey(eng_obj, k) for k in ["rs", "xs", "linecode"]) + # build virtual bus - data_math["gen"]["$(math_obj["index"])"] = math_obj + f_bus = data_math["bus"]["$(math_obj["f_bus"])"] - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_generator!, - ) - end -end - - -"" -function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) - for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("solar", eng_obj, length(data_math["gen"])+1) - - connections = eng_obj["connections"] - nconductors = data_math["conductors"] + bus_obj = Dict{String,Any}( + "name" => "_virtual_bus.switch.$name", + "bus_i" => length(data_math["bus"])+1, + "bus_type" => 1, + "vmin" => f_bus["vmin"], + "vmax" => f_bus["vmax"], + "base_kv" => f_bus["base_kv"], + "status" => 1, + "index" => length(data_math["bus"])+1, + ) - math_obj["name"] = name - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] + #= TODO enable real switches + # math_obj["t_bus"] = bus_obj["bus_i"] + # data_math["bus"]["$(bus_obj["index"])"] = bus_obj + =# - math_obj["pg"] = eng_obj["kva"] - math_obj["qg"] = eng_obj["kvar"] - math_obj["vg"] = eng_obj["kv"] + # build virtual branch + _apply_linecode!(eng_obj, data_eng) - math_obj["pmin"] = get(eng_obj, "minkva", zeros(size(eng_obj["kva"]))) - math_obj["pmax"] = get(eng_obj, "maxkva", eng_obj["kva"]) + branch_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) - math_obj["qmin"] = get(eng_obj, "minkvar", -eng_obj["kvar"]) - math_obj["qmax"] = get(eng_obj, "maxkvar", eng_obj["kvar"]) + _branch_obj = Dict{String,Any}( + "name" => "_virtual_branch.switch.$name", + "source_id" => "_virtual_branch.switch.$name", + # "f_bus" => bus_obj["bus_i"], # TODO enable real switches + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], + "br_r" => _impedance_conversion(data_eng, eng_obj, "rs"), + "br_x" => _impedance_conversion(data_eng, eng_obj, "xs"), + "g_fr" => _admittance_conversion(data_eng, eng_obj, "g_fr"), + "g_to" => _admittance_conversion(data_eng, eng_obj, "g_to"), + "b_fr" => _admittance_conversion(data_eng, eng_obj, "b_fr"), + "b_to" => _admittance_conversion(data_eng, eng_obj, "b_to"), + "angmin" => fill(-60.0, nphases), + "angmax" => fill( 60.0, nphases), + "transformer" => false, + "shift" => zeros(nphases), + "tap" => ones(nphases), + "switch" => false, + "br_status" => 1, + ) - _add_gen_cost_model!(math_obj, eng_obj) + merge!(branch_obj, _branch_obj) - if kron_reduced - if math_obj["configuration"]=="wye" - @assert(connections[end]==kr_neutral) - _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) + if kron_reduced + @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") + filter = _kron_reduce_branch!(branch_obj, + ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], + eng_obj["f_connections"], kr_neutral + ) + _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) + connections = eng_obj["f_connections"][filter] + _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) else - _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) + branch_obj["f_connections"] = eng_obj["f_connections"] + branch_obj["f_connections"] = eng_obj["t_connections"] end - else - math_obj["connections"] = connections - end - - data_math["gen"]["$(math_obj["index"])"] = math_obj - - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "solar.$name", - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_solar!, - ) - end -end + data_math["branch"]["$(branch_obj["index"])"] = branch_obj -"" -function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) - for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("storage", eng_obj, length(data_math["storage"])+1) - - connections = eng_obj["connections"] - nconductors = data_math["conductors"] - - math_obj["name"] = name - math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + # build switch + switch_obj = Dict{String,Any}( + "name" => name, + "source_id" => eng_obj["source_id"], + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => bus_obj["bus_i"], + "status" => eng_obj["status"], + "index" => length(data_math["switch"])+1 + ) - math_obj["energy"] = eng_obj["kwhstored"] / 1e3 - math_obj["energy_rating"] = eng_obj["kwhrated"] / 1e3 - math_obj["charge_rating"] = eng_obj["%charge"] * eng_obj["kwrated"] / 1e3 / 100.0 - math_obj["discharge_rating"] = eng_obj["%discharge"] * eng_obj["kwrated"] / 1e3 / 100.0 - math_obj["charge_efficiency"] = eng_obj["%effcharge"] / 100.0 - math_obj["discharge_efficiency"] = eng_obj["%effdischarge"] / 100.0 - math_obj["thermal_rating"] = eng_obj["kva"] ./ 1e3 - math_obj["qmin"] = -eng_obj["kvar"] ./ 1e3 - math_obj["qmax"] = eng_obj["kvar"] ./ 1e3 - math_obj["r"] = eng_obj["%r"] ./ 100.0 - math_obj["x"] = eng_obj["%x"] ./ 100.0 - math_obj["p_loss"] = eng_obj["%idlingkw"] .* eng_obj["kwrated"] ./ 1e3 - math_obj["q_loss"] = eng_obj["%idlingkvar"] * sum(eng_obj["kvar"]) / 1e3 - - math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["kva"]))) - math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["kva"]))) + # data_math["switch"]["$(switch_obj["index"])"] = switch_obj - if kron_reduced - _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, kr_phases) + map_to = ["branch.$(branch_obj["index"])"] + # map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] # TODO enable real switches end - data_math["storage"]["$(math_obj["index"])"] = math_obj + # data_math["switch"]["$(math_obj["index"])"] = math_obj # TODO enable real switches data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => "storage.$(math_obj["index"])", - :unmap_function => :_map_math2eng_storage!, + :to => map_to, + :unmap_function => :_map_math2eng_switch!, ) + end end -"" -function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "line", Dict{Any,Dict{String,Any}}()) - _apply_linecode!(eng_obj, data_eng) - - math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) - math_obj["name"] = name +"converts engineering line reactors into mathematical branches" +function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + # TODO support line reactors natively, currently treated like branches + for (name, eng_obj) in get(data_eng, "line_reactor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) + math_obj["name"] = "_virtual_branch.$(eng_obj["source_id"])" nphases = length(eng_obj["f_connections"]) nconductors = data_math["conductors"] @@ -495,14 +566,14 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] - math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] + math_obj["br_r"] = _impedance_conversion(data_eng, eng_obj, "rs") + math_obj["br_x"] = _impedance_conversion(data_eng, eng_obj, "xs") - math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) - math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) + math_obj["g_fr"] = _admittance_conversion(data_eng, eng_obj, "g_fr") + math_obj["g_to"] = _admittance_conversion(data_eng, eng_obj, "g_to") - math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) - math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) + math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") + math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") math_obj["angmin"] = fill(-60.0, nphases) math_obj["angmax"] = fill( 60.0, nphases) @@ -537,321 +608,372 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, :to => "branch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_line!, + :unmap_function => :_map_math2eng_line_reactor!, ) + + # time series + # TODO + for (fr, to) in zip(["status"], ["status"]) + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) + end + end end end -"" -function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - # TODO support line reactors natively, currently treated like branches - for (name, eng_obj) in get(data_eng, "line_reactor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) - math_obj["name"] = "_virtual_branch.$(eng_obj["source_id"])" +"converts engineering generic shunt components into mathematical shunt components" +function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "shunt", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt", name, eng_obj, length(data_math["shunt"])+1) - nphases = length(eng_obj["f_connections"]) - nconductors = data_math["conductors"] + # TODO change to new capacitor shunt calc logic + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] + math_obj["gs"] = eng_obj["gs"] + math_obj["bs"] = eng_obj["bs"] - math_obj["br_r"] = eng_obj["rs"] * eng_obj["length"] - math_obj["br_x"] = eng_obj["xs"] * eng_obj["length"] + if kron_reduced + filter = _kron_reduce_branch!(math_obj, + Vector{String}([]), ["gs", "bs"], + eng_obj["connections"], kr_neutral + ) + connections = eng_obj["connections"][filter] + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + else + math_obj["connections"] = eng_obj["connections"] + end - math_obj["g_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9) - math_obj["g_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9) + data_math["shunt"]["$(math_obj["index"])"] = math_obj - math_obj["b_fr"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9) - math_obj["b_to"] = (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9) + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_capacitor!, + ) - math_obj["angmin"] = fill(-60.0, nphases) - math_obj["angmax"] = fill( 60.0, nphases) + # time series + for (fr, (f, to)) in _time_series_parameters["shunt"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) + end + end + end +end - math_obj["transformer"] = false - math_obj["shift"] = zeros(nphases) - math_obj["tap"] = ones(nphases) - f_bus = data_eng["bus"][eng_obj["f_bus"]] - t_bus = data_eng["bus"][eng_obj["t_bus"]] +"converts engineering shunt capacitors into mathematical shunts" +function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "shunt_capacitor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt_capacitor", name, eng_obj, length(data_math["shunt"])+1) + + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + + math_obj["gs"] = zeros(size(eng_obj["bs"])) if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") filter = _kron_reduce_branch!(math_obj, - ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], + Vector{String}([]), ["gs", "bs"], eng_obj["f_connections"], kr_neutral ) - _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) connections = eng_obj["f_connections"][filter] - _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) else - math_obj["f_connections"] = eng_obj["f_connections"] - math_obj["t_connections"] = eng_obj["t_connections"] + math_obj["connections"] = eng_obj["connections"] end - math_obj["switch"] = false + data_math["shunt"]["$(math_obj["index"])"] = math_obj - math_obj["br_status"] = eng_obj["status"] + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_shunt_capacitor!, + ) - data_math["branch"]["$(math_obj["index"])"] = math_obj + # time series + # TODO + for (fr, (f, to)) in _time_series_parameters["shunt_capacitor"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) + end + end + end +end + + +"converts engineering shunt reactors into mathematical shunts" +function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("shunt_reactor", name, eng_obj, length(data_math["shunt"])+1) + + connections = eng_obj["connections"] + nconductors = data_math["conductors"] + + math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + + math_obj["gs"] = fill(0.0, size(eng_obj["bs"])...) + + if kron_reduced + if eng_obj["configuration"] == "wye" + _pad_properties!(math_obj, ["gs", "bs"], connections[1:end-1], kr_phases) + else + _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) + end + end + + data_math["shunt"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => "branch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_line_reactor!, + :to => "shunt.$(math_obj["index"])", + :unmap_function => :_map_math2eng_shunt_reactor!, ) + + # time series + # TODO + for (fr, (f, to)) in _time_series_parameters["shunt_reactor"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) + end + end end end -"" -function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4, lossless::Bool=false) - # TODO enable real switches (right now only using vitual lines) - for (name, eng_obj) in get(data_eng, "switch", Dict{Any,Dict{String,Any}}()) - nphases = length(eng_obj["f_connections"]) - nconductors = data_math["conductors"] +"converts engineering load components into mathematical load components" +function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "load", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("load", name, eng_obj, length(data_math["load"])+1) - if lossless - math_obj = _init_math_obj("switch", eng_obj, length(data_math["switch"])+1) - math_obj["name"] = name + connections = eng_obj["connections"] - math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - math_obj["f_connections"] = eng_obj["f_connections"] - math_obj["t_connections"] = eng_obj["t_connections"] + math_obj["load_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - data_math["switch"]["$(math_obj["index"])"] = math_obj + math_obj["pd"] = eng_obj["pd_nom"] + math_obj["qd"] = eng_obj["qd_nom"] - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "switch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_switch!, - ) + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pd", "qd"], connections[connections.!=kr_neutral], kr_phases) + else + _pad_properties_delta!(math_obj, ["pd", "qd"], connections, kr_phases) + end else - # build virtual bus - t_bus = data_math["bus"]["$(data_math["bus_lookup"][eng_obj["t_bus"]])"] + math_obj["connections"] = connections + end - bus_obj = Dict{String,Any}( - "name" => "_virtual_bus.switch.$name", - "bus_i" => length(data_math["bus"])+1, - "terminals" => collect(1:nphases), - "bus_type" => 1, - "vmin" => t_bus["vmin"], #TODO handle slicing correctly if switch size <= to-side bus size - "vmax" => t_bus["vmax"], #TODO handle slicing correctly if switch size <= to-side bus size - "base_kv" => t_bus["base_kv"], - "status" => 1, - "index" => length(data_math["bus"])+1, - ) + math_obj["vnom_kv"] = eng_obj["vnom"] - # data_math["bus"]["$(bus_obj["index"])"] = bus_obj + data_math["load"]["$(math_obj["index"])"] = math_obj - # build virtual branch - if haskey(eng_obj, "linecode") - linecode = data_eng["linecode"][eng_obj["linecode"]] + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "load.$(math_obj["index"])", + :unmap_function => :_map_math2eng_load!, + ) - for property in ["rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - if !haskey(eng_obj, property) && haskey(linecode, property) - eng_obj[property] = linecode[property] - end - end + # time series + for (fr, (f, to)) in _time_series_parameters["load"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "load", "$(math_obj["index"])", to) end + end + end +end - branch_obj = _init_math_obj("line", eng_obj, length(data_math["branch"])+1) - Zbase = (data_math["basekv"])^2 / (data_math["baseMVA"]) - Zbase = 1 +"converts engineering generators into mathematical generators" +function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) + math_obj = _init_math_obj("generator", name, eng_obj, length(data_math["gen"])+1) - _branch_obj = Dict{String,Any}( - "name" => "_virtual_branch.switch.$name", - "source_id" => "_virtual_branch.switch.$name", - # "f_bus" => bus_obj["bus_i"], # TODO enable real switches - "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => bus_obj["bus_i"], - "f_connections" => eng_obj["f_connections"], - "t_connections" => collect(1:nphases), - "br_r" => eng_obj["rs"] * eng_obj["length"] / Zbase, - "br_x" => eng_obj["xs"] * eng_obj["length"] / Zbase, - "g_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_fr"] * eng_obj["length"] / 1e9), - "g_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["g_to"] * eng_obj["length"] / 1e9), - "b_fr" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_fr"] * eng_obj["length"] / 1e9), - "b_to" => Zbase * (2.0 * pi * data_eng["settings"]["base_frequency"] * eng_obj["b_to"] * eng_obj["length"] / 1e9), - "angmin" => fill(-60.0, nphases), - "angmax" => fill( 60.0, nphases), - "transformer" => false, - "shift" => zeros(nphases), - "tap" => ones(nphases), - "switch" => false, - "br_status" => 1, - ) + phases = eng_obj["phases"] + connections = eng_obj["connections"] + nconductors = data_math["conductors"] - merge!(branch_obj, _branch_obj) + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] - if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") - filter = _kron_reduce_branch!(branch_obj, - ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], - eng_obj["f_connections"], kr_neutral - ) - _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) - connections = eng_obj["f_connections"][filter] - _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) - end + math_obj["vg"] = eng_obj["vg"] ./ data_math["basekv"] - data_math["branch"]["$(branch_obj["index"])"] = branch_obj + math_obj["qmin"] = eng_obj["qg_lb"] + math_obj["qmax"] = eng_obj["qg_ub"] - # build switch - switch_obj = Dict{String,Any}( - "name" => name, - "source_id" => eng_obj["source_id"], - "f_bus" => bus_obj["bus_i"], - "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], - "f_connections" => collect(1:nphases), - "t_connections" => eng_obj["t_connections"], - "status" => eng_obj["status"], - "index" => length(data_math["switch"])+1 - ) + math_obj["pmax"] = eng_obj["pg"] + math_obj["pmin"] = zeros(phases) - ommit_switch = true - if ommit_switch - branch_obj["t_bus"] = switch_obj["t_bus"] - branch_obj["t_connections"] = switch_obj["t_connections"] + _add_gen_cost_model!(math_obj, eng_obj) + + math_obj["configuration"] = eng_obj["configuration"] + + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) else - data_math["switch"]["$(switch_obj["index"])"] = switch_obj + _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) end + else + math_obj["connections"] = connections + end - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - # :to_id => ["switch.$(switch_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], # TODO enable real switches - :to => ["branch.$(branch_obj["index"])"], - :unmap_function => :_map_math2eng_switch!, - ) + # if PV generator mode convert attached bus to PV bus + if eng_obj["control_mode"] == 3 + data_math["bus"]["$(data_math["bus_lookup"][eng_obj["bus"]])"]["bus_type"] = 2 end - end -end + data_math["gen"]["$(math_obj["index"])"] = math_obj -"" -function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "transformer", Dict{Any,Dict{String,Any}}()) - # Build map first, so we can update it as we decompose the transformer - map_idx = length(data_math["map"])+1 - data_math["map"][map_idx] = Dict{Symbol,Any}( + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( :from => name, - :to => Vector{String}([]), - :unmap_function => :_map_math2eng_transformer!, + :to => "gen.$(math_obj["index"])", + :unmap_function => :_map_math2eng_generator!, ) - to_map = data_math["map"][map_idx][:to] + # time series + # TODO + for (fr, (f, to)) in _time_series_parameters["generator"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "gen", "$(math_obj["index"])", to) + end + end + end +end - _apply_xfmrcode!(eng_obj, data_eng) - vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] - snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] +"converts engineering solar components into mathematical generators" +function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "solar", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("solar", name, eng_obj, length(data_math["gen"])+1) - nrw = length(eng_obj["bus"]) + connections = eng_obj["connections"] + nconductors = data_math["conductors"] - # calculate zbase in which the data is specified, and convert to SI - zbase = (vnom.^2) ./ snom + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] - # x_sc is specified with respect to first winding - x_sc = eng_obj["xsc"] .* zbase[1] + math_obj["pg"] = eng_obj["kva"] + math_obj["qg"] = eng_obj["kvar"] + math_obj["vg"] = eng_obj["kv"] - # rs is specified with respect to each winding - r_s = eng_obj["rs"] .* zbase + math_obj["pmin"] = get(eng_obj, "minkva", zeros(size(eng_obj["kva"]))) + math_obj["pmax"] = get(eng_obj, "maxkva", eng_obj["kva"]) - g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 - b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 + math_obj["qmin"] = get(eng_obj, "minkvar", -eng_obj["kvar"]) + math_obj["qmax"] = get(eng_obj, "maxkvar", eng_obj["kvar"]) - # data is measured externally, but we now refer it to the internal side - ratios = vnom/data_eng["settings"]["v_var_scalar"] - x_sc = x_sc./ratios[1]^2 - r_s = r_s./ratios.^2 - g_sh = g_sh*ratios[1]^2 - b_sh = b_sh*ratios[1]^2 + _add_gen_cost_model!(math_obj, eng_obj) - # convert x_sc from list of upper triangle elements to an explicit dict - y_sh = g_sh + im*b_sh - z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) + if kron_reduced + if math_obj["configuration"]=="wye" + @assert(connections[end]==kr_neutral) + _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) + else + _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) + end + else + math_obj["connections"] = connections + end - nphases = length(eng_obj["fixed"][1]) - transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh, nphases=nphases, kron_reduced=kron_reduced) + data_math["gen"]["$(math_obj["index"])"] = math_obj - for w in 1:nrw - # 2-WINDING TRANSFORMER - # make virtual bus and mark it for reduction - tm_nom = eng_obj["vnom"][w] - # three-phase delta transformers need a correction of sqrt(3) - if eng_obj["configuration"][w]=="delta" && length(eng_obj["connections"][w])==3 - tm_nom = eng_obj["vnom"][w]*sqrt(3) + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => "solar.$name", + :to => "gen.$(math_obj["index"])", + :unmap_function => :_map_math2eng_solar!, + ) + + # time series + # TODO + for (fr, (f, to)) in _time_series_parameters["solar"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj, "gen", "$(math_obj["index"])", fr, to, f) end - # tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] + end + end +end - transformer_2wa_obj = Dict{String,Any}( - "name" => "_virtual_transformer.$name.$w", - "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", - "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], - "t_bus" => transformer_t_bus_w[w], - "tm_nom" => tm_nom, - "f_connections" => eng_obj["connections"][w], - "t_connections" => collect(1:nphases+1), - "configuration" => eng_obj["configuration"][w], - "polarity" => eng_obj["polarity"][w], - "tm" => eng_obj["tm"][w], - "fixed" => eng_obj["fixed"][w], - "index" => length(data_math["transformer"])+1 - ) +"converts engineering storage into mathematical storage" +function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) + for (name, eng_obj) in get(data_eng, "storage", Dict{Any,Dict{String,Any}}()) + math_obj = _init_math_obj("storage", name, eng_obj, length(data_math["storage"])+1) - for prop in ["tm_min", "tm_max", "tm_step"] - if haskey(eng_obj, prop) - transformer_2wa_obj[prop] = eng_obj[prop][w] - end - end + connections = eng_obj["connections"] + nconductors = data_math["conductors"] - if kron_reduced - # TODO fix how padding works, this is a workaround to get bank working - if all(eng_obj["configuration"] .== "wye") - f_connections = transformer_2wa_obj["f_connections"] - _pad_properties!(transformer_2wa_obj, ["tm_min", "tm_max", "tm", "fixed"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) + math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] - end - end + # needs to be in units MW + math_obj["energy"] = eng_obj["energy"] * data_eng["settings"]["v_var_scalar"] / 1e6 + math_obj["energy_rating"] = eng_obj["energy_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 + math_obj["charge_rating"] = eng_obj["charge_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 + math_obj["discharge_rating"] = eng_obj["discharge_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 + math_obj["charge_efficiency"] = eng_obj["charge_efficiency"] / 100.0 + math_obj["discharge_efficiency"] = eng_obj["discharge_efficiency"] / 100.0 + math_obj["thermal_rating"] = eng_obj["cm_ub"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + math_obj["qmin"] = eng_obj["qs_lb"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + math_obj["qmax"] = eng_obj["qs_ub"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + math_obj["r"] = eng_obj["rs"] + math_obj["x"] = eng_obj["xs"] + math_obj["p_loss"] = eng_obj["pex"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + math_obj["q_loss"] = eng_obj["qex"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + + math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["cm_ub"]))) + math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["cm_ub"]))) - data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj + if kron_reduced + _pad_properties!(math_obj, ["thermal_rating", "qmin", "qmax", "r", "x", "ps", "qs"], connections, kr_phases) + end - push!(to_map, "transformer.$(transformer_2wa_obj["index"])") + data_math["storage"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => "storage.$(math_obj["index"])", + :unmap_function => :_map_math2eng_storage!, + ) + + # time series + # TODO + for (fr, (f, to)) in _time_series_parameters["storage"] + if haskey(eng_obj, "$(fr)_time_series") + time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] + _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "storage", "$(math_obj["index"])", to) + end end end end -"" -function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4, lossless::Bool=false) +"converts engineering voltage sources into mathematical generators and (if needed) impedance branches to represent the loss model" +function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1,2,3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "voltage_source", Dict{Any,Any}()) nconductors = length(eng_obj["vm"]) - if lossless - #TODO add unreduced logic here - math_obj = _init_math_obj("voltage_source", eng_obj, length(data_math["gen"])+1) + math_obj = _init_math_obj("voltage_source", name, eng_obj, length(data_math["gen"])+1) - math_obj["name"] = name - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] - math_obj["pg"] = fill(0.0, nconductors) - math_obj["qg"] = fill(0.0, nconductors) - math_obj["configuration"] = "wye" + math_obj["name"] = "_virtual_gen.voltage_source.$name" + math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_status"] = eng_obj["status"] + math_obj["pg"] = fill(0.0, nconductors) + math_obj["qg"] = fill(0.0, nconductors) + math_obj["configuration"] = "wye" + math_obj["source_id"] = "_virtual_gen.$(eng_obj["source_id"])" - _add_gen_cost_model!(math_obj, eng_obj) + _add_gen_cost_model!(math_obj, eng_obj) - data_math["gen"]["$(math_obj["index"])"] = math_obj + map_to = "gen.$(math_obj["index"])" - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_voltage_source!, - ) - else + if haskey(eng_obj, "rs") && haskey(eng_obj, "xs") bus_obj = Dict{String,Any}( "bus_i" => length(data_math["bus"])+1, "index" => length(data_math["bus"])+1, @@ -866,26 +988,18 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "basekv" => data_math["basekv"] ) - data_math["bus"]["$(bus_obj["index"])"] = bus_obj + if kron_reduced + bus_obj["terminals"] = bus_obj["terminals"][1:end-1] + bus_obj["grounded"] = bus_obj["grounded"][1:end-1] + bus_obj["vm"] = bus_obj["vm"][1:end-1] + bus_obj["va"] = bus_obj["va"][1:end-1] + bus_obj["vmin"] = bus_obj["vmin"][1:end-1] + bus_obj["vmax"] = bus_obj["vmax"][1:end-1] + end - gen_obj = Dict{String,Any}( - "gen_bus" => bus_obj["bus_i"], - "connections" => collect(1:nconductors+1), - "name" => "_virtual_gen.voltage_source.$name", - "gen_status" => eng_obj["status"], - "pg" => fill(0.0, nconductors), - "qg" => fill(0.0, nconductors), - "model" => 2, - "startup" => 0.0, - "shutdown" => 0.0, - "ncost" => 3, - "cost" => [0.0, 1.0, 0.0], - "configuration" => "wye", - "index" => length(data_math["gen"]) + 1, - "source_id" => "_virtual_gen.$(eng_obj["source_id"])" - ) + math_obj["gen_bus"] = bus_obj["bus_i"] - data_math["gen"]["$(gen_obj["index"])"] = gen_obj + data_math["bus"]["$(bus_obj["index"])"] = bus_obj branch_obj = Dict{String,Any}( "name" => "_virtual_branch.voltage_source.$name", @@ -901,8 +1015,8 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "tranformer" => false, "switch" => false, "br_status" => 1, - "br_r" => eng_obj["rs"], - "br_x" => eng_obj["xs"], + "br_r" => _impedance_conversion(data_eng, eng_obj, "rs"), + "br_x" => _impedance_conversion(data_eng, eng_obj, "xs"), "g_fr" => zeros(nconductors, nconductors), "g_to" => zeros(nconductors, nconductors), "b_fr" => zeros(nconductors, nconductors), @@ -912,11 +1026,15 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: data_math["branch"]["$(branch_obj["index"])"] = branch_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => ["gen.$(gen_obj["index"])", "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"], - :unmap_function => :_map_math2eng_voltage_source!, - ) + map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] end + + data_math["gen"]["$(math_obj["index"])"] = math_obj + + data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( + :from => name, + :to => map_to, + :unmap_function => :_map_math2eng_voltage_source!, + ) end end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index a518b5b82..2bd6b79e8 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -584,3 +584,16 @@ function _convert_grounding(terminals, grounded, rg, xg) end return grounded_lossless, shunts end + + +"slices branches based on connected terminals" +function _slice_branches!(data_math::Dict{String,<:Any}) + for (_, branch) in data_math["branch"] + if haskey(branch, "f_connections") + N = length(branch["f_connections"]) + for prop in ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to"] + branch[prop] = branch[prop][1:N,1:N] + end + end + end +end diff --git a/src/io/common.jl b/src/io/common.jl index 8741777ec..e1a9f0e00 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -69,7 +69,7 @@ function correct_network_data!(data::Dict{String,Any}; make_pu::Bool=true) _PM.correct_voltage_angle_differences!(data) _PM.correct_thermal_limits!(data) _PM.correct_branch_directions!(data) - # _PM.check_branch_loops(data) + _PM.check_branch_loops(data) _PM.correct_bus_types!(data) _PM.correct_dcline_limits!(data) _PM.correct_cost_functions!(data) diff --git a/src/io/utils.jl b/src/io/utils.jl index 54000be34..1833b48a4 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -824,15 +824,3 @@ function _guess_dtype(value::AbstractString)::Type return String end end - - -function _slice_branches!(data_math) - for (_, branch) in data_math["branch"] - if haskey(branch, "f_connections") - N = length(branch["f_connections"]) - for prop in ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to"] - branch[prop] = branch[prop][1:N,1:N] - end - end - end -end From b45969b49baf053fabdb4f20ee87c425be5f883c Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 21 Apr 2020 13:00:14 +1000 Subject: [PATCH 143/224] prev ut fixing --- src/data_model/eng2math.jl | 12 +++++++++--- src/data_model/units.jl | 2 +- src/data_model/utils.jl | 12 ++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 00a37e0b3..7ece468de 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -190,9 +190,15 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_voltage_source!(data_math, data_eng; kron_reduced=kron_reduced) # post fix - #TODO fix this in place / throw error instead? IEEE8500 leads to switches - # with 3x3 R matrices but only 1 phase - _slice_branches!(data_math) + if kron_reduced + #TODO move this out when kron-reducing becomes a transformation + _kron_reduce_buses!(data_math) + else + #TODO fix this in place / throw error instead? IEEE8500 leads to switches + # with 3x3 R matrices but only 1 phase + #NOTE: Don't do this when kron-reducing, it will undo the padding + _slice_branches!(data_math) + end return data_math end diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 6fedde816..e56147429 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -383,7 +383,7 @@ function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true elseif prop in ibase_props comp[prop] = _apply_func_vals(comp[prop], x->x*ibase) elseif prop in rad2deg_props - comp[prop] = _apply_func_vals(comp[prop], x->x*_wrap_to_180(rad2deg(x))) + comp[prop] = _apply_func_vals(comp[prop], x->_wrap_to_180(rad2deg(x))) end end end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 2bd6b79e8..51149f1ac 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -597,3 +597,15 @@ function _slice_branches!(data_math::Dict{String,<:Any}) end end end + + +"transformations might have introduced buses with four-terminals; crop here" +function _kron_reduce_buses!(data_math) + for (_, bus) in data_math["bus"] + for prop in ["vm", "va", "vmax", "vmin"] + if haskey(bus, prop) && length(bus[prop])>3 + bus[prop] = bus[prop][1:3] + end + end + end +end From ad2c0ddc092504241977b08a83083adcd46dae01 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 21 Apr 2020 15:17:57 +1000 Subject: [PATCH 144/224] cap fix --- src/io/opendss.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index ed80dc043..44c35c433 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -202,8 +202,6 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String "status" => convert(Int, defaults["enabled"]), "source_id" => "capacitor.$name", "phases" => nphases, - "f_connections" => f_terminals, - "t_connections" => t_terminals, ) if import_all @@ -238,6 +236,7 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) eng_obj["bs"] = B + eng_obj["connections"] = terminals _add_eng_obj!(data_eng, "shunt_capacitor", name, eng_obj) else From 0a3206342d157d8568da49ee822a9b1ac8dc0c53 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 21 Apr 2020 15:18:16 +1000 Subject: [PATCH 145/224] subphase transformer terminals fix --- src/data_model/eng2math.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 7ece468de..e2ac84375 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -393,6 +393,8 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh) + dims = length(eng_obj["tm"][1]) + for w in 1:nrw # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction @@ -404,7 +406,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic "t_bus" => transformer_t_bus_w[w], "tm_nom" => tm_nom, "f_connections" => eng_obj["connections"][w], - "t_connections" => collect(1:4), + "t_connections" => collect(1:dims+1), "configuration" => eng_obj["configuration"][w], "polarity" => eng_obj["polarity"][w], "tm" => eng_obj["tm"][w], From dd66cc757a917f9cf8c4331134ee39a8e6d6ac3a Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 21 Apr 2020 15:36:14 +1000 Subject: [PATCH 146/224] sourcegen fix sourcegen needs nconductors+1 connections --- src/data_model/eng2math.jl | 14 +++++++++----- src/data_model/utils.jl | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index e2ac84375..a4dd18060 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -15,7 +15,7 @@ const _1to1_maps = Dict{String,Vector{String}}( "generator" => ["pg", "qg", "configuration", "connections", "source_id", "dss"], "solar" => ["configuration", "connections", "source_id", "dss"], "storage" => ["status", "energy", "ps", "qs", "connections", "source_id", "dss"], - "voltage_source" => ["connections", "source_id", "dss"], + "voltage_source" => ["source_id", "dss"], ) "list of nodal type elements in the engineering model" @@ -684,9 +684,9 @@ function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng: if kron_reduced filter = _kron_reduce_branch!(math_obj, Vector{String}([]), ["gs", "bs"], - eng_obj["f_connections"], kr_neutral + eng_obj["connections"], kr_neutral ) - connections = eng_obj["f_connections"][filter] + connections = eng_obj["connections"][filter] _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) else math_obj["connections"] = eng_obj["connections"] @@ -970,7 +970,7 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: math_obj = _init_math_obj("voltage_source", name, eng_obj, length(data_math["gen"])+1) math_obj["name"] = "_virtual_gen.voltage_source.$name" - math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] + math_obj["gen_bus"] = gen_bus = data_math["bus_lookup"][eng_obj["bus"]] math_obj["gen_status"] = eng_obj["status"] math_obj["pg"] = fill(0.0, nconductors) math_obj["qg"] = fill(0.0, nconductors) @@ -1005,7 +1005,7 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: bus_obj["vmax"] = bus_obj["vmax"][1:end-1] end - math_obj["gen_bus"] = bus_obj["bus_i"] + math_obj["gen_bus"] = gen_bus = bus_obj["bus_i"] data_math["bus"]["$(bus_obj["index"])"] = bus_obj @@ -1032,6 +1032,10 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "index" => length(data_math["branch"])+1 ) + # finally, we have to set a neutral for the virtual generator + neutral = _get_ground_math!(data_math["bus"]["$gen_bus"], exclude_terminals=[1:nconductors...]) + math_obj["connections"] = [collect(1:nconductors)..., neutral] + data_math["branch"]["$(branch_obj["index"])"] = branch_obj map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 51149f1ac..9b1055571 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -609,3 +609,21 @@ function _kron_reduce_buses!(data_math) end end end + + +"generate a new, unique terminal" +new_t(terms) = maximum([terms[isa.(terms, Int)]..., 3])+1 + + +"get a grounded terminal from a bus; if not present, create one" +function _get_ground_math!(bus; exclude_terminals=[]) + tgs = setdiff(bus["terminals"][bus["grounded"]], exclude_terminals) + if !isempty(tgs) + return tgs[1] + else + n = new_t[bus["terminals"]] + push!(bus["terminals"], n) + push!(bus["grounded"], true) + return n + end +end From 1ff6bc383d5a32388a9cb49520fb49efe8964389 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 21 Apr 2020 15:58:49 +1000 Subject: [PATCH 147/224] fix loss model transformers if subphase, also the loss model should be subphase --- src/data_model/eng2math.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index a4dd18060..a12f6fc01 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -391,9 +391,8 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic y_sh = g_sh + im*b_sh z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) - transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh) - dims = length(eng_obj["tm"][1]) + transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh, nphases=dims) for w in 1:nrw # 2-WINDING TRANSFORMER From 5b66e697df9a4c673d83276e3391104fa84f98b7 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 21 Apr 2020 16:46:35 +1000 Subject: [PATCH 148/224] minor fix all old ut + 4w ut (external) working now --- src/data_model/eng2math.jl | 3 ++- src/data_model/utils.jl | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index a12f6fc01..781adc311 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -391,7 +391,8 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic y_sh = g_sh + im*b_sh z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) - dims = length(eng_obj["tm"][1]) + #TODO remove once moving out kron-reduction + dims = kron_reduced ? 3 : length(eng_obj["tm"][1]) transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh, nphases=dims) for w in 1:nrw diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 9b1055571..e79650fda 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -621,7 +621,7 @@ function _get_ground_math!(bus; exclude_terminals=[]) if !isempty(tgs) return tgs[1] else - n = new_t[bus["terminals"]] + n = new_t([bus["terminals"]]) push!(bus["terminals"], n) push!(bus["grounded"], true) return n From 5a36637b7299b9fbb02fe4d25bc66bdb940ece16 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 21 Apr 2020 18:41:10 +1000 Subject: [PATCH 149/224] json parser fix made json parser more robust --- src/io/json.jl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/io/json.jl b/src/io/json.jl index 044eacf0f..64ddc3888 100644 --- a/src/io/json.jl +++ b/src/io/json.jl @@ -32,7 +32,7 @@ end function _parse_mats!(data::Dict{String,<:Any}) - stack = [(data, k, v) for (k, v) in data] + stack = Array{Tuple{Any, Any, Any}}([(data, k, v) for (k, v) in data]) while !isempty(stack) (store, k, v) = pop!(stack) @@ -76,15 +76,19 @@ function parse_json(io::IO; kwargs...)::Dict{String,Any} end -function print_file(path::String, data) +function print_file(path::String, data; kwargs...) open(path, "w") do io - print_file(io, data) + print_file(io, data; kwargs...) end end -function print_file(io::IO, data) - JSON.print(io, JSON.parse(sprint(show_json, PMDSerialization(), data))) +function print_file(io::IO, data; indent=false) + if indent + JSON.print(io, JSON.parse(sprint(show_json, PMDSerialization(), data)), 4) + else + JSON.print(io, JSON.parse(sprint(show_json, PMDSerialization(), data))) + end end From eb8d64f329ffae8f098aa2cd0568c9123bfe72c6 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 21 Apr 2020 18:42:02 +1000 Subject: [PATCH 150/224] expanded math2eng solution parsing --- src/data_model/math2eng.jl | 16 +++++++++++++--- src/data_model/units.jl | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index 9a2099f75..b94e2f168 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -113,6 +113,14 @@ end function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "line", map) + math_obj = _get_math_obj(data_math, map[:to]) + + merge!(eng_obj, math_obj) + + if !isempty(eng_obj) + data_eng["line"][map[:from]] = eng_obj + end end @@ -129,9 +137,11 @@ function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dic trans_2wa_ids = [index for (comp_type, index) in split.(map[:to], ".", limit=2) if comp_type=="transformer"] - prop_map = Dict("pf"=>"p", "qf"=>"q") + prop_map = Dict("pf"=>"p", "qf"=>"q", "crt_fr"=>"crt", "cit_fr"=>"cit") for (prop_from, prop_to) in prop_map - eng_obj[prop_to] = [get(data_math["transformer"][id], prop_from, NaN) for id in trans_2wa_ids] + if any(haskey(data_math["transformer"][id], prop_from) for id in trans_2wa_ids) + eng_obj[prop_to] = [get(data_math["transformer"][id], prop_from, NaN) for id in trans_2wa_ids] + end end if !isempty(eng_obj) @@ -144,4 +154,4 @@ function _map_math2eng_root!(data_eng::Dict{String,<:Any}, data_math::Dict{Strin data_eng["per_unit"] = data_math["per_unit"] data_eng["settings"] = Dict{String,Any}("sbase" => data_math["baseMVA"]) -end \ No newline at end of file +end diff --git a/src/data_model/units.jl b/src/data_model/units.jl index e56147429..24154a8d2 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -5,13 +5,20 @@ const _dimensionalize_math = Dict{String,Dict{String,Vector{String}}}( "vbase"=>Vector{String}(["vm", "vr", "vi", "vm_pp", "vm_pn"]) ), "gen" => Dict{String,Vector{String}}( - "sbase"=>Vector{String}(["pg", "qg", "pg_bus", "qg_bus"]) + "sbase"=>Vector{String}(["pg", "qg", "pg_bus", "qg_bus"]), + "ibase"=>Vector{String}(["crg", "cig", "crg_bus", "cig_bus"]) ), "load" => Dict{String,Vector{String}}( - "sbase"=>Vector{String}(["pd", "qd", "pd_bus", "qd_bus"]) + "sbase"=>Vector{String}(["pd", "qd", "pd_bus", "qd_bus"]), + "ibase"=>Vector{String}(["crd", "cid", "crd_bus", "cid_bus"]) ), - "line" => Dict{String,Vector{String}}( - "sbase"=>Vector{String}(["pf", "qf", "pt", "qt"]) + "branch" => Dict{String,Vector{String}}( + "sbase"=>Vector{String}(["pf", "qf", "pt", "qt"]), + "ibase"=>Vector{String}(["cr_fr", "ci_fr", "cr_to", "cr_to"]) + ), + "transformer" => Dict{String,Vector{String}}( + "ibase_fr"=>Vector{String}(["crt_fr", "cit_fr"]), + "ibase_to"=>Vector{String}(["crt_to", "cit_to"]) ), ) @@ -369,6 +376,8 @@ function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true ibase_props = mult_ibase ? get(dimensionalize_math_comp, "ibase", []) : [] rad2deg_props = convert_rad2deg ? get(dimensionalize_math_comp, "rad2deg", []) : [] + + for (id, comp) in comp_dict if !isempty(vbase_props) || !isempty(ibase_props) vbase = math_model[comp_type][id]["vbase"] @@ -386,6 +395,24 @@ function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true comp[prop] = _apply_func_vals(comp[prop], x->_wrap_to_180(rad2deg(x))) end end + + if comp_type=="transformer" + # transformers have different vbase/ibase on each side + f_bus = math_model["transformer"][id]["f_bus"] + f_vbase = math_model["bus"]["$f_bus"]["vbase"] + t_bus = math_model["transformer"][id]["t_bus"] + t_vbase = math_model["bus"]["$t_bus"]["vbase"] + f_ibase = sbase/f_vbase + t_ibase = sbase/t_vbase + + for (prop, val) in comp + if prop in dimensionalize_math_comp["ibase_fr"] + comp[prop] = _apply_func_vals(comp[prop], x->x*f_ibase) + elseif prop in dimensionalize_math_comp["ibase_to"] + comp[prop] = _apply_func_vals(comp[prop], x->x*t_ibase) + end + end + end end end From 411b3f984b308d1c1ffa6ad4360932a4651461a0 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 10:57:41 -0600 Subject: [PATCH 151/224] REF: Switch to engineering model by default Updates unit tests to reflect that data models are engineering by default now. FIX: json parsing "files" field in eng model is supposed to be a set, fixed in parser TODO update unit tests to use eng model instead of math --- src/io/common.jl | 10 +- src/io/json.jl | 4 + test/data.jl | 4 +- test/delta_gens.jl | 2 +- test/loadmodels.jl | 8 +- test/mld.jl | 4 +- test/opendss.jl | 130 ++++++++++----------- test/opf.jl | 84 +++++++------- test/opf_bf.jl | 4 +- test/opf_iv.jl | 6 +- test/pf.jl | 267 +++++++++++++++++++++++--------------------- test/pf_iv.jl | 25 ----- test/runtests.jl | 2 - test/shunt.jl | 2 +- test/transformer.jl | 22 ++-- 15 files changed, 280 insertions(+), 294 deletions(-) delete mode 100644 test/pf_iv.jl diff --git a/src/io/common.jl b/src/io/common.jl index e1a9f0e00..6cb675ad2 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -3,10 +3,14 @@ Parses the IOStream of a file into a Three-Phase PowerModels data structure. """ -function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="mathematical", import_all::Bool=false, bank_transformers::Bool=true, transformations::Vector{Function}=Vector{Function}([]))::Dict{String,Any} +function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="engineering", import_all::Bool=false, bank_transformers::Bool=true, transformations::Vector{<:Function}=Vector{Function}([]))::Dict{String,Any} if filetype == "dss" data_eng = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) + for transformation in transformations + transformation(data_eng) + end + if data_model == "mathematical" return transform_data_model(data_eng; make_pu=true) else @@ -37,7 +41,7 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=false)::Dict{String,Any} +function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=true)::Dict{String,Any} current_data_model = get(data, "data_model", "mathematical") if current_data_model == "engineering" @@ -50,7 +54,7 @@ function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, Memento.warn(_LOGGER, "A mathematical data model cannot be converted back to an engineering data model, irreversible transformations have been made") return data else - @warn "Data model '$current_data_model' is not recognized, no model type transformation performed" + Memento.warn(_LOGGER, "Data model '$current_data_model' is not recognized, no model type transformation performed") return data end end diff --git a/src/io/json.jl b/src/io/json.jl index 64ddc3888..0ee1a71c9 100644 --- a/src/io/json.jl +++ b/src/io/json.jl @@ -68,6 +68,10 @@ function parse_json(io::IO; kwargs...)::Dict{String,Any} _parse_mats!(data) + if haskey(data, "files") + data["files"] = Set(data["files"]) + end + if get(kwargs, :validate, true) PowerModels.correct_network_data!(data) end diff --git a/test/data.jl b/test/data.jl index 56cbf2739..86c10677c 100644 --- a/test/data.jl +++ b/test/data.jl @@ -71,10 +71,10 @@ end @testset "node counting functions" begin dss = PMD.parse_dss("../test/data/opendss/case5_phase_drop.dss") - pmd = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss") + math = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss"; data_model="mathematical") @test count_nodes(dss) == 10 # stopped excluding source from node count - @test count_nodes(dss) == count_nodes(pmd) + @test count_nodes(dss) == count_nodes(math) dss = PMD.parse_dss("../test/data/opendss/ut_trans_2w_yy.dss") @test count_nodes(dss) == 12 # stopped excluding source from node count diff --git a/test/delta_gens.jl b/test/delta_gens.jl index e97914321..98b2d81c1 100644 --- a/test/delta_gens.jl +++ b/test/delta_gens.jl @@ -4,7 +4,7 @@ # This test checks the generators are connected properly by comparing them # to equivalent constant-power loads. This is achieved by fixing their bounds. @testset "ACP/ACR tests" begin - pmd_1 = PMD.parse_file("$pmd_path/test/data/opendss/case3_delta_gens.dss") + pmd_1 = PMD.parse_file("$pmd_path/test/data/opendss/case3_delta_gens.dss"; data_model="mathematical") # convert to constant power loads for (_, load) in pmd_1["load"] diff --git a/test/loadmodels.jl b/test/loadmodels.jl index 0b2ca2e49..701252e17 100644 --- a/test/loadmodels.jl +++ b/test/loadmodels.jl @@ -2,7 +2,7 @@ @testset "test loadmodels pf" begin @testset "loadmodels connection variations" begin - pmd = PMD.parse_file("../test/data/opendss/case3_lm_1230.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_lm_1230.dss"; data_model="mathematical") pm = PMs.instantiate_model(pmd, PMs.ACPPowerModel, PMD.build_mc_pf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) # voltage magnitude at load bus @@ -22,7 +22,7 @@ @test isapprox(qd(sol, pmd, "d3ph213"), [0.100, 0.100, 0.100], atol=1E-4) end @testset "loadmodels 1/2/5 in acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss"; data_model="mathematical") pm = PMs.instantiate_model(pmd, PMs.ACPPowerModel, PMD.build_mc_pf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) # voltage magnitude at load bus @@ -50,7 +50,7 @@ @test isapprox(qd(sol, pmd, "y3phm5"), [0.0831, 0.0997, 0.1006], atol=1E-4) end @testset "loadmodels 1/2/5 in acr pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss"; data_model="mathematical") pm = PMs.instantiate_model(pmd, PMs.ACRPowerModel, PMD.build_mc_pf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) # voltage magnitude at load bus @@ -78,7 +78,7 @@ @test isapprox(qd(sol, pmd, "y3phm5"), [0.0831, 0.0997, 0.1006], atol=1E-4) end @testset "loadmodels 1/2/5 in ivr pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss"; data_model="mathematical") pm = PMs.instantiate_model(pmd, PMs.IVRPowerModel, PMD.build_mc_pf_iv, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) # voltage magnitude at load bus diff --git a/test/mld.jl b/test/mld.jl index 6d3b0133a..c93e615a5 100644 --- a/test/mld.jl +++ b/test/mld.jl @@ -74,7 +74,7 @@ end @testset "transformer nfa mld" begin - mp_data = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + mp_data = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; data_model="mathematical") result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @@ -105,7 +105,7 @@ end @testset "transformer case" begin - dss = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + dss = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; data_model="mathematical") result = run_mc_mld_bf(dss, LPUBFDiagPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED diff --git a/test/opendss.jl b/test/opendss.jl index 17b148f74..722957273 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -2,14 +2,14 @@ @testset "test opendss parser" begin @testset "bus discovery parsing" begin - eng = PMD.parse_file("../test/data/opendss/test_bus_discovery.dss"; data_model="engineering") + eng = parse_file("../test/data/opendss/test_bus_discovery.dss") @test length(eng["bus"]) == 24 @test all(k in keys(eng["bus"]) for k in [["$i" for i in 1:23]..., "sourcebus"]) end @testset "loadshape parsing" begin - dss = PMD.parse_dss("../test/data/opendss/loadshapes.dss") + dss = parse_dss("../test/data/opendss/loadshapes.dss") loadshapes = Dict{String,Any}() for (name, ls) in dss["loadshape"] @@ -23,7 +23,7 @@ end @testset "arrays from files" begin - dss = PMD.parse_dss("../test/data/opendss/test2_master.dss") + dss = parse_dss("../test/data/opendss/test2_master.dss") @test isa(dss["load"]["ld3"]["yearly"], Vector) @test isa(dss["load"]["ld2"]["daily"], Vector) @@ -57,21 +57,21 @@ # TODO fix, do we support these previously erroring cases now? # @testset "opendss parse load model errors" begin - # dss = PMD.parse_dss("../test/data/opendss/loadparser_error.dss") + # dss = parse_dss("../test/data/opendss/loadparser_error.dss") # for (name, load) in dss["load"] # _dss = deepcopy(dss) # _dss["load"] = Dict{String,Any}(name => load) - # @test_throws(TESTLOG, AssertionError, PMD.parse_opendss(_dss)) + # @test_throws(TESTLOG, AssertionError, parse_opendss(_dss)) # end # end @testset "opendss parse load model warnings" begin for model in [3, 4, 7, 8] - dss = PMD.parse_dss("../test/data/opendss/loadparser_warn_model.dss") + dss = parse_dss("../test/data/opendss/loadparser_warn_model.dss") dss["load"] = Dict{String,Any}((n,l) for (n,l) in dss["load"] if l["name"]=="d1phm$model") Memento.setlevel!(TESTLOG, "info") - @test_warn(TESTLOG, ": load model $model not supported. Treating as model 1.", PMD.parse_opendss(dss)) + @test_warn(TESTLOG, ": load model $model not supported. Treating as model 1.", parse_opendss(dss)) Memento.setlevel!(TESTLOG, "error") end end @@ -81,60 +81,60 @@ Memento.setlevel!(TESTLOG, "info") @test_throws(TESTLOG, ErrorException, - PMD.parse_file("../test/data/opendss/test_simple2.dss")) + parse_file("../test/data/opendss/test_simple2.dss"; data_model="mathematical")) @test_warn(TESTLOG, "Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.", - PMD.parse_file("../test/data/opendss/test2_master.dss")) + parse_file("../test/data/opendss/test2_master.dss")) @test_warn(TESTLOG, "Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.", - PMD.parse_file("../test/data/opendss/test2_master.dss")) + parse_file("../test/data/opendss/test2_master.dss")) @test_warn(TESTLOG, "reactors as constant impedance elements is not yet supported, treating reactor.reactor1 like line", - PMD.parse_file("../test/data/opendss/test2_master.dss")) + parse_file("../test/data/opendss/test2_master.dss")) @test_warn(TESTLOG, "line.l1: like=something cannot be found", - PMD.parse_file("../test/data/opendss/test2_master.dss")) + parse_file("../test/data/opendss/test2_master.dss")) @test_warn(TESTLOG, "Rg,Xg are not fully supported", - PMD.parse_file("../test/data/opendss/test2_master.dss")) + parse_file("../test/data/opendss/test2_master.dss")) Memento.TestUtils.@test_log(TESTLOG, "info", "Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"", - PMD.parse_file("../test/data/opendss/test2_master.dss")) + parse_file("../test/data/opendss/test2_master.dss")) Memento.TestUtils.@test_log(TESTLOG, "info", "Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"", - PMD.parse_file("../test/data/opendss/test2_master.dss")) + parse_file("../test/data/opendss/test2_master.dss")) Memento.TestUtils.@test_log(TESTLOG, "info", "Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"", - PMD.parse_file("../test/data/opendss/test2_master.dss")) + parse_file("../test/data/opendss/test2_master.dss")) Memento.setlevel!(TESTLOG, "error") end - eng = PMD.parse_file("../test/data/opendss/test2_master.dss"; data_model="engineering", import_all=true) - pmd = PMD.parse_file("../test/data/opendss/test2_master.dss"; data_model="mathematical", import_all=true) + eng = parse_file("../test/data/opendss/test2_master.dss", import_all=true) + math = parse_file("../test/data/opendss/test2_master.dss"; data_model="mathematical", import_all=true) @testset "buscoords automatic parsing" begin - @test all(haskey(bus, "lon") && haskey(bus, "lat") for bus in values(pmd["bus"]) if "bus_i" in 1:10) + @test all(haskey(bus, "lon") && haskey(bus, "lat") for bus in values(math["bus"]) if "bus_i" in 1:10) end @testset "import_all parsing" begin @test all(haskey(comp, "dss") && isa(comp["dss"], Dict) for (comp_type, comps) in eng if isa(comps, Dict) for (_,comp) in comps if isa(comp, Dict) && comp_type != "bus" && comp_type != "settings") - @test all(haskey(comp, "dss") && isa(comp["dss"], Dict) for (comp_type, comps) in pmd if isa(comps, Dict) for (id,comp) in comps if isa(comp, Dict) && comp_type != "bus" && comp_type != "settings" && comp_type != "map" && !startswith(comp["name"], "_virtual")) + @test all(haskey(comp, "dss") && isa(comp["dss"], Dict) for (comp_type, comps) in math if isa(comps, Dict) for (id,comp) in comps if isa(comp, Dict) && comp_type != "bus" && comp_type != "settings" && comp_type != "map" && !startswith(comp["name"], "_virtual")) end @testset "opendss parse generic parser verification" begin - dss = PMD.parse_dss("../test/data/opendss/test2_master.dss") + dss = parse_dss("../test/data/opendss/test2_master.dss") @test dss["line"]["l7"]["test_param"] == 100.0 - @test pmd["name"] == "test2" + @test math["name"] == "test2" - @test length(pmd) == 19 + @test length(math) == 19 @test length(dss) == 16 for (key, len) in zip(["bus", "load", "shunt", "branch", "gen", "dcline", "transformer"], [33, 4, 5, 27, 4, 0, 10]) - @test haskey(pmd, key) - @test length(pmd[key]) == len + @test haskey(math, key) + @test length(math[key]) == len end @test all(haskey(dss, key) for key in ["loadshape", "linecode", "buscoords", "options", "filename"]) @@ -143,12 +143,12 @@ # TODO fix, the way we calculate voltage bases changed @testset "opendss parse like" begin # for i in [6, 3] - # basekv_bri = pmd["bus"][string(pmd["branch"]["$i"]["f_bus"])]["base_kv"] - # @test all(isapprox.(diag(pmd["branch"]["$i"]["b_fr"]), (3.4 * 2.0 + 1.6) / 3.0 * (basekv_bri^2 / pmd["baseMVA"] * 2.0 * pi * 60.0 / 1e9) / 2.0; atol=1e-6)) + # basekv_bri = math["bus"][string(math["branch"]["$i"]["f_bus"])]["base_kv"] + # @test all(isapprox.(diag(math["branch"]["$i"]["b_fr"]), (3.4 * 2.0 + 1.6) / 3.0 * (basekv_bri^2 / math["baseMVA"] * 2.0 * pi * 60.0 / 1e9) / 2.0; atol=1e-6)) # end - # @test all(isapprox.(pmd["branch"]["9"]["br_r"].*(115/69)^2, diagm(0 => fill(6.3012e-8, 3)); atol=1e-12)) - # @test all(isapprox.(pmd["branch"]["9"]["br_x"].*(115/69)^2, diagm(0 => fill(6.3012e-7, 3)); atol=1e-12)) + # @test all(isapprox.(math["branch"]["9"]["br_r"].*(115/69)^2, diagm(0 => fill(6.3012e-8, 3)); atol=1e-12)) + # @test all(isapprox.(math["branch"]["9"]["br_x"].*(115/69)^2, diagm(0 => fill(6.3012e-7, 3)); atol=1e-12)) for k in ["pd_nom", "qd_nom"] @test all(isapprox.(eng["load"]["ld2"][k], eng["load"]["ld4"][k]; atol=1e-12)) @@ -164,17 +164,17 @@ end end - # for k in keys(pmd["branch"]["11"]) + # for k in keys(math["branch"]["11"]) # if !(k in ["f_bus", "t_bus", "index", "name", "linecode", "source_id", "t_connections", "f_connections"]) # mult = 1.0 # if k in ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to"] # # compensation for the different voltage base - # basekv_br5 = pmd["bus"][string(pmd["branch"]["5"]["f_bus"])]["base_kv"] - # basekv_br2 = pmd["bus"][string(pmd["branch"]["2"]["f_bus"])]["base_kv"] + # basekv_br5 = math["bus"][string(math["branch"]["5"]["f_bus"])]["base_kv"] + # basekv_br2 = math["bus"][string(math["branch"]["2"]["f_bus"])]["base_kv"] # zmult = (basekv_br5/basekv_br2)^2 # mult = (k in ["br_r", "br_x"]) ? zmult : 1/zmult # end - # @test all(isapprox.(pmd["branch"]["5"][k].*mult, pmd["branch"]["2"][k]; atol=1e-12)) + # @test all(isapprox.(math["branch"]["5"][k].*mult, math["branch"]["2"][k]; atol=1e-12)) # end # end end @@ -193,12 +193,12 @@ @testset "opendss parse switch length verify" begin @testset "branches with switches" begin @test eng["switch"]["_l4"]["length"] == 0.001 - @test !all(get(br, "switch", false) for (_,br) in pmd["branch"] if !startswith(br["name"],"_virtual_branch.switch")) + @test !all(get(br, "switch", false) for (_,br) in math["branch"] if !startswith(br["name"],"_virtual_branch.switch")) end end @testset "opendss parse transformer parsing verify" begin - dss_data = PMD.parse_dss("../test/data/opendss/test_transformer_formatting.dss") + dss_data = parse_dss("../test/data/opendss/test_transformer_formatting.dss") transformer = dss_data["transformer"]["transformer_test"] @test transformer["phases"] == 3 @test transformer["tap"] == PMD._parse_rpn("(0.00625 12 * 1 +)") @@ -213,13 +213,13 @@ PMD._apply_like!(dss_data["transformer"]["reg4b"], dss_data, "transformer") @test dss_data["transformer"]["reg4b"]["%loadloss"] == dss_data["transformer"]["reg4a"]["%loadloss"] - eng_data = PMD.parse_file("../test/data/opendss/test_transformer_formatting.dss"; data_model="engineering") + eng_data = parse_file("../test/data/opendss/test_transformer_formatting.dss"; data_model="engineering") @test all(all(eng_data["transformer"]["$n"]["tm"] .== tm) for (n, tm) in zip(["transformer_test", "reg4"], [[fill(1.075, 3), fill(1.5, 3), fill(0.9, 3)], [ones(3), ones(3)]])) end @testset "opendss parse storage" begin - pmd_storage = PMD.parse_file("../test/data/opendss/case3_balanced_battery.dss") - for bat in values(pmd_storage["storage"]) + math_storage = parse_file("../test/data/opendss/case3_balanced_battery.dss"; data_model="mathematical") + for bat in values(math_storage["storage"]) for key in ["energy", "storage_bus", "energy_rating", "charge_rating", "discharge_rating", "charge_efficiency", "discharge_efficiency", "thermal_rating", "qmin", "qmax", "r", "x", "p_loss", "q_loss", "status", "source_id"] @@ -230,36 +230,36 @@ end end - @test pmd_storage["storage"]["1"]["source_id"] == "storage.s1" + @test math_storage["storage"]["1"]["source_id"] == "storage.s1" end @testset "opendss parse verify source_id" begin - @test pmd["shunt"]["2"]["source_id"] == "capacitor.c1" - @test pmd["shunt"]["4"]["source_id"] == "reactor.reactor3" + @test math["shunt"]["2"]["source_id"] == "capacitor.c1" + @test math["shunt"]["4"]["source_id"] == "reactor.reactor3" - @test pmd["branch"]["8"]["source_id"] == "line.l1" - @test pmd["transformer"]["9"]["source_id"] == "_virtual_transformer.transformer.t4.1" # winding indicated by .1 - @test pmd["branch"]["25"]["source_id"] == "reactor.reactor1" + @test math["branch"]["8"]["source_id"] == "line.l1" + @test math["transformer"]["9"]["source_id"] == "_virtual_transformer.transformer.t4.1" # winding indicated by .1 + @test math["branch"]["25"]["source_id"] == "reactor.reactor1" - @test pmd["gen"]["4"]["source_id"] == "_virtual_gen.vsource.source" - @test pmd["gen"]["1"]["source_id"] == "generator.g2" + @test math["gen"]["4"]["source_id"] == "_virtual_gen.vsource.source" + @test math["gen"]["1"]["source_id"] == "generator.g2" - @test pmd["load"]["1"]["source_id"] == "load.ld1" + @test math["load"]["1"]["source_id"] == "load.ld1" - @test all(haskey(component, "source_id") for component_type in PMD._dss_supported_components for component in values(get(pmd, component_type, Dict())) if component_type != "bus") + @test all(haskey(component, "source_id") for component_type in PMD._dss_supported_components for component in values(get(math, component_type, Dict())) if component_type != "bus") end @testset "opendss parse verify order of properties on line" begin - pmd1 = PMD.parse_file("../test/data/opendss/case3_balanced.dss") - pmd2 = PMD.parse_file("../test/data/opendss/case3_balanced_prop-order.dss") + math1 = parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") + math2 = parse_file("../test/data/opendss/case3_balanced_prop-order.dss"; data_model="mathematical") - delete!(pmd1, "map") - delete!(pmd2, "map") + delete!(math1, "map") + delete!(math2, "map") - @test pmd1 == pmd2 + @test math1 == math2 - dss1 = PMD.parse_dss("../test/data/opendss/case3_balanced.dss") - dss2 = PMD.parse_dss("../test/data/opendss/case3_balanced_prop-order.dss") + dss1 = parse_dss("../test/data/opendss/case3_balanced.dss") + dss2 = parse_dss("../test/data/opendss/case3_balanced_prop-order.dss") @test dss1 != dss2 @test all(a == b for (a, b) in zip(dss2["line"]["ohline"]["prop_order"],["name", "bus1", "bus2", "linecode", "rmatrix", "length"])) @@ -267,7 +267,7 @@ end @testset "opendss parse verify mvasc3/mvasc1 circuit parse" begin - dss = PMD.parse_dss("../test/data/opendss/test_simple.dss") + dss = parse_dss("../test/data/opendss/test_simple.dss") circuit = PMD._create_vsource("source"; PMD._to_kwargs(dss["vsource"]["source"])...) @test circuit["mvasc1"] == 2100.0 @@ -275,7 +275,7 @@ @test isapprox(circuit["isc3"], 9538.8; atol=1e-1) @test isapprox(circuit["isc1"], 10543.0; atol=1e-1) - dss = PMD.parse_dss("../test/data/opendss/test_simple3.dss") + dss = parse_dss("../test/data/opendss/test_simple3.dss") circuit = PMD._create_vsource("source"; PMD._to_kwargs(dss["vsource"]["source"])...) @test circuit["mvasc1"] == 2100.0 @@ -283,7 +283,7 @@ @test circuit["isc3"] == 9538.8 @test isapprox(circuit["isc1"], 10543.0; atol=1e-1) - dss = PMD.parse_dss("../test/data/opendss/test_simple4.dss") + dss = parse_dss("../test/data/opendss/test_simple4.dss") circuit = PMD._create_vsource("source"; PMD._to_kwargs(dss["vsource"]["source"])...) @test isapprox(circuit["mvasc1"], 2091.5; atol=1e-1) @@ -293,12 +293,12 @@ end end -# @testset "test json parser" begin -# pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") +@testset "test json parser" begin + eng = parse_file("../test/data/opendss/case3_balanced.dss") -# io = PipeBuffer() -# PMD.print_file(io, pmd) -# pmd_json_file = PMD.parse_file(io) + io = PipeBuffer() + print_file(io, eng) + eng_json_file = parse_file(io) -# @test pmd == pmd_json_file -# end + @test eng == eng_json_file +end diff --git a/test/opf.jl b/test/opf.jl index 5b2180332..30d43729b 100644 --- a/test/opf.jl +++ b/test/opf.jl @@ -2,69 +2,63 @@ @testset "test opf" begin @testset "test matpower opf" begin + case5 = PMs.parse_file("../test/data/matpower/case5.m") + case30 = PMs.parse_file("../test/data/matpower/case30.m") + + PMD.make_multiconductor!(case5, 3) + PMD.make_multiconductor!(case30, 3) + @testset "5-bus matpower acp opf" begin - mp_data = PMs.parse_file("../test/data/matpower/case5.m") - PMD.make_multiconductor!(mp_data, 3) - result = run_mc_opf(mp_data, PMs.ACPPowerModel, ipopt_solver) + result = run_mc_opf(case5, PMs.ACPPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 45522.096; atol=1e-1) - @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 0.3999999; atol=1e-3) for c in 1:mp_data["conductors"]) - @test all(isapprox(result["solution"]["bus"]["2"]["va"][c], -0.0538204; atol=1e-5) for c in 1:mp_data["conductors"]) + @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 0.3999999; atol=1e-3) for c in 1:case5["conductors"]) + @test all(isapprox(result["solution"]["bus"]["2"]["va"][c], -0.0538204; atol=1e-5) for c in 1:case5["conductors"]) end @testset "5-bus matpower acr opf" begin - mp_data = PMs.parse_file("../test/data/matpower/case5.m") - PMD.make_multiconductor!(mp_data, 3) - result = run_mc_opf(mp_data, PMs.ACRPowerModel, ipopt_solver) + result = run_mc_opf(case5, PMs.ACRPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 45522.096; atol=1e-1) calc_va(id) = atan.(result["solution"]["bus"][id]["vi"], result["solution"]["bus"][id]["vr"]) - @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 0.3999999; atol=1e-3) for c in 1:mp_data["conductors"]) - @test all(isapprox(calc_va("2")[c], -0.0538204; atol=1e-5) for c in 1:mp_data["conductors"]) + @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 0.3999999; atol=1e-3) for c in 1:case5["conductors"]) + @test all(isapprox(calc_va("2")[c], -0.0538204; atol=1e-5) for c in 1:case5["conductors"]) end @testset "30-bus matpower acp opf" begin - mp_data = PMs.parse_file("../test/data/matpower/case30.m") - PMD.make_multiconductor!(mp_data, 3) - result = run_mc_opf(mp_data, PMs.ACPPowerModel, ipopt_solver) + result = run_mc_opf(case30, PMs.ACPPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 614.007; atol=1e-1) - @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 2.192189; atol=1e-3) for c in 1:mp_data["conductors"]) - @test all(isapprox(result["solution"]["bus"]["2"]["va"][c], -0.071853; atol=1e-4) for c in 1:mp_data["conductors"]) + @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 2.192189; atol=1e-3) for c in 1:case30["conductors"]) + @test all(isapprox(result["solution"]["bus"]["2"]["va"][c], -0.071853; atol=1e-4) for c in 1:case30["conductors"]) end @testset "30-bus matpower acr opf" begin - mp_data = PMs.parse_file("../test/data/matpower/case30.m") - PMD.make_multiconductor!(mp_data, 3) - result = run_mc_opf(mp_data, PMs.ACRPowerModel, ipopt_solver) + result = run_mc_opf(case30, PMs.ACRPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 614.007; atol=1e-1) calc_va(id) = atan.(result["solution"]["bus"][id]["vi"], result["solution"]["bus"][id]["vr"]) - @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 2.192189; atol=1e-3) for c in 1:mp_data["conductors"]) - @test all(isapprox(calc_va("2")[c], -0.071853; atol=1e-4) for c in 1:mp_data["conductors"]) + @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 2.192189; atol=1e-3) for c in 1:case30["conductors"]) + @test all(isapprox(calc_va("2")[c], -0.071853; atol=1e-4) for c in 1:case30["conductors"]) end @testset "30-bus matpower dcp opf" begin - mp_data = PMs.parse_file("../test/data/matpower/case30.m") - PMD.make_multiconductor!(mp_data, 3) - result = run_mc_opf(mp_data, PMs.DCPPowerModel, ipopt_solver) + result = run_mc_opf(case30, PMs.DCPPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 566.112; atol=1e-1) end @testset "30-bus matpower nfa opf" begin - mp_data = PMs.parse_file("../test/data/matpower/case30.m") - PMD.make_multiconductor!(mp_data, 3) - result = run_mc_opf(mp_data, PMs.NFAPowerModel, ipopt_solver) + result = run_mc_opf(case30, PMs.NFAPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 458.006; atol=1e-1) @@ -72,9 +66,11 @@ end @testset "test dropped phases opf" begin + case4_phase_drop = PMD.parse_file("../test/data/opendss/case4_phase_drop.dss"; data_model="mathematical") + case5_phase_drop = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss"; data_model="mathematical") + @testset "4-bus phase drop acp opf" begin - mp_data = PMD.parse_file("../test/data/opendss/case4_phase_drop.dss") - result = run_mc_opf(mp_data, PMs.ACPPowerModel, ipopt_solver) + result = run_mc_opf(case4_phase_drop, PMs.ACPPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 0.0182595; atol=1e-4) @@ -84,8 +80,7 @@ end @testset "4-bus phase drop acr opf" begin - mp_data = PMD.parse_file("../test/data/opendss/case4_phase_drop.dss") - result = run_mc_opf(mp_data, PMs.ACRPowerModel, ipopt_solver) + result = run_mc_opf(case4_phase_drop, PMs.ACRPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 0.0182595; atol=1e-4) @@ -96,8 +91,8 @@ end @testset "5-bus phase drop acp opf" begin - mp_data = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss") - result = run_mc_opf(mp_data, PMs.ACPPowerModel, ipopt_solver) + + result = run_mc_opf(case5_phase_drop, PMs.ACPPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 0.0599389; atol=1e-4) @@ -107,8 +102,7 @@ end @testset "5-bus phase drop acr opf" begin - mp_data = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss") - result = run_mc_opf(mp_data, PMs.ACRPowerModel, ipopt_solver) + result = run_mc_opf(case5_phase_drop, PMs.ACRPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 0.0599400; atol = 1e-4) @@ -119,16 +113,14 @@ end @testset "5-bus phase drop dcp opf" begin - mp_data = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss") - result = run_mc_opf(mp_data, PMs.DCPPowerModel, ipopt_solver) + result = run_mc_opf(case5_phase_drop, PMs.DCPPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 0.0544220; atol=1e-4) end @testset "5-bus phase drop nfa opf" begin - mp_data = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss") - result = run_mc_opf(mp_data, PMs.NFAPowerModel, ipopt_solver) + result = run_mc_opf(case5_phase_drop, PMs.NFAPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED @test isapprox(result["objective"], 0.054; atol=1e-4) @@ -137,7 +129,7 @@ @testset "test opendss opf" begin @testset "2-bus diagonal acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case2_diag.dss") + pmd = PMD.parse_file("../test/data/opendss/case2_diag.dss"; data_model="mathematical") sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @@ -150,7 +142,7 @@ end @testset "3-bus balanced acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @@ -165,7 +157,7 @@ end @testset "3-bus unbalanced acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model="mathematical") sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @@ -182,7 +174,7 @@ end @testset "3-bus unbalanced isc acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced_isc.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_balanced_isc.dss"; data_model="mathematical") sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @@ -190,7 +182,7 @@ end @testset "3-bus balanced pv acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced_pv.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_balanced_pv.dss"; data_model="mathematical") @test length(pmd["gen"]) == 2 @test all(pmd["gen"]["1"]["qmin"] .== -pmd["gen"]["1"]["qmax"]) @@ -206,7 +198,7 @@ end @testset "3-bus unbalanced single-phase pv acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced_1phase-pv.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced_1phase-pv.dss"; data_model="mathematical") sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @@ -219,7 +211,7 @@ end @testset "3-bus balanced capacitor acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced_cap.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_balanced_cap.dss"; data_model="mathematical") sol = PMD.run_mc_pf(pmd, PMs.ACPPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @@ -229,7 +221,7 @@ end @testset "3w transformer nfa opf" begin - mp_data = PMD.parse_file("../test/data/opendss/ut_trans_3w_dyy_1.dss") + mp_data = PMD.parse_file("../test/data/opendss/ut_trans_3w_dyy_1.dss"; data_model="mathematical") result = run_mc_opf(mp_data, PMs.NFAPowerModel, ipopt_solver) @test result["termination_status"] == PMs.LOCALLY_SOLVED diff --git a/test/opf_bf.jl b/test/opf_bf.jl index ecdefb8cb..b00250726 100644 --- a/test/opf_bf.jl +++ b/test/opf_bf.jl @@ -16,7 +16,7 @@ end @testset "3-bus balanced lpubfdiag opf_bf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") sol = PMD.run_mc_opf_bf(pmd, LPUBFDiagPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @@ -25,7 +25,7 @@ end @testset "3-bus unbalanced lpubfdiag opf_bf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model="mathematical") sol = PMD.run_mc_opf_bf(pmd, LPUBFDiagPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED diff --git a/test/opf_iv.jl b/test/opf_iv.jl index c81cb9076..ce971cb67 100644 --- a/test/opf_iv.jl +++ b/test/opf_iv.jl @@ -3,7 +3,7 @@ @testset "test current-voltage formulations" begin @testset "test IVR opf_iv" begin @testset "2-bus diagonal acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case2_diag.dss") + pmd = PMD.parse_file("../test/data/opendss/case2_diag.dss"; data_model="mathematical") sol = PMD.run_mc_opf_iv(pmd, PMs.IVRPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @@ -14,7 +14,7 @@ end @testset "3-bus balanced acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") sol = PMD.run_mc_opf_iv(pmd, PMs.IVRPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED @@ -25,7 +25,7 @@ end @testset "3-bus unbalanced acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss") + pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model="mathematical") sol = PMD.run_mc_opf_iv(pmd, PMs.IVRPowerModel, ipopt_solver) @test sol["termination_status"] == PMs.LOCALLY_SOLVED diff --git a/test/pf.jl b/test/pf.jl index 52fb53e06..d11a98903 100644 --- a/test/pf.jl +++ b/test/pf.jl @@ -1,182 +1,195 @@ @info "running power flow (pf) tests" @testset "test pf" begin + case2_diag = PMD.parse_file("../test/data/opendss/case2_diag.dss"; data_model="mathematical") + case3_balanced = PMD.parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") + case3_unbalanced = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model="mathematical") + case5_phase_drop = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss"; data_model="mathematical") + case_mxshunt = PMD.parse_file("../test/data/opendss/case_mxshunt.dss"; data_model="mathematical") - @testset "test opendss pf" begin - @testset "2-bus diagonal acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case2_diag.dss") - sol = PMD.run_mc_pf(pmd, PMs.ACPPowerModel, ipopt_solver) + @testset "2-bus diagonal acp pf" begin + sol = PMD.run_mc_pf(case2_diag, PMs.ACPPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], 0.984377; atol=1e-4)) - @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) + @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], 0.984377; atol=1e-4)) + @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], [PMD._wrap_to_pi(2*pi/case2_diag["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:case2_diag["conductors"]]; atol=deg2rad(0.2))) - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) - end + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) + end - @testset "2-bus diagonal acr pf" begin - pmd = PMD.parse_file("../test/data/opendss/case2_diag.dss") - sol = PMD.run_mc_pf(pmd, PMs.ACRPowerModel, ipopt_solver) + @testset "2-bus diagonal acr pf" begin + sol = PMD.run_mc_pf(case2_diag, PMs.ACRPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == PMs.LOCALLY_SOLVED - calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) - calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) - @test all(isapprox.(calc_vm("1"), 0.984377; atol=1e-4)) - @test all(isapprox.(calc_va("1"), [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) + calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) + calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) + @test all(isapprox.(calc_vm("1"), 0.984377; atol=1e-4)) + @test all(isapprox.(calc_va("1"), [PMD._wrap_to_pi(2*pi/case2_diag["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:case2_diag["conductors"]]; atol=deg2rad(0.2))) + + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) + end + + @testset "3-bus balanced acp pf" begin + sol = PMD.run_mc_pf(case3_balanced, PMs.ACPPowerModel, ipopt_solver) + + @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) + @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [PMD._wrap_to_pi(2*pi/case3_balanced["conductors"]*(1-c) .+ va) for c in 1:case3_balanced["conductors"]]; atol=deg2rad(0.2))) + @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-3)) end - @testset "3-bus balanced acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") - sol = PMD.run_mc_pf(pmd, PMs.ACPPowerModel, ipopt_solver) + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=1e-5) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=1e-4) + end - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @testset "3-bus balanced acr pf" begin + sol = PMD.run_mc_pf(case3_balanced, PMs.ACRPowerModel, ipopt_solver) - for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) - @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) .+ va) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) - @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-3)) - end + @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=1e-4) + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) + calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) + calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) + @test all(isapprox.(calc_va(bus), [PMD._wrap_to_pi(2*pi/case3_balanced["conductors"]*(1-c) .+ va) for c in 1:case3_balanced["conductors"]]; atol=deg2rad(0.2))) + @test all(isapprox.(calc_vm(bus), vm; atol=1e-3)) end - @testset "3-bus balanced acr pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") - sol = PMD.run_mc_pf(pmd, PMs.ACRPowerModel, ipopt_solver) + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=1e-5) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=1e-4) + end - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @testset "3-bus balanced no linecode basefreq defined acp pf" begin + sol = PMD.run_mc_pf(case3_balanced, PMs.ACPPowerModel, ipopt_solver) - for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) - calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) - calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) - @test all(isapprox.(calc_va(bus), [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) .+ va) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) - @test all(isapprox.(calc_vm(bus), vm; atol=1e-3)) - end + pmd2 = PMD.parse_file("../test/data/opendss/case3_balanced_basefreq.dss"; data_model="mathematical") + sol2 = PMD.run_mc_pf(pmd2, PMs.ACPPowerModel, ipopt_solver) - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=1e-4) - end + @test all(all(isapprox.(bus["vm"], sol2["solution"]["bus"][i]["vm"]; atol=1e-8)) for (i, bus) in sol["solution"]["bus"]) + @test all(all(isapprox.(bus["va"], sol2["solution"]["bus"][i]["va"]; atol=1e-8)) for (i, bus) in sol["solution"]["bus"]) + @test all(isapprox(sum(sol["solution"]["gen"]["1"][field] * sol["solution"]["baseMVA"]), sum(sol2["solution"]["gen"]["1"][field] * sol2["solution"]["baseMVA"]); atol=1e-8) for field in ["pg", "qg"]) + end - @testset "3-bus balanced no linecode basefreq defined acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") - sol = PMD.run_mc_pf(pmd, PMs.ACPPowerModel, ipopt_solver) + @testset "3-bus balanced w/ switch acp pf" begin + pmd = PMD.parse_file("../test/data/opendss/case3_balanced_switch.dss"; data_model="mathematical") + sol = PMD.run_mc_pf(pmd, PMs.ACPPowerModel, ipopt_solver) - pmd2 = PMD.parse_file("../test/data/opendss/case3_balanced_basefreq.dss") - sol2 = PMD.run_mc_pf(pmd2, PMs.ACPPowerModel, ipopt_solver) + @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test all(all(isapprox.(bus["vm"], sol2["solution"]["bus"][i]["vm"]; atol=1e-8)) for (i, bus) in sol["solution"]["bus"]) - @test all(all(isapprox.(bus["va"], sol2["solution"]["bus"][i]["va"]; atol=1e-8)) for (i, bus) in sol["solution"]["bus"]) - @test all(isapprox(sum(sol["solution"]["gen"]["1"][field] * sol["solution"]["baseMVA"]), sum(sol2["solution"]["gen"]["1"][field] * sol2["solution"]["baseMVA"]); atol=1e-8) for field in ["pg", "qg"]) + for (bus, va, vm) in zip(["2", "1", "3"], [0.0, 0.0, deg2rad(-0.04)], [0.9959, 0.995729, 0.985454]) + @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) .+ va) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) + @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-3)) end + end - @testset "3-bus balanced w/ switch acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced_switch.dss") - sol = PMD.run_mc_pf(pmd, PMs.ACPPowerModel, ipopt_solver) + @testset "3-bus unbalanced acp pf" begin + sol = PMD.run_mc_pf(case3_unbalanced, PMs.ACPPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == PMs.LOCALLY_SOLVED - for (bus, va, vm) in zip(["2", "1", "3"], [0.0, 0.0, deg2rad(-0.04)], [0.9959, 0.995729, 0.985454]) - @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) .+ va) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) - @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-3)) - end + for (bus, va, vm) in zip(["2", "1", "3"], + [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], + [0.9959, [0.98094, 0.989365, 0.987043], [0.96355, 0.981767, 0.976786]]) + @test all(isapprox.(sol["solution"]["bus"][bus]["va"], PMD._wrap_to_pi([2*pi/case3_unbalanced["conductors"]*(1-c) for c in 1:case3_unbalanced["conductors"]] .+ va); atol=deg2rad(0.2))) + @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=2e-3)) end - @testset "3-bus unbalanced acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss") - sol = PMD.run_mc_pf(pmd, PMs.ACPPowerModel, ipopt_solver) + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214835; atol=1e-5) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00932693; atol=1e-4) + end - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @testset "3-bus unbalanced acr pf" begin + sol = PMD.run_mc_pf(case3_unbalanced, PMs.ACRPowerModel, ipopt_solver) - for (bus, va, vm) in zip(["2", "1", "3"], - [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], - [0.9959, [0.98094, 0.989365, 0.987043], [0.96355, 0.981767, 0.976786]]) - @test all(isapprox.(sol["solution"]["bus"][bus]["va"], PMD._wrap_to_pi([2*pi/pmd["conductors"]*(1-c) for c in 1:pmd["conductors"]] .+ va); atol=deg2rad(0.2))) - @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=2e-3)) - end + @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214835; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00932693; atol=1e-4) + for (bus, va, vm) in zip(["2", "1", "3"], + [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], + [0.9959, [0.98094, 0.989365, 0.987043], [0.96355, 0.981767, 0.976786]]) + calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) + calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) + @test all(isapprox.(calc_va(bus), PMD._wrap_to_pi([2*pi/case3_unbalanced["conductors"]*(1-c) for c in 1:case3_unbalanced["conductors"]] .+ va); atol=deg2rad(0.2))) + @test all(isapprox.(calc_vm(bus), vm; atol=2e-3)) end - @testset "3-bus unbalanced acr pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss") - sol = PMD.run_mc_pf(pmd, PMs.ACRPowerModel, ipopt_solver) + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214835; atol=1e-5) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00932693; atol=1e-4) + end + + @testset "3-bus unbalanced w/ assymetric linecode & phase order swap acp pf" begin + pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced_assym_swap.dss"; data_model="mathematical") + sol = PMD.run_ac_mc_pf(pmd, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == PMs.LOCALLY_SOLVED - for (bus, va, vm) in zip(["2", "1", "3"], - [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], - [0.9959, [0.98094, 0.989365, 0.987043], [0.96355, 0.981767, 0.976786]]) - calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) - calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) - @test all(isapprox.(calc_va(bus), PMD._wrap_to_pi([2*pi/pmd["conductors"]*(1-c) for c in 1:pmd["conductors"]] .+ va); atol=deg2rad(0.2))) - @test all(isapprox.(calc_vm(bus), vm; atol=2e-3)) - end + @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], [0.983453, 0.98718, 0.981602]; atol=1e-5)) + @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], deg2rad.([-0.07, -120.19, 120.29]); atol=1e-2)) + end - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214835; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00932693; atol=1e-4) - end + @testset "5-bus phase drop acp pf" begin + result = run_mc_pf(case5_phase_drop, PMs.ACPPowerModel, ipopt_solver) - @testset "3-bus unbalanced w/ assymetric linecode & phase order swap acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced_assym_swap.dss") - sol = PMD.run_ac_mc_pf(pmd, ipopt_solver) + @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test isapprox(result["objective"], 0.0; atol = 1e-4) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test all(isapprox.(result["solution"]["bus"]["3"]["vm"], [0.973519, 0.964902, 0.956465]; atol = 1e-3)) + end - @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], [0.983453, 0.98718, 0.981602]; atol=1e-5)) - @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], deg2rad.([-0.07, -120.19, 120.29]); atol=1e-2)) - end + @testset "5-bus phase drop acr pf" begin + result = run_mc_pf(case5_phase_drop, PMs.ACRPowerModel, ipopt_solver) - @testset "5-bus phase drop acp pf" begin - mp_data = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss") - result = run_mc_pf(mp_data, PMs.ACPPowerModel, ipopt_solver) + @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test isapprox(result["objective"], 0.0; atol = 1e-4) - @test result["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(result["objective"], 0.0; atol = 1e-4) + calc_vm(id) = sqrt.(result["solution"]["bus"][id]["vr"].^2+result["solution"]["bus"][id]["vi"].^2) + @test isapprox(calc_vm("3")[1], 0.973519; atol = 1e-4) + @test isapprox(calc_vm("3")[2], 0.964902; atol = 1e-4) + @test isapprox(calc_vm("3")[3], 0.956465; atol = 1e-4) + end - @test all(isapprox.(result["solution"]["bus"]["3"]["vm"], [0.973519, 0.964902, 0.956465]; atol = 1e-3)) - end + @testset "matrix branch shunts acp pf" begin + sol = PMD.run_ac_mc_pf(case_mxshunt, ipopt_solver) - @testset "5-bus phase drop acr pf" begin - mp_data = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss") - result = run_mc_pf(mp_data, PMs.ACRPowerModel, ipopt_solver) + @test all(isapprox.(sol["solution"]["bus"]["2"]["vm"], [0.987399, 0.981300, 1.003536]; atol=1E-6)) + end - @test result["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(result["objective"], 0.0; atol = 1e-4) + @testset "matrix branch shunts acr pf" begin + pm = PMs.instantiate_model(case_mxshunt, PMs.ACRPowerModel, PMD.build_mc_pf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) + sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) - calc_vm(id) = sqrt.(result["solution"]["bus"][id]["vr"].^2+result["solution"]["bus"][id]["vi"].^2) - @test isapprox(calc_vm("3")[1], 0.973519; atol = 1e-4) - @test isapprox(calc_vm("3")[2], 0.964902; atol = 1e-4) - @test isapprox(calc_vm("3")[3], 0.956465; atol = 1e-4) - end + calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) + @test all(isapprox.(calc_vm("2"), [0.987399, 0.981299, 1.003537]; atol=1E-6)) + end - @testset "matrix branch shunts acp pf" begin - sol = PMD.run_ac_mc_pf("../test/data/opendss/case_mxshunt.dss", ipopt_solver) + @testset "virtual sourcebus creation acp pf" begin + pmd = parse_file("../test/data/opendss/virtual_sourcebus.dss"; data_model="mathematical") + result = run_ac_mc_pf(pmd, ipopt_solver) + @test result["termination_status"] == PMs.LOCALLY_SOLVED - @test all(isapprox.(sol["solution"]["bus"]["2"]["vm"], [0.987399, 0.981300, 1.003536]; atol=1E-6)) - end + @test all(all(isapprox.(result["solution"]["bus"]["$n"]["vm"], [0.961352, 0.999418, 1.00113]; atol=1e-6)) for n in [1, 2]) + @test all(all(isapprox.(rad2deg.(result["solution"]["bus"]["$n"]["va"]), [-1.25, -120.06, 120.0]; atol=1e-1)) for n in [1, 2]) + end - @testset "matrix branch shunts acr pf" begin - data_pmd = PMD.parse_file("../test/data/opendss/case_mxshunt.dss") - pm = PMs.instantiate_model(data_pmd, PMs.ACRPowerModel, PMD.build_mc_pf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) - sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + @testset "2-bus diagonal ivr pf" begin + sol = PMD.run_mc_pf_iv(case2_diag, PMs.IVRPowerModel, ipopt_solver) - calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) - @test all(isapprox.(calc_vm("2"), [0.987399, 0.981299, 1.003537]; atol=1E-6)) - end + @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @testset "virtual sourcebus creation acp pf" begin - result = run_ac_mc_pf("../test/data/opendss/virtual_sourcebus.dss", ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) + end - @test all(all(isapprox.(result["solution"]["bus"]["$n"]["vm"], [0.961352, 0.999418, 1.00113]; atol=1e-6)) for n in [1, 2]) - @test all(all(isapprox.(rad2deg.(result["solution"]["bus"]["$n"]["va"]), [-1.25, -120.06, 120.0]; atol=1e-1)) for n in [1, 2]) - end + @testset "3-bus balanced ivr pf" begin + sol = PMD.run_mc_pf_iv(case3_balanced, PMs.IVRPowerModel, ipopt_solver) + + @test sol["termination_status"] == PMs.LOCALLY_SOLVED + + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=1e-5) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=1e-4) end + end diff --git a/test/pf_iv.jl b/test/pf_iv.jl deleted file mode 100644 index e6dbbd8df..000000000 --- a/test/pf_iv.jl +++ /dev/null @@ -1,25 +0,0 @@ -@info "running current-voltage power flow (pf_iv) tests" - -@testset "test pf" begin - @testset "test opendss pf" begin - @testset "2-bus diagonal ivr pf" begin - pmd = PMD.parse_file("../test/data/opendss/case2_diag.dss") - sol = PMD.run_mc_pf_iv(pmd, PMs.IVRPowerModel, ipopt_solver) - - @test sol["termination_status"] == PMs.LOCALLY_SOLVED - - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) - end - - @testset "3-bus balanced ivr pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss") - sol = PMD.run_mc_pf_iv(pmd, PMs.IVRPowerModel, ipopt_solver) - - @test sol["termination_status"] == PMs.LOCALLY_SOLVED - - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=1e-4) - end - end -end diff --git a/test/runtests.jl b/test/runtests.jl index d6b1903b7..1029b4a62 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,8 +41,6 @@ include("common.jl") include("pf.jl") # all passing - include("pf_iv.jl") # all passing - include("opf.jl") # all passing include("opf_bf.jl") # all passing diff --git a/test/shunt.jl b/test/shunt.jl index 3e8c3210a..0116d71c1 100644 --- a/test/shunt.jl +++ b/test/shunt.jl @@ -1,7 +1,7 @@ @info "running matrix shunt tests" @testset "matrix shunts ACP/ACR/IVR" begin - data = PMD.parse_file("data/opendss/case_mxshunt_2.dss") + data = PMD.parse_file("data/opendss/case_mxshunt_2.dss"; data_model="mathematical") shunt = data["shunt"]["1"] @test(isa(shunt["gs"], Matrix)) @test(isa(shunt["bs"], Matrix)) diff --git a/test/transformer.jl b/test/transformer.jl index db4130553..640cd1a79 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -5,7 +5,7 @@ @testset "test transformer acp pf" begin @testset "2w transformer acp pf yy" begin file = "../test/data/opendss/ut_trans_2w_yy.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 @@ -14,7 +14,7 @@ @testset "2w transformer acp pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 @@ -23,7 +23,7 @@ @testset "2w transformer acp pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 @@ -34,7 +34,7 @@ @testset "test transformer ivr pf" begin @testset "2w transformer ivr pf yy" begin file = "../test/data/opendss/ut_trans_2w_yy.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 @@ -43,7 +43,7 @@ @testset "2w transformer ivr pf dy_lead" begin file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 @@ -52,7 +52,7 @@ @testset "2w transformer ivr pf dy_lag" begin file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 @@ -77,7 +77,7 @@ @testset "three winding transformer pf" begin @testset "3w transformer ac pf dyy - all non-zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_1.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 @@ -86,7 +86,7 @@ @testset "3w transformer ac pf dyy - some non-zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_2.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) #@test isapprox(vm(sol, pmd_data, "3"), [0.93876, 0.90227, 0.90454], atol=1E-4) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @@ -96,7 +96,7 @@ @testset "3w transformer ac pf dyy - all zero" begin file = "../test/data/opendss/ut_trans_3w_dyy_3.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 @@ -105,7 +105,7 @@ @testset "3w transformer ac pf dyy - %loadloss=0" begin file = "../test/data/opendss/ut_trans_3w_dyy_3_loadloss.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 @@ -116,7 +116,7 @@ @testset "oltc tests" begin @testset "2w transformer acp opf_oltc yy" begin file = "../test/data/opendss/ut_trans_2w_yy_oltc.dss" - pmd_data = PMD.parse_file(file) + pmd_data = PMD.parse_file(file; data_model="mathematical") # free the taps pmd_data["transformer"]["1"]["fixed"] = zeros(Bool, 3) pmd_data["transformer"]["2"]["fixed"] = zeros(Bool, 3) From c1dbaf2fd233cc351be6171c7af1b07e26e0a3d8 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 13:57:21 -0600 Subject: [PATCH 152/224] FIX: PowerModels imports Removed SOCWR and added IVR to import/export --- src/PowerModelsDistribution.jl | 2 +- src/core/export.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index bc612f2e6..19d0f51e4 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -14,7 +14,7 @@ module PowerModelsDistribution const _PM = PowerModels const _IM = InfrastructureModels - import PowerModels: ACPPowerModel, ACRPowerModel, DCPPowerModel, NFAPowerModel, SOCWRPowerModel, conductor_ids, ismulticonductor + import PowerModels: ACPPowerModel, ACRPowerModel, DCPPowerModel, IVRPowerModel, NFAPowerModel, conductor_ids, ismulticonductor import InfrastructureModels: ids, ref, var, con, sol, nw_ids, nws, ismultinetwork function __init__() diff --git a/src/core/export.jl b/src/core/export.jl index 6b4b5a089..e2048872f 100644 --- a/src/core/export.jl +++ b/src/core/export.jl @@ -45,7 +45,7 @@ for status_code_enum in [TerminationStatusCode, ResultStatusCode] end # PowerModels Exports -export ACPPowerModel, ACRPowerModel, DCPPowerModel, NFAPowerModel, SOCWRPowerModel, conductor_ids, ismulticonductor +export ACPPowerModel, ACRPowerModel, DCPPowerModel, IVRPowerModel, NFAPowerModel, conductor_ids, ismulticonductor # InfrastructureModels Exports export ids, ref, var, con, sol, nw_ids, nws, ismultinetwork From 1601e412e817ba9bd965fe7248d64ada6d05ade4 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 13:57:57 -0600 Subject: [PATCH 153/224] ADD: make_si kwarg for run_mc_model adds a way to prevent solution from being converted into SI units --- src/prob/common.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prob/common.jl b/src/prob/common.jl index 34c891138..cd92a931e 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -1,11 +1,11 @@ "alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" -function run_mc_model(data::Dict{String,<:Any}, model_type, solver, build_mc; ref_extensions=[], kwargs...) +function run_mc_model(data::Dict{String,<:Any}, model_type, solver, build_mc; ref_extensions=[], make_si=!get(data, "per_unit", false), kwargs...) if get(data, "data_model", "mathematical") == "engineering" data_math = transform_data_model(data, make_pu=true) result = _PM.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) - result["solution"] = solution_math2eng(result["solution"], data_math; make_si=!get(data, "per_unit", false), make_deg=!get(data, "per_unit", false)) + result["solution"] = solution_math2eng(result["solution"], data_math; make_si=make_si, make_deg=!get(data, "per_unit", false)) else result = _PM.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) end From ac37419a16d9a7970d8328acd7b862040b6652cb Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 13:58:41 -0600 Subject: [PATCH 154/224] REF: converts pf tests to eng model --- test/common.jl | 14 ++-- test/pf.jl | 172 +++++++++++++++++++++++-------------------------- 2 files changed, 90 insertions(+), 96 deletions(-) diff --git a/test/common.jl b/test/common.jl index a9cd8d2fd..2d6fcfb9b 100644 --- a/test/common.jl +++ b/test/common.jl @@ -22,6 +22,12 @@ vr(sol, pmd_data, name) = sol["solution"]["bus"][string(bus_name2id(pmd_data, na calc_vm_acr(sol, pmd_data, name) = sqrt.(vi(sol, pmd_data, name).^2 .+ vr(sol, pmd_data, name).^2) calc_va_acr(sol, pmd_data, name) = rad2deg.(PMD._wrap_to_pi(atan.(vi(sol, pmd_data, name), vr(sol, pmd_data, name)))) +# Helper functions adjusted for eng model +vi(sol, name) = sol["solution"]["bus"][name]["vi"] +vr(sol, name) = sol["solution"]["bus"][name]["vr"] +calc_vm_acr(sol, name) = sqrt.(vi(sol, name).^2 .+ vr(sol, name).^2) +calc_va_acr(sol, name) = rad2deg.(PMD._wrap_to_pi(atan.(vi(sol, name), vr(sol, name)))) + # Helper functions for load models tests load_name2id(pmd_data, name) = [load["index"] for (_,load) in pmd_data["load"] if haskey(load, "name") && load["name"]==name][1] pdvar(pm, pmd_data, name) = [PMs.var(pm, pm.cnw, c, :pd, load_name2id(pmd_data, name)) for c in 1:3] @@ -35,14 +41,14 @@ calc_vm_W(result, id) = sqrt.(diag( result["solution"]["bus"][id]["Wr"])) function build_mc_data!(base_data; conductors::Int=3) mp_data = PowerModels.parse_file(base_data) - PMD.make_multiconductor!(mp_data, conductors) + make_multiconductor!(mp_data, conductors) return mp_data end function build_mn_mc_data!(base_data; replicates::Int=3, conductors::Int=3) mp_data = PowerModels.parse_file(base_data) - PMD.make_multiconductor!(mp_data, conductors) + make_multiconductor!(mp_data, conductors) mn_mc_data = PowerModels.replicate(mp_data, replicates) mn_mc_data["conductors"] = mn_mc_data["nw"]["1"]["conductors"] return mn_mc_data @@ -56,11 +62,11 @@ function build_mn_mc_data!(base_data_1, base_data_2; conductors_1::Int=3, conduc @assert mp_data_1["per_unit"] == mp_data_2["per_unit"] if conductors_1 > 0 - PMD.make_multiconductor!(mp_data_1, conductors_1) + make_multiconductor!(mp_data_1, conductors_1) end if conductors_2 > 0 - PMD.make_multiconductor!(mp_data_2, conductors_2) + make_multiconductor!(mp_data_2, conductors_2) end mn_data = Dict( diff --git a/test/pf.jl b/test/pf.jl index d11a98903..026b41c04 100644 --- a/test/pf.jl +++ b/test/pf.jl @@ -1,195 +1,183 @@ @info "running power flow (pf) tests" @testset "test pf" begin - case2_diag = PMD.parse_file("../test/data/opendss/case2_diag.dss"; data_model="mathematical") - case3_balanced = PMD.parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") - case3_unbalanced = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model="mathematical") - case5_phase_drop = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss"; data_model="mathematical") - case_mxshunt = PMD.parse_file("../test/data/opendss/case_mxshunt.dss"; data_model="mathematical") + case2_diag = parse_file("../test/data/opendss/case2_diag.dss") + case3_balanced = parse_file("../test/data/opendss/case3_balanced.dss") + case3_unbalanced = parse_file("../test/data/opendss/case3_unbalanced.dss") + case5_phase_drop = parse_file("../test/data/opendss/case5_phase_drop.dss") + case_mxshunt = parse_file("../test/data/opendss/case_mxshunt.dss") @testset "2-bus diagonal acp pf" begin - sol = PMD.run_mc_pf(case2_diag, PMs.ACPPowerModel, ipopt_solver) + sol = run_mc_pf(case2_diag, ACPPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], 0.984377; atol=1e-4)) - @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], [PMD._wrap_to_pi(2*pi/case2_diag["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:case2_diag["conductors"]]; atol=deg2rad(0.2))) + @test all(isapprox.(sol["solution"]["bus"]["primary"]["vm"], 0.227339; atol=1e-4)) + @test all(isapprox.(sol["solution"]["bus"]["primary"]["va"], [-0.657496, -120.657, 119.343]; atol=0.2)) - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"]), 18.20887; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"]), 0.20887; atol=1e-5) end @testset "2-bus diagonal acr pf" begin - sol = PMD.run_mc_pf(case2_diag, PMs.ACRPowerModel, ipopt_solver) + sol = run_mc_pf(case2_diag, ACRPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) - calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) - @test all(isapprox.(calc_vm("1"), 0.984377; atol=1e-4)) - @test all(isapprox.(calc_va("1"), [PMD._wrap_to_pi(2*pi/case2_diag["conductors"]*(1-c) - deg2rad(0.79)) for c in 1:case2_diag["conductors"]]; atol=deg2rad(0.2))) + @test all(isapprox.(calc_vm_acr(sol, "primary"), 0.227339; atol=1e-4)) + @test all(isapprox.(calc_va_acr(sol, "primary"), [-0.657496, -120.657, 119.343]; atol=0.2)) - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"]), 18.20888; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"]), 0.20888; atol=1e-5) end @testset "3-bus balanced acp pf" begin - sol = PMD.run_mc_pf(case3_balanced, PMs.ACPPowerModel, ipopt_solver) + sol = run_mc_pf(case3_balanced, ACPPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) - @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [PMD._wrap_to_pi(2*pi/case3_balanced["conductors"]*(1-c) .+ va) for c in 1:case3_balanced["conductors"]]; atol=deg2rad(0.2))) + for (bus, va, vm) in zip(["sourcebus", "primary", "loadbus"], [0.0, -0.08, -0.17], [0.229993, 0.227932, 0.225537]) + @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [0, -120, 120] .+ va; atol=0.2)) @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-3)) end - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=1e-4) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"]), 18.34478; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"]), 9.19392; atol=1e-4) end @testset "3-bus balanced acr pf" begin - sol = PMD.run_mc_pf(case3_balanced, PMs.ACRPowerModel, ipopt_solver) + sol = run_mc_pf(case3_balanced, ACRPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.08), deg2rad(-0.17)], [0.9959, 0.986976, 0.976611]) - calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) - calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) - @test all(isapprox.(calc_va(bus), [PMD._wrap_to_pi(2*pi/case3_balanced["conductors"]*(1-c) .+ va) for c in 1:case3_balanced["conductors"]]; atol=deg2rad(0.2))) - @test all(isapprox.(calc_vm(bus), vm; atol=1e-3)) + for (bus, va, vm) in zip(["sourcebus", "primary", "loadbus"], [0.0, -0.08, -0.17], [0.229993, 0.227932, 0.225537]) + @test all(isapprox.(calc_va_acr(sol, bus), [0, -120, 120] .+ va; atol=0.2)) + @test all(isapprox.(calc_vm_acr(sol, bus), vm; atol=1e-3)) end - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=1e-4) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"]), 18.34478; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"]), 9.19392; atol=1e-4) end @testset "3-bus balanced no linecode basefreq defined acp pf" begin - sol = PMD.run_mc_pf(case3_balanced, PMs.ACPPowerModel, ipopt_solver) + sol = run_mc_pf(case3_balanced, ACPPowerModel, ipopt_solver) - pmd2 = PMD.parse_file("../test/data/opendss/case3_balanced_basefreq.dss"; data_model="mathematical") - sol2 = PMD.run_mc_pf(pmd2, PMs.ACPPowerModel, ipopt_solver) + pmd2 = parse_file("../test/data/opendss/case3_balanced_basefreq.dss") + sol2 = run_mc_pf(pmd2, ACPPowerModel, ipopt_solver) @test all(all(isapprox.(bus["vm"], sol2["solution"]["bus"][i]["vm"]; atol=1e-8)) for (i, bus) in sol["solution"]["bus"]) @test all(all(isapprox.(bus["va"], sol2["solution"]["bus"][i]["va"]; atol=1e-8)) for (i, bus) in sol["solution"]["bus"]) - @test all(isapprox(sum(sol["solution"]["gen"]["1"][field] * sol["solution"]["baseMVA"]), sum(sol2["solution"]["gen"]["1"][field] * sol2["solution"]["baseMVA"]); atol=1e-8) for field in ["pg", "qg"]) + @test all(isapprox(sum(sol["solution"]["voltage_source"]["source"][field]), sum(sol2["solution"]["voltage_source"]["source"][field]); atol=1e-8) for field in ["pg", "qg"]) end @testset "3-bus balanced w/ switch acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced_switch.dss"; data_model="mathematical") - sol = PMD.run_mc_pf(pmd, PMs.ACPPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_balanced_switch.dss") + sol = run_mc_pf(pmd, ACPPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - for (bus, va, vm) in zip(["2", "1", "3"], [0.0, 0.0, deg2rad(-0.04)], [0.9959, 0.995729, 0.985454]) - @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [PMD._wrap_to_pi(2*pi/pmd["conductors"]*(1-c) .+ va) for c in 1:pmd["conductors"]]; atol=deg2rad(0.2))) + for (bus, va, vm) in zip(["sourcebus", "primary", "loadbus"], [0.0, 0.0, -0.04], [0.9959, 0.995729, 0.985454]) + @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [0, -120, 120] .+ va; atol=0.2)) @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-3)) end end @testset "3-bus unbalanced acp pf" begin - sol = PMD.run_mc_pf(case3_unbalanced, PMs.ACPPowerModel, ipopt_solver) + sol = run_mc_pf(case3_unbalanced, ACPPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - for (bus, va, vm) in zip(["2", "1", "3"], - [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], + for (bus, va, vm) in zip(["sourcebus", "primary", "loadbus"], + [0.0, [-0.22, -0.11, 0.12], [-0.48, -0.24, 0.27]], [0.9959, [0.98094, 0.989365, 0.987043], [0.96355, 0.981767, 0.976786]]) - @test all(isapprox.(sol["solution"]["bus"][bus]["va"], PMD._wrap_to_pi([2*pi/case3_unbalanced["conductors"]*(1-c) for c in 1:case3_unbalanced["conductors"]] .+ va); atol=deg2rad(0.2))) + @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [0, -120, 120] .+ va; atol=0.2)) @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=2e-3)) end - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214835; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00932693; atol=1e-4) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"]), 0.04296; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"]), 0.01854; atol=1e-4) end @testset "3-bus unbalanced acr pf" begin - sol = PMD.run_mc_pf(case3_unbalanced, PMs.ACRPowerModel, ipopt_solver) + sol = run_mc_pf(case3_unbalanced, ACRPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - for (bus, va, vm) in zip(["2", "1", "3"], - [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], + for (bus, va, vm) in zip(["sourcebus", "primary", "loadbus"], + [0.0, [-0.22, -0.11, 0.12], [-0.48, -0.24, 0.27]], [0.9959, [0.98094, 0.989365, 0.987043], [0.96355, 0.981767, 0.976786]]) - calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) - calc_va(id) = atan.(sol["solution"]["bus"][id]["vi"], sol["solution"]["bus"][id]["vr"]) - @test all(isapprox.(calc_va(bus), PMD._wrap_to_pi([2*pi/case3_unbalanced["conductors"]*(1-c) for c in 1:case3_unbalanced["conductors"]] .+ va); atol=deg2rad(0.2))) - @test all(isapprox.(calc_vm(bus), vm; atol=2e-3)) + @test all(isapprox.(calc_va_acr(sol, bus), [0, -120, 120] .+ va; atol=0.2)) + @test all(isapprox.(calc_vm_acr(sol, bus), vm; atol=2e-3)) end - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214835; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00932693; atol=1e-4) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"]), 0.04296; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"]), 0.01854; atol=1e-4) end @testset "3-bus unbalanced w/ assymetric linecode & phase order swap acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced_assym_swap.dss"; data_model="mathematical") - sol = PMD.run_ac_mc_pf(pmd, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_unbalanced_assym_swap.dss") + sol = run_ac_mc_pf(pmd, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], [0.983453, 0.98718, 0.981602]; atol=1e-5)) - @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], deg2rad.([-0.07, -120.19, 120.29]); atol=1e-2)) + @test all(isapprox.(sol["solution"]["bus"]["primary"]["vm"], [0.983453, 0.98718, 0.981602]; atol=1e-5)) + @test all(isapprox.(sol["solution"]["bus"]["primary"]["va"], [-0.07, -120.19, 120.29]; atol=1e-2)) end @testset "5-bus phase drop acp pf" begin - result = run_mc_pf(case5_phase_drop, PMs.ACPPowerModel, ipopt_solver) + result = run_mc_pf(case5_phase_drop, ACPPowerModel, ipopt_solver; make_si=false) - @test result["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(result["objective"], 0.0; atol = 1e-4) + @test result["termination_status"] == LOCALLY_SOLVED - @test all(isapprox.(result["solution"]["bus"]["3"]["vm"], [0.973519, 0.964902, 0.956465]; atol = 1e-3)) + @test all(isapprox.(result["solution"]["bus"]["midbus"]["vm"], [0.973519, 0.964902, 0.956465]; atol = 1e-3)) end @testset "5-bus phase drop acr pf" begin - result = run_mc_pf(case5_phase_drop, PMs.ACRPowerModel, ipopt_solver) + sol = run_mc_pf(case5_phase_drop, ACRPowerModel, ipopt_solver; make_si=false) - @test result["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(result["objective"], 0.0; atol = 1e-4) + @test sol["termination_status"] == LOCALLY_SOLVED - calc_vm(id) = sqrt.(result["solution"]["bus"][id]["vr"].^2+result["solution"]["bus"][id]["vi"].^2) - @test isapprox(calc_vm("3")[1], 0.973519; atol = 1e-4) - @test isapprox(calc_vm("3")[2], 0.964902; atol = 1e-4) - @test isapprox(calc_vm("3")[3], 0.956465; atol = 1e-4) + @test all(isapprox.(calc_vm_acr(sol, "midbus"), [0.973519, 0.964902, 0.956465]; atol=1e-4)) end @testset "matrix branch shunts acp pf" begin - sol = PMD.run_ac_mc_pf(case_mxshunt, ipopt_solver) + sol = run_ac_mc_pf(case_mxshunt, ipopt_solver; make_si=false) - @test all(isapprox.(sol["solution"]["bus"]["2"]["vm"], [0.987399, 0.981300, 1.003536]; atol=1E-6)) + @test all(isapprox.(sol["solution"]["bus"]["loadbus"]["vm"], [0.987399, 0.981300, 1.003536]; atol=1E-6)) end @testset "matrix branch shunts acr pf" begin - pm = PMs.instantiate_model(case_mxshunt, PMs.ACRPowerModel, PMD.build_mc_pf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) - sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + sol = run_mc_pf(case_mxshunt, ACRPowerModel, ipopt_solver; make_si=false) - calc_vm(id) = sqrt.(sol["solution"]["bus"][id]["vr"].^2+sol["solution"]["bus"][id]["vi"].^2) - @test all(isapprox.(calc_vm("2"), [0.987399, 0.981299, 1.003537]; atol=1E-6)) + @test all(isapprox.(calc_vm_acr(sol, "loadbus"), [0.987399, 0.981299, 1.003537]; atol=1E-6)) end @testset "virtual sourcebus creation acp pf" begin pmd = parse_file("../test/data/opendss/virtual_sourcebus.dss"; data_model="mathematical") result = run_ac_mc_pf(pmd, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + + @test result["termination_status"] == LOCALLY_SOLVED @test all(all(isapprox.(result["solution"]["bus"]["$n"]["vm"], [0.961352, 0.999418, 1.00113]; atol=1e-6)) for n in [1, 2]) - @test all(all(isapprox.(rad2deg.(result["solution"]["bus"]["$n"]["va"]), [-1.25, -120.06, 120.0]; atol=1e-1)) for n in [1, 2]) + @test all(all(isapprox.(result["solution"]["bus"]["$n"]["va"], deg2rad.([-1.25, -120.06, 120.0]); atol=1e-1)) for n in [1, 2]) end @testset "2-bus diagonal ivr pf" begin - sol = PMD.run_mc_pf_iv(case2_diag, PMs.IVRPowerModel, ipopt_solver) + sol = run_mc_pf_iv(case2_diag, IVRPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"]), 18.20896; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"]), 0.20896; atol=1e-5) end @testset "3-bus balanced ivr pf" begin - sol = PMD.run_mc_pf_iv(case3_balanced, PMs.IVRPowerModel, ipopt_solver) + sol = run_mc_pf_iv(case3_balanced, IVRPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=1e-4) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"]), 18.34498; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"]), 9.19404; atol=1e-4) end end From 3e31ec8fc834e98462146f543c1088f5a7041af7 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 14:11:07 -0600 Subject: [PATCH 155/224] FIX: count_nodes for eng model --- src/core/data.jl | 6 +++++- test/data.jl | 10 ++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/core/data.jl b/src/core/data.jl index 0268bdf37..d7681d0b1 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -106,7 +106,11 @@ function count_nodes(data::Dict{String,<:Any})::Int end if all(!occursin(pattern, name) for pattern in [_excluded_count_busname_patterns...]) - n_nodes += sum(bus["vmax"] .> 0.0) + if data["data_model"] == "mathematical" + n_nodes += length(bus["terminals"][.!get(bus, "grounded", zeros(length(bus["terminals"])))]) + else + n_nodes += length([n for n in bus["terminals"] if !(n in get(bus, "grounded", []))]) + end end end end diff --git a/test/data.jl b/test/data.jl index 86c10677c..eb7e505de 100644 --- a/test/data.jl +++ b/test/data.jl @@ -70,13 +70,15 @@ end end @testset "node counting functions" begin - dss = PMD.parse_dss("../test/data/opendss/case5_phase_drop.dss") - math = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss"; data_model="mathematical") + dss = parse_dss("../test/data/opendss/case5_phase_drop.dss") + eng = parse_file("../test/data/opendss/case5_phase_drop.dss") + math = parse_file("../test/data/opendss/case5_phase_drop.dss"; data_model="mathematical") @test count_nodes(dss) == 10 # stopped excluding source from node count - @test count_nodes(dss) == count_nodes(math) + @test count_nodes(dss) == count_nodes(eng) + @test count_nodes(eng) == count_nodes(math) - dss = PMD.parse_dss("../test/data/opendss/ut_trans_2w_yy.dss") + dss = parse_dss("../test/data/opendss/ut_trans_2w_yy.dss") @test count_nodes(dss) == 12 # stopped excluding source from node count end end From 9797025a84163a86309d9b88b2359ade04837861 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 14:51:49 -0600 Subject: [PATCH 156/224] REF: convert opf tests to eng data model Fix bug in solar solution building for eng model --- src/data_model/eng2math.jl | 2 +- src/data_model/math2eng.jl | 18 +++- test/opf.jl | 176 ++++++++++++++++++------------------- 3 files changed, 102 insertions(+), 94 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 781adc311..671309a13 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -893,7 +893,7 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An data_math["gen"]["$(math_obj["index"])"] = math_obj data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => "solar.$name", + :from => name, :to => "gen.$(math_obj["index"])", :unmap_function => :_map_math2eng_solar!, ) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index b94e2f168..fa7670bf3 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -94,8 +94,6 @@ function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{ eng_obj = _init_unmap_eng_obj!(data_eng, "generator", map) math_obj = _get_math_obj(data_math, map[:to]) - @warn "gen" math_obj eng_obj - merge!(eng_obj, math_obj) if !isempty(eng_obj) @@ -105,10 +103,26 @@ end function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "solar", map) + math_obj = _get_math_obj(data_math, map[:to]) + + merge!(eng_obj, math_obj) + + if !isempty(eng_obj) + data_eng["solar"][map[:from]] = eng_obj + end end function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "storage", map) + math_obj = _get_math_obj(data_math, map[:to]) + + merge!(eng_obj, math_obj) + + if !isempty(eng_obj) + data_eng["storage"][map[:from]] = eng_obj + end end diff --git a/test/opf.jl b/test/opf.jl index 30d43729b..d3bfb43ef 100644 --- a/test/opf.jl +++ b/test/opf.jl @@ -2,16 +2,16 @@ @testset "test opf" begin @testset "test matpower opf" begin - case5 = PMs.parse_file("../test/data/matpower/case5.m") - case30 = PMs.parse_file("../test/data/matpower/case30.m") + case5 = PM.parse_file("../test/data/matpower/case5.m") + case30 = PM.parse_file("../test/data/matpower/case30.m") - PMD.make_multiconductor!(case5, 3) - PMD.make_multiconductor!(case30, 3) + make_multiconductor!(case5, 3) + make_multiconductor!(case30, 3) @testset "5-bus matpower acp opf" begin - result = run_mc_opf(case5, PMs.ACPPowerModel, ipopt_solver) + result = run_mc_opf(case5, ACPPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 45522.096; atol=1e-1) @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 0.3999999; atol=1e-3) for c in 1:case5["conductors"]) @@ -19,9 +19,9 @@ end @testset "5-bus matpower acr opf" begin - result = run_mc_opf(case5, PMs.ACRPowerModel, ipopt_solver) + result = run_mc_opf(case5, ACRPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 45522.096; atol=1e-1) calc_va(id) = atan.(result["solution"]["bus"][id]["vi"], result["solution"]["bus"][id]["vr"]) @@ -30,9 +30,9 @@ end @testset "30-bus matpower acp opf" begin - result = run_mc_opf(case30, PMs.ACPPowerModel, ipopt_solver) + result = run_mc_opf(case30, ACPPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 614.007; atol=1e-1) @test all(isapprox(result["solution"]["gen"]["1"]["pg"][c], 2.192189; atol=1e-3) for c in 1:case30["conductors"]) @@ -40,9 +40,9 @@ end @testset "30-bus matpower acr opf" begin - result = run_mc_opf(case30, PMs.ACRPowerModel, ipopt_solver) + result = run_mc_opf(case30, ACRPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 614.007; atol=1e-1) calc_va(id) = atan.(result["solution"]["bus"][id]["vi"], result["solution"]["bus"][id]["vr"]) @@ -51,180 +51,174 @@ end @testset "30-bus matpower dcp opf" begin - result = run_mc_opf(case30, PMs.DCPPowerModel, ipopt_solver) + result = run_mc_opf(case30, DCPPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 566.112; atol=1e-1) end @testset "30-bus matpower nfa opf" begin - result = run_mc_opf(case30, PMs.NFAPowerModel, ipopt_solver) + result = run_mc_opf(case30, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 458.006; atol=1e-1) end end @testset "test dropped phases opf" begin - case4_phase_drop = PMD.parse_file("../test/data/opendss/case4_phase_drop.dss"; data_model="mathematical") - case5_phase_drop = PMD.parse_file("../test/data/opendss/case5_phase_drop.dss"; data_model="mathematical") + case4_phase_drop = parse_file("../test/data/opendss/case4_phase_drop.dss") + case5_phase_drop = parse_file("../test/data/opendss/case5_phase_drop.dss") @testset "4-bus phase drop acp opf" begin - result = run_mc_opf(case4_phase_drop, PMs.ACPPowerModel, ipopt_solver) + result = run_mc_opf(case4_phase_drop, ACPPowerModel, ipopt_solver, make_si=false) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.0182595; atol=1e-4) - @test all(isapprox.(result["solution"]["gen"]["1"]["pg"], [5.06513e-5, 6.0865e-5, 7.1119e-5]; atol=1e-7)) - @test isapprox(result["solution"]["bus"]["4"]["vm"][1], 0.98995; atol=1.5e-4) + @test all(isapprox.(result["solution"]["voltage_source"]["source"]["pg"], [5.06513e-5, 6.0865e-5, 7.1119e-5]; atol=1e-7)) + @test isapprox(result["solution"]["bus"]["loadbus1"]["vm"][1], 0.98995; atol=1.5e-4) end @testset "4-bus phase drop acr opf" begin - result = run_mc_opf(case4_phase_drop, PMs.ACRPowerModel, ipopt_solver) + result = run_mc_opf(case4_phase_drop, ACRPowerModel, ipopt_solver; make_si=false) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.0182595; atol=1e-4) - calc_vm(id) = sqrt.(result["solution"]["bus"][id]["vr"].^2+result["solution"]["bus"][id]["vi"].^2) - @test all(isapprox.(result["solution"]["gen"]["1"]["pg"], [5.06513e-5, 6.0865e-5, 7.1119e-5]; atol=1e-7)) - @test isapprox(calc_vm("4")[1], 0.98995; atol=1.5e-4) + @test all(isapprox.(result["solution"]["voltage_source"]["source"]["pg"], [5.06513e-5, 6.0865e-5, 7.1119e-5]; atol=1e-7)) + @test isapprox(calc_vm_acr(result, "loadbus1")[1], 0.98995; atol=1.5e-4) end @testset "5-bus phase drop acp opf" begin - result = run_mc_opf(case5_phase_drop, PMs.ACPPowerModel, ipopt_solver) + result = run_mc_opf(case5_phase_drop, ACPPowerModel, ipopt_solver; make_si=false) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.0599389; atol=1e-4) - @test all(isapprox.(result["solution"]["gen"]["1"]["pg"], [0.00015236280779412599, 0.00019836795302238667, 0.0002486642034746673]; atol=1e-7)) - @test all(isapprox.(result["solution"]["bus"]["3"]["vm"], [0.97351, 0.96490, 0.95646]; atol=1e-4)) + @test all(isapprox.(result["solution"]["voltage_source"]["source"]["pg"], [0.00015236280779412599, 0.00019836795302238667, 0.0002486642034746673]; atol=1e-7)) + @test all(isapprox.(result["solution"]["bus"]["midbus"]["vm"], [0.97351, 0.96490, 0.95646]; atol=1e-4)) end @testset "5-bus phase drop acr opf" begin - result = run_mc_opf(case5_phase_drop, PMs.ACRPowerModel, ipopt_solver) + result = run_mc_opf(case5_phase_drop, ACRPowerModel, ipopt_solver; make_si=false) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.0599400; atol = 1e-4) - calc_vm(id) = sqrt.(result["solution"]["bus"][id]["vr"].^2+result["solution"]["bus"][id]["vi"].^2) - @test all(isapprox.(result["solution"]["gen"]["1"]["pg"], [0.00015236280779412599, 0.00019836795302238667, 0.0002486688793741932]; atol=1e-7)) - @test all(isapprox.(calc_vm("3"), [0.9735188343958152, 0.9649003198689144, 0.9564593296045091]; atol=1e-4)) + @test all(isapprox.(result["solution"]["voltage_source"]["source"]["pg"], [0.00015236280779412599, 0.00019836795302238667, 0.0002486688793741932]; atol=1e-7)) + @test all(isapprox.(calc_vm_acr(result, "midbus"), [0.9735188343958152, 0.9649003198689144, 0.9564593296045091]; atol=1e-4)) end @testset "5-bus phase drop dcp opf" begin - result = run_mc_opf(case5_phase_drop, PMs.DCPPowerModel, ipopt_solver) + result = run_mc_opf(case5_phase_drop, DCPPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.0544220; atol=1e-4) end @testset "5-bus phase drop nfa opf" begin - result = run_mc_opf(case5_phase_drop, PMs.NFAPowerModel, ipopt_solver) + result = run_mc_opf(case5_phase_drop, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.054; atol=1e-4) end end @testset "test opendss opf" begin @testset "2-bus diagonal acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case2_diag.dss"; data_model="mathematical") - sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case2_diag.dss") + sol = run_mc_opf(pmd, ACPPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - @test all(isapprox.(sol["solution"]["bus"]["1"]["vm"], 0.984377; atol=1e-4)) - @test all(isapprox.(sol["solution"]["bus"]["1"]["va"], PMD._wrap_to_pi.([2 * pi / pmd["conductors"] * (1 - c) - deg2rad(0.79) for c in 1:pmd["conductors"]]); atol=deg2rad(0.2))) + @test all(isapprox.(sol["solution"]["bus"]["primary"]["vm"], 0.984377; atol=1e-4)) + @test all(isapprox.(sol["solution"]["bus"]["primary"]["va"], [0, -120, 120] .- 0.79; atol=0.2)) - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0181409; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.0; atol=1e-4) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.0181409; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.0; atol=1e-4) end @testset "3-bus balanced acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") - sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_balanced.dss") + sol = run_mc_opf(pmd, ACPPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - for (bus, va, vm) in zip(["2", "1", "3"], [0.0, deg2rad(-0.03), deg2rad(-0.07)], [0.9959, 0.986973, 0.976605]) - @test all(isapprox.(sol["solution"]["bus"][bus]["va"], PMD._wrap_to_pi.([2 * pi / pmd["conductors"] * (1 - c) + va for c in 1:pmd["conductors"]]); atol=deg2rad(0.01))) + for (bus, va, vm) in zip(["sourcebus", "primary", "loadbus"], [0.0, -0.03, -0.07], [0.9959, 0.986973, 0.976605]) + @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [0, -120, 120] .+ va; atol=0.01)) @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-4)) end - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018276; atol=1e-6) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.008922; atol=1.2e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.018276; atol=1e-6) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.008922; atol=1.2e-5) end @testset "3-bus unbalanced acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model="mathematical") - sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_unbalanced.dss") + sol = run_mc_opf(pmd, ACPPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - for (bus, va, vm) in zip(["2", "1", "3"], - [0.0, deg2rad.([-0.22, -0.11, 0.12]), deg2rad.([-0.48, -0.24, 0.27])], + for (bus, va, vm) in zip(["sourcebus", "primary", "loadbus"], + [0.0, [-0.22, -0.11, 0.12], [-0.48, -0.24, 0.27]], [0.9959, [0.980937, 0.98936, 0.987039], [0.963546, 0.981757, 0.976779]]) - @test all(isapprox.(sol["solution"]["bus"][bus]["va"], PMD._wrap_to_pi.([2 * pi / pmd["conductors"] * (1 - c) for c in 1:pmd["conductors"]]) .+ va; atol=deg2rad(0.01))) + @test all(isapprox.(sol["solution"]["bus"][bus]["va"], [0, -120, 120] .+ va; atol=0.01)) @test all(isapprox.(sol["solution"]["bus"][bus]["vm"], vm; atol=1e-5)) end - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214812; atol=1e-6) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00927263; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.0214812; atol=1e-6) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.00927263; atol=1e-5) end @testset "3-bus unbalanced isc acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced_isc.dss"; data_model="mathematical") - sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_balanced_isc.dss") + sol = run_mc_opf(pmd, ACPPowerModel, ipopt_solver) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED @test isapprox(sol["objective"], 0.0185; atol=1e-4) end @testset "3-bus balanced pv acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced_pv.dss"; data_model="mathematical") + pmd = parse_file("../test/data/opendss/case3_balanced_pv.dss") - @test length(pmd["gen"]) == 2 - @test all(pmd["gen"]["1"]["qmin"] .== -pmd["gen"]["1"]["qmax"]) - @test all(pmd["gen"]["1"]["pmin"] .== 0.0) + sol = run_mc_opf(pmd, ACPPowerModel, ipopt_solver; make_si=false) - sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) - - @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test sum(sol["solution"]["gen"]["2"]["pg"] * sol["solution"]["baseMVA"]) < 0.0 - @test sum(sol["solution"]["gen"]["2"]["qg"] * sol["solution"]["baseMVA"]) < 0.005 - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183685; atol=1e-4) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.0048248; atol=1e-4) + @test sol["termination_status"] == LOCALLY_SOLVED + @test sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]) < 0.0 + @test sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]) < 0.005 + @test isapprox(sum(sol["solution"]["solar"]["pv1"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.0183685; atol=1e-4) + @test isapprox(sum(sol["solution"]["solar"]["pv1"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.0048248; atol=1e-4) end @testset "3-bus unbalanced single-phase pv acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced_1phase-pv.dss"; data_model="mathematical") - sol = PMD.run_mc_opf(pmd, PMs.ACPPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_unbalanced_1phase-pv.dss") + sol = run_mc_opf(pmd, ACPPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - @test isapprox(sum(sol["solution"]["gen"]["2"]["pg"] * sol["solution"]["baseMVA"]), 0.01838728; atol=1e-3) - @test isapprox(sum(sol["solution"]["gen"]["2"]["qg"] * sol["solution"]["baseMVA"]), 0.00756634; atol=1e-3) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.01838728; atol=1e-3) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.00756634; atol=1e-3) - @test all(sol["solution"]["gen"]["1"]["pg"][2:3] .== 0.0) - @test all(sol["solution"]["gen"]["1"]["qg"][2:3] .== 0.0) + @test all(sol["solution"]["solar"]["pv1"]["pg"][2:3] .== 0.0) + @test all(sol["solution"]["solar"]["pv1"]["qg"][2:3] .== 0.0) end @testset "3-bus balanced capacitor acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced_cap.dss"; data_model="mathematical") - sol = PMD.run_mc_pf(pmd, PMs.ACPPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_balanced_cap.dss") + sol = run_mc_pf(pmd, ACPPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED - @test all(abs(sol["solution"]["bus"]["3"]["vm"][c]-0.98588)<=1E-4 for c in 1:3) - @test all(abs(sol["solution"]["bus"]["1"]["vm"][c]-0.99127)<=1E-4 for c in 1:3) + @test all(abs(sol["solution"]["bus"]["loadbus"]["vm"][c]-0.98588)<=1E-4 for c in 1:3) + @test all(abs(sol["solution"]["bus"]["primary"]["vm"][c]-0.99127)<=1E-4 for c in 1:3) end @testset "3w transformer nfa opf" begin - mp_data = PMD.parse_file("../test/data/opendss/ut_trans_3w_dyy_1.dss"; data_model="mathematical") - result = run_mc_opf(mp_data, PMs.NFAPowerModel, ipopt_solver) + mp_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_1.dss", data_model="mathematical") + result = run_mc_opf(mp_data, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.616; atol=1e-3) end end From f6685fc127e5a7c277dc7e16c08b6f4eec8420b6 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 14:54:50 -0600 Subject: [PATCH 157/224] FIX: transformer solution reporting --- src/data_model/math2eng.jl | 6 ++++-- test/opf.jl | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index fa7670bf3..92083c1ed 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -153,8 +153,10 @@ function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dic prop_map = Dict("pf"=>"p", "qf"=>"q", "crt_fr"=>"crt", "cit_fr"=>"cit") for (prop_from, prop_to) in prop_map - if any(haskey(data_math["transformer"][id], prop_from) for id in trans_2wa_ids) - eng_obj[prop_to] = [get(data_math["transformer"][id], prop_from, NaN) for id in trans_2wa_ids] + if haskey(data_math, "transformer") + if any(haskey(data_math["transformer"][id], prop_from) for id in trans_2wa_ids) + eng_obj[prop_to] = [get(data_math["transformer"][id], prop_from, NaN) for id in trans_2wa_ids] + end end end diff --git a/test/opf.jl b/test/opf.jl index d3bfb43ef..591bfe42f 100644 --- a/test/opf.jl +++ b/test/opf.jl @@ -215,7 +215,7 @@ end @testset "3w transformer nfa opf" begin - mp_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_1.dss", data_model="mathematical") + mp_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_1.dss") result = run_mc_opf(mp_data, NFAPowerModel, ipopt_solver) @test result["termination_status"] == LOCALLY_SOLVED From 0304d691cf7223fcd16df7e54edd7a1ec664556c Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 15:04:13 -0600 Subject: [PATCH 158/224] REF: opf_bf tests to eng data model --- test/opf_bf.jl | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/test/opf_bf.jl b/test/opf_bf.jl index b00250726..09f497cdb 100644 --- a/test/opf_bf.jl +++ b/test/opf_bf.jl @@ -1,13 +1,14 @@ @info "running branch-flow optimal power flow (opf_bf) tests" @testset "test distflow formulations" begin + case5 = PowerModels.parse_file("../test/data/matpower/case5.m") + make_multiconductor!(case5, 3) + @testset "test linearised distflow opf_bf" begin @testset "5-bus lpubfdiag opf_bf" begin - mp_data = PowerModels.parse_file("../test/data/matpower/case5.m") - PMD.make_multiconductor!(mp_data, 3) - result = run_mc_opf_bf(mp_data, LPUBFDiagPowerModel, ipopt_solver) + result = run_mc_opf_bf(case5, LPUBFDiagPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 44880; atol = 1e0) # @test isapprox(result["solution"]["bus"]["3"]["vm"], 0.911466*[1,1,1]; atol = 1e-3) vm = calc_vm_w(result, "3") @@ -16,42 +17,38 @@ end @testset "3-bus balanced lpubfdiag opf_bf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") - sol = PMD.run_mc_opf_bf(pmd, LPUBFDiagPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_balanced.dss") + sol = run_mc_opf_bf(pmd, LPUBFDiagPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=2e-3) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=2e-3) + @test sol["termination_status"] == LOCALLY_SOLVED + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.0183456; atol=2e-3) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.00923328; atol=2e-3) end @testset "3-bus unbalanced lpubfdiag opf_bf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model="mathematical") - sol = PMD.run_mc_opf_bf(pmd, LPUBFDiagPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_unbalanced.dss") + sol = run_mc_opf_bf(pmd, LPUBFDiagPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214812; atol=2e-3) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00927263; atol=2e-3) + @test sol["termination_status"] == LOCALLY_SOLVED + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.0214812; atol=2e-3) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.00927263; atol=2e-3) end end @testset "test linearised distflow opf_bf in diagonal matrix form" begin @testset "5-bus lpdiagubf opf_bf" begin - mp_data = PowerModels.parse_file("../test/data/matpower/case5.m") - PMD.make_multiconductor!(mp_data, 3) - result = run_mc_opf_bf(mp_data, LPUBFDiagPowerModel, ipopt_solver) + result = run_mc_opf_bf(case5, LPUBFDiagPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 44880; atol = 1e0) end end @testset "test linearised distflow opf_bf in full matrix form" begin @testset "5-bus lpfullubf opf_bf" begin - mp_data = PowerModels.parse_file("../test/data/matpower/case5.m") - PMD.make_multiconductor!(mp_data, 3) - result = run_mc_opf_bf(mp_data, LPUBFDiagPowerModel, ipopt_solver) + result = run_mc_opf_bf(case5, LPUBFDiagPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 44880; atol = 1e0) end end From feaef3e00236b25e4f7a005d6843b4c89fef756d Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 15:11:06 -0600 Subject: [PATCH 159/224] REF: opf_iv tests to eng data model --- test/opf_iv.jl | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/opf_iv.jl b/test/opf_iv.jl index ce971cb67..97ad1f56d 100644 --- a/test/opf_iv.jl +++ b/test/opf_iv.jl @@ -3,37 +3,37 @@ @testset "test current-voltage formulations" begin @testset "test IVR opf_iv" begin @testset "2-bus diagonal acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case2_diag.dss"; data_model="mathematical") - sol = PMD.run_mc_opf_iv(pmd, PMs.IVRPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case2_diag.dss") + sol = run_mc_opf_iv(pmd, IVRPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED @test isapprox(sol["objective"], 0.018208969542066918; atol = 1e-4) - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018209; atol=1e-5) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.000208979; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.018209; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.000208979; atol=1e-5) end @testset "3-bus balanced acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") - sol = PMD.run_mc_opf_iv(pmd, PMs.IVRPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_balanced.dss") + sol = run_mc_opf_iv(pmd, IVRPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED @test isapprox(sol["objective"], 0.018345004773175046; atol = 1e-4) - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.018345; atol=1e-6) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00919404; atol=1.2e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.018345; atol=1e-6) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.00919404; atol=1.2e-5) end @testset "3-bus unbalanced acp opf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model="mathematical") - sol = PMD.run_mc_opf_iv(pmd, PMs.IVRPowerModel, ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_unbalanced.dss") + sol = run_mc_opf_iv(pmd, IVRPowerModel, ipopt_solver; make_si=false) - @test sol["termination_status"] == PMs.LOCALLY_SOLVED + @test sol["termination_status"] == LOCALLY_SOLVED @test isapprox(sol["objective"], 0.021481176584287; atol = 1e-4) - @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214812; atol=1e-6) - @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00927263; atol=1e-5) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.0214812; atol=1e-6) + @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.00927263; atol=1e-5) end end end From 1e2e638142946f788d7f6284d1cbe075ab05e496 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 15:12:54 -0600 Subject: [PATCH 160/224] REF: storage tests to eng data model --- test/debug.jl | 6 +++--- test/storage.jl | 47 ++++++++++++++++++----------------------------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/test/debug.jl b/test/debug.jl index e9d964013..2c423d27e 100644 --- a/test/debug.jl +++ b/test/debug.jl @@ -2,10 +2,10 @@ @testset "test pbs" begin @testset "case 3 unbalanced - ac pf pbs" begin - mp_data = PMD.parse_file("../test/data/opendss/case3_unbalanced.dss") - result = PMD.run_mc_pf_pbs(mp_data, PMs.ACPPowerModel, ipopt_solver) + mp_data = parse_file("../test/data/opendss/case3_unbalanced.dss") + result = run_mc_pf_pbs(mp_data, ACPPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.0; atol=1e-4) end end diff --git a/test/storage.jl b/test/storage.jl index a999b2e72..820031072 100644 --- a/test/storage.jl +++ b/test/storage.jl @@ -1,56 +1,45 @@ @info "running storage optimal power flow tests" @testset "test storage opf" begin - @testset "5-bus storage acp opf_strg" begin - mp_data = PowerModels.parse_file("../test/data/matpower/case5_strg.m") - PMD.make_multiconductor!(mp_data, 3) + mp_data = PM.parse_file("../test/data/matpower/case5_strg.m") + make_multiconductor!(mp_data, 3) - result = PMD.run_mc_opf(mp_data, PowerModels.ACPPowerModel, ipopt_solver) + @testset "5-bus storage acp opf_strg" begin + result = run_mc_opf(mp_data, ACPPowerModel, ipopt_solver; make_si=false) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 52299.2; atol = 1e0) - @test isapprox(result["solution"]["storage"]["1"]["se"], 0.0; atol = 1e0) - @test isapprox(result["solution"]["storage"]["2"]["se"], 0.0; atol = 1e0) - for c in 1:mp_data["conductors"] - @test isapprox(result["solution"]["storage"]["1"]["ps"][c], -0.0596928; atol = 1e-3) - @test isapprox(result["solution"]["storage"]["2"]["ps"][c], -0.0794600; atol = 1e-3) - end + @test isapprox(result["solution"]["storage"]["1"]["se"], 0.0; atol=1e0) + @test isapprox(result["solution"]["storage"]["2"]["se"], 0.0; atol=1e0) + + @test all(isapprox.(result["solution"]["storage"]["1"]["ps"], -0.0596928; atol=1e-3)) + @test all(isapprox.(result["solution"]["storage"]["2"]["ps"], -0.0794600; atol=1e-3)) end @testset "5-bus storage dcp opf_strg" begin - mp_data = PowerModels.parse_file("../test/data/matpower/case5_strg.m") - PMD.make_multiconductor!(mp_data, 3) - - result = PMD.run_mc_opf(mp_data, PowerModels.DCPPowerModel, ipopt_solver) + result = run_mc_opf(mp_data, DCPPowerModel, ipopt_solver; make_si=false) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 52059.6; atol = 1e0) @test isapprox(result["solution"]["storage"]["1"]["se"], 0.0; atol = 1e0) @test isapprox(result["solution"]["storage"]["2"]["se"], 0.0; atol = 1e0) - for c in 1:mp_data["conductors"] - @test isapprox(result["solution"]["storage"]["1"]["ps"][c], -0.0596443; atol = 1e-3) - @test isapprox(result["solution"]["storage"]["2"]["ps"][c], -0.0793700; atol = 1e-3) - end + @test all(isapprox.(result["solution"]["storage"]["1"]["ps"], -0.0596443; atol=1e-3)) + @test all(isapprox.(result["solution"]["storage"]["2"]["ps"], -0.0793700; atol=1e-3)) end @testset "5-bus storage nfa opf_strg" begin - mp_data = PowerModels.parse_file("../test/data/matpower/case5_strg.m") - PMD.make_multiconductor!(mp_data, 3) - - result = PMD.run_mc_opf(mp_data, PowerModels.NFAPowerModel, ipopt_solver) + result = run_mc_opf(mp_data, NFAPowerModel, ipopt_solver; make_si=false) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 43169.9; atol = 1e0) @test isapprox(result["solution"]["storage"]["1"]["se"], 0.0; atol = 1e0) @test isapprox(result["solution"]["storage"]["2"]["se"], 0.0; atol = 1e0) - for c in 1:mp_data["conductors"] - @test isapprox(result["solution"]["storage"]["1"]["ps"][c], -0.0596443; atol = 1e-3) - @test isapprox(result["solution"]["storage"]["2"]["ps"][c], -0.0793700; atol = 1e-3) - end + @test all(isapprox.(result["solution"]["storage"]["1"]["ps"], -0.0596443; atol=1e-3)) + @test all(isapprox.(result["solution"]["storage"]["2"]["ps"], -0.0793700; atol=1e-3)) end end From f05f9973e78dac823e0861066f6cec3521648fc2 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 16:27:44 -0600 Subject: [PATCH 161/224] REF: loadmodels tests to eng data model --- test/common.jl | 5 ++ test/loadmodels.jl | 156 ++++++++++++++++++++++----------------------- 2 files changed, 81 insertions(+), 80 deletions(-) diff --git a/test/common.jl b/test/common.jl index 2d6fcfb9b..1020f95d8 100644 --- a/test/common.jl +++ b/test/common.jl @@ -27,6 +27,11 @@ vi(sol, name) = sol["solution"]["bus"][name]["vi"] vr(sol, name) = sol["solution"]["bus"][name]["vr"] calc_vm_acr(sol, name) = sqrt.(vi(sol, name).^2 .+ vr(sol, name).^2) calc_va_acr(sol, name) = rad2deg.(PMD._wrap_to_pi(atan.(vi(sol, name), vr(sol, name)))) +va(sol, name) = PMD._wrap_to_pi(sol["solution"]["bus"][name]["va"][:])*180/pi +vm(sol, name) = sol["solution"]["bus"][name]["vm"] +pd(sol, name) = sol["solution"]["load"][name]["pd_bus"] +qd(sol, name) = sol["solution"]["load"][name]["qd_bus"] + # Helper functions for load models tests load_name2id(pmd_data, name) = [load["index"] for (_,load) in pmd_data["load"] if haskey(load, "name") && load["name"]==name][1] diff --git a/test/loadmodels.jl b/test/loadmodels.jl index 701252e17..f16ea9d2a 100644 --- a/test/loadmodels.jl +++ b/test/loadmodels.jl @@ -2,107 +2,103 @@ @testset "test loadmodels pf" begin @testset "loadmodels connection variations" begin - pmd = PMD.parse_file("../test/data/opendss/case3_lm_1230.dss"; data_model="mathematical") - pm = PMs.instantiate_model(pmd, PMs.ACPPowerModel, PMD.build_mc_pf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) - sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_lm_1230.dss") + sol = run_mc_pf(pmd, ACPPowerModel, ipopt_solver; make_si=false) # voltage magnitude at load bus - @test isapprox(vm(sol, pmd, "loadbus"), [0.999993, 0.999992, 0.999993], atol=1E-5) + @test isapprox(vm(sol, "loadbus"), [0.999993, 0.999992, 0.999993], atol=1E-5) # single-phase delta loads - @test isapprox(pd(sol, pmd, "d1ph23"), [0, 0.2866, 0.1134], atol=1E-4) - @test isapprox(qd(sol, pmd, "d1ph23"), [0, 0.0345, 0.2655], atol=1E-4) + @test isapprox(pd(sol, "d1ph23"), [0, 0.2866, 0.1134], atol=1E-4) + @test isapprox(qd(sol, "d1ph23"), [0, 0.0345, 0.2655], atol=1E-4) # single-phase wye loads - @test isapprox(pd(sol, pmd, "y1ph2"), [0, 0.4000, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1ph2"), [0, 0.3000, 0], atol=1E-4) + @test isapprox(pd(sol, "y1ph2"), [0, 0.4000, 0], atol=1E-4) + @test isapprox(qd(sol, "y1ph2"), [0, 0.3000, 0], atol=1E-4) # three-phase loads - @test isapprox(pd(sol, pmd, "d3ph"), [0.1333, 0.1333, 0.1333], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3ph"), [0.100, 0.100, 0.100], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3ph123"), [0.1333, 0.1333, 0.1333], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3ph123"), [0.100, 0.100, 0.100], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3ph213"), [0.1333, 0.1333, 0.1333], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3ph213"), [0.100, 0.100, 0.100], atol=1E-4) + @test isapprox(pd(sol, "d3ph"), [0.1333, 0.1333, 0.1333], atol=1E-4) + @test isapprox(qd(sol, "d3ph"), [0.100, 0.100, 0.100], atol=1E-4) + @test isapprox(pd(sol, "d3ph123"), [0.1333, 0.1333, 0.1333], atol=1E-4) + @test isapprox(qd(sol, "d3ph123"), [0.100, 0.100, 0.100], atol=1E-4) + @test isapprox(pd(sol, "d3ph213"), [0.1333, 0.1333, 0.1333], atol=1E-4) + @test isapprox(qd(sol, "d3ph213"), [0.100, 0.100, 0.100], atol=1E-4) end @testset "loadmodels 1/2/5 in acp pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss"; data_model="mathematical") - pm = PMs.instantiate_model(pmd, PMs.ACPPowerModel, PMD.build_mc_pf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) - sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_lm_models.dss") + sol = run_mc_pf(pmd, ACPPowerModel, ipopt_solver; make_si=false) # voltage magnitude at load bus - @test isapprox(vm(sol, pmd, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) + @test isapprox(vm(sol, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) # delta and wye single-phase load models - @test isapprox(pd(sol, pmd, "y1phm1"), [0.4000, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1phm1"), [0.3000, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "y1phm2"), [0.2783, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1phm2"), [0.2087, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "y1phm5"), [0.3336, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1phm5"), [0.2502, 0, 0], atol=1E-4) + @test isapprox(pd(sol, "y1phm1"), [0.4000, 0, 0], atol=1E-4) + @test isapprox(qd(sol, "y1phm1"), [0.3000, 0, 0], atol=1E-4) + @test isapprox(pd(sol, "y1phm2"), [0.2783, 0, 0], atol=1E-4) + @test isapprox(qd(sol, "y1phm2"), [0.2087, 0, 0], atol=1E-4) + @test isapprox(pd(sol, "y1phm5"), [0.3336, 0, 0], atol=1E-4) + @test isapprox(qd(sol, "y1phm5"), [0.2502, 0, 0], atol=1E-4) # delta three-phase loads - @test isapprox(pd(sol, pmd, "d3phm1"), [0.1160, 0.1465, 0.1375], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3phm1"), [0.0896, 0.0977, 0.1127], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3phm2"), [0.1005, 0.1348, 0.1212], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3phm2"), [0.0771, 0.0854, 0.1048], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3phm5"), [0.1080, 0.1405, 0.1291], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3phm5"), [0.0831, 0.0914, 0.1087], atol=1E-4) + @test isapprox(pd(sol, "d3phm1"), [0.1160, 0.1465, 0.1375], atol=1E-4) + @test isapprox(qd(sol, "d3phm1"), [0.0896, 0.0977, 0.1127], atol=1E-4) + @test isapprox(pd(sol, "d3phm2"), [0.1005, 0.1348, 0.1212], atol=1E-4) + @test isapprox(qd(sol, "d3phm2"), [0.0771, 0.0854, 0.1048], atol=1E-4) + @test isapprox(pd(sol, "d3phm5"), [0.1080, 0.1405, 0.1291], atol=1E-4) + @test isapprox(qd(sol, "d3phm5"), [0.0831, 0.0914, 0.1087], atol=1E-4) # wye three-phase loads - @test isapprox(pd(sol, pmd, "y3phm1"), [0.1333, 0.1333, 0.1333], atol=1E-4) - @test isapprox(qd(sol, pmd, "y3phm1"), [0.1000, 0.1000, 0.1000], atol=1E-4) - @test isapprox(pd(sol, pmd, "y3phm2"), [0.0920, 0.1324, 0.1349], atol=1E-4) - @test isapprox(qd(sol, pmd, "y3phm2"), [0.0690, 0.0993, 0.1012], atol=1E-4) - @test isapprox(pd(sol, pmd, "y3phm5"), [0.1108, 0.1329, 0.1341], atol=1E-4) - @test isapprox(qd(sol, pmd, "y3phm5"), [0.0831, 0.0997, 0.1006], atol=1E-4) + @test isapprox(pd(sol, "y3phm1"), [0.1333, 0.1333, 0.1333], atol=1E-4) + @test isapprox(qd(sol, "y3phm1"), [0.1000, 0.1000, 0.1000], atol=1E-4) + @test isapprox(pd(sol, "y3phm2"), [0.0920, 0.1324, 0.1349], atol=1E-4) + @test isapprox(qd(sol, "y3phm2"), [0.0690, 0.0993, 0.1012], atol=1E-4) + @test isapprox(pd(sol, "y3phm5"), [0.1108, 0.1329, 0.1341], atol=1E-4) + @test isapprox(qd(sol, "y3phm5"), [0.0831, 0.0997, 0.1006], atol=1E-4) end @testset "loadmodels 1/2/5 in acr pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss"; data_model="mathematical") - pm = PMs.instantiate_model(pmd, PMs.ACRPowerModel, PMD.build_mc_pf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) - sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_lm_models.dss") + sol = run_mc_pf(pmd, ACRPowerModel, ipopt_solver; make_si=false) # voltage magnitude at load bus - @test isapprox(calc_vm_acr(sol, pmd, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) + @test isapprox(calc_vm_acr(sol, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) # delta and wye single-phase load models - @test isapprox(pd(sol, pmd, "y1phm1"), [0.4000, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1phm1"), [0.3000, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "y1phm2"), [0.2783, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1phm2"), [0.2087, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "y1phm5"), [0.3336, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1phm5"), [0.2502, 0, 0], atol=1E-4) + @test isapprox(pd(sol, "y1phm1"), [0.4000, 0, 0], atol=1E-4) + @test isapprox(qd(sol, "y1phm1"), [0.3000, 0, 0], atol=1E-4) + @test isapprox(pd(sol, "y1phm2"), [0.2783, 0, 0], atol=1E-4) + @test isapprox(qd(sol, "y1phm2"), [0.2087, 0, 0], atol=1E-4) + @test isapprox(pd(sol, "y1phm5"), [0.3336, 0, 0], atol=1E-4) + @test isapprox(qd(sol, "y1phm5"), [0.2502, 0, 0], atol=1E-4) # delta three-phase loads - @test isapprox(pd(sol, pmd, "d3phm1"), [0.1160, 0.1465, 0.1375], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3phm1"), [0.0896, 0.0977, 0.1127], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3phm2"), [0.1005, 0.1348, 0.1212], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3phm2"), [0.0771, 0.0854, 0.1048], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3phm5"), [0.1080, 0.1405, 0.1291], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3phm5"), [0.0831, 0.0914, 0.1087], atol=1E-4) + @test isapprox(pd(sol, "d3phm1"), [0.1160, 0.1465, 0.1375], atol=1E-4) + @test isapprox(qd(sol, "d3phm1"), [0.0896, 0.0977, 0.1127], atol=1E-4) + @test isapprox(pd(sol, "d3phm2"), [0.1005, 0.1348, 0.1212], atol=1E-4) + @test isapprox(qd(sol, "d3phm2"), [0.0771, 0.0854, 0.1048], atol=1E-4) + @test isapprox(pd(sol, "d3phm5"), [0.1080, 0.1405, 0.1291], atol=1E-4) + @test isapprox(qd(sol, "d3phm5"), [0.0831, 0.0914, 0.1087], atol=1E-4) # wye three-phase loads - @test isapprox(pd(sol, pmd, "y3phm1"), [0.1333, 0.1333, 0.1333], atol=1E-4) - @test isapprox(qd(sol, pmd, "y3phm1"), [0.1000, 0.1000, 0.1000], atol=1E-4) - @test isapprox(pd(sol, pmd, "y3phm2"), [0.0920, 0.1324, 0.1349], atol=1E-4) - @test isapprox(qd(sol, pmd, "y3phm2"), [0.0690, 0.0993, 0.1012], atol=1E-4) - @test isapprox(pd(sol, pmd, "y3phm5"), [0.1108, 0.1329, 0.1341], atol=1E-4) - @test isapprox(qd(sol, pmd, "y3phm5"), [0.0831, 0.0997, 0.1006], atol=1E-4) + @test isapprox(pd(sol, "y3phm1"), [0.1333, 0.1333, 0.1333], atol=1E-4) + @test isapprox(qd(sol, "y3phm1"), [0.1000, 0.1000, 0.1000], atol=1E-4) + @test isapprox(pd(sol, "y3phm2"), [0.0920, 0.1324, 0.1349], atol=1E-4) + @test isapprox(qd(sol, "y3phm2"), [0.0690, 0.0993, 0.1012], atol=1E-4) + @test isapprox(pd(sol, "y3phm5"), [0.1108, 0.1329, 0.1341], atol=1E-4) + @test isapprox(qd(sol, "y3phm5"), [0.0831, 0.0997, 0.1006], atol=1E-4) end @testset "loadmodels 1/2/5 in ivr pf" begin - pmd = PMD.parse_file("../test/data/opendss/case3_lm_models.dss"; data_model="mathematical") - pm = PMs.instantiate_model(pmd, PMs.IVRPowerModel, PMD.build_mc_pf_iv, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) - sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + pmd = parse_file("../test/data/opendss/case3_lm_models.dss") + sol = run_mc_pf_iv(pmd, IVRPowerModel, ipopt_solver; make_si=false) # voltage magnitude at load bus - @test isapprox(calc_vm_acr(sol, pmd, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) + @test isapprox(calc_vm_acr(sol, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) # delta and wye single-phase load models - @test isapprox(pd(sol, pmd, "y1phm1"), [0.4000, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1phm1"), [0.3000, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "y1phm2"), [0.2783, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1phm2"), [0.2087, 0, 0], atol=1E-4) - @test isapprox(pd(sol, pmd, "y1phm5"), [0.3336, 0, 0], atol=1E-4) - @test isapprox(qd(sol, pmd, "y1phm5"), [0.2502, 0, 0], atol=1E-4) + @test isapprox(pd(sol, "y1phm1"), [0.4000, 0, 0], atol=1E-4) + @test isapprox(qd(sol, "y1phm1"), [0.3000, 0, 0], atol=1E-4) + @test isapprox(pd(sol, "y1phm2"), [0.2783, 0, 0], atol=1E-4) + @test isapprox(qd(sol, "y1phm2"), [0.2087, 0, 0], atol=1E-4) + @test isapprox(pd(sol, "y1phm5"), [0.3336, 0, 0], atol=1E-4) + @test isapprox(qd(sol, "y1phm5"), [0.2502, 0, 0], atol=1E-4) # delta three-phase loads - @test isapprox(pd(sol, pmd, "d3phm1"), [0.1160, 0.1465, 0.1375], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3phm1"), [0.0896, 0.0977, 0.1127], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3phm2"), [0.1005, 0.1348, 0.1212], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3phm2"), [0.0771, 0.0854, 0.1048], atol=1E-4) - @test isapprox(pd(sol, pmd, "d3phm5"), [0.1080, 0.1405, 0.1291], atol=1E-4) - @test isapprox(qd(sol, pmd, "d3phm5"), [0.0831, 0.0914, 0.1087], atol=1E-4) + @test isapprox(pd(sol, "d3phm1"), [0.1160, 0.1465, 0.1375], atol=1E-4) + @test isapprox(qd(sol, "d3phm1"), [0.0896, 0.0977, 0.1127], atol=1E-4) + @test isapprox(pd(sol, "d3phm2"), [0.1005, 0.1348, 0.1212], atol=1E-4) + @test isapprox(qd(sol, "d3phm2"), [0.0771, 0.0854, 0.1048], atol=1E-4) + @test isapprox(pd(sol, "d3phm5"), [0.1080, 0.1405, 0.1291], atol=1E-4) + @test isapprox(qd(sol, "d3phm5"), [0.0831, 0.0914, 0.1087], atol=1E-4) # wye three-phase loads - @test isapprox(pd(sol, pmd, "y3phm1"), [0.1333, 0.1333, 0.1333], atol=1E-4) - @test isapprox(qd(sol, pmd, "y3phm1"), [0.1000, 0.1000, 0.1000], atol=1E-4) - @test isapprox(pd(sol, pmd, "y3phm2"), [0.0920, 0.1324, 0.1349], atol=1E-4) - @test isapprox(qd(sol, pmd, "y3phm2"), [0.0690, 0.0993, 0.1012], atol=1E-4) - @test isapprox(pd(sol, pmd, "y3phm5"), [0.1108, 0.1329, 0.1341], atol=1E-4) - @test isapprox(qd(sol, pmd, "y3phm5"), [0.0831, 0.0997, 0.1006], atol=1E-4) + @test isapprox(pd(sol, "y3phm1"), [0.1333, 0.1333, 0.1333], atol=1E-4) + @test isapprox(qd(sol, "y3phm1"), [0.1000, 0.1000, 0.1000], atol=1E-4) + @test isapprox(pd(sol, "y3phm2"), [0.0920, 0.1324, 0.1349], atol=1E-4) + @test isapprox(qd(sol, "y3phm2"), [0.0690, 0.0993, 0.1012], atol=1E-4) + @test isapprox(pd(sol, "y3phm5"), [0.1108, 0.1329, 0.1341], atol=1E-4) + @test isapprox(qd(sol, "y3phm5"), [0.0831, 0.0997, 0.1006], atol=1E-4) end end From 89e9e73ad9b4d365d5c65bcff88968fc0ebf062b Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 16:27:57 -0600 Subject: [PATCH 162/224] REF: mld tests to eng data model --- test/mld.jl | 75 +++++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/test/mld.jl b/test/mld.jl index c93e615a5..c03826014 100644 --- a/test/mld.jl +++ b/test/mld.jl @@ -1,11 +1,15 @@ @info "running minimum load delta (mld) tests" @testset "test mld" begin + case5 = PM.parse_file("$(pms_path)/test/data/matpower/case5.m"); make_multiconductor!(case5, 3) + case5_strg = PM.parse_file("$(pms_path)/test/data/matpower/case5_strg.m"); make_multiconductor!(case5_strg, 3) + case3_ml = PM.parse_file("../test/data/matpower/case3_ml.m"); make_multiconductor!(case3_ml, 3) + + ut_trans_2w_yy = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss") @testset "5-bus acp mld" begin - mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5.m"); PMD.make_multiconductor!(mp_data, 3) - result = run_mc_mld(mp_data, ACPPowerModel, ipopt_solver) + result = run_mc_mld(case5, ACPPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.3377; atol = 1e-4) @test all(isapprox(result["solution"]["load"]["1"]["pd"], [3.0, 3.0, 3.0]; atol=1e-4)) @test all(isapprox(result["solution"]["load"]["1"]["qd"], [0.9861, 0.9861, 0.9861]; atol=1e-4)) @@ -14,120 +18,113 @@ end @testset "5-bus storage acp mld" begin - mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5_strg.m"); PMD.make_multiconductor!(mp_data, 3) - result = run_mc_mld(mp_data, ACPPowerModel, ipopt_solver) + mp_data = + result = run_mc_mld(case5_strg, ACPPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.3553; atol=1e-2) @test isapprox(result["solution"]["load"]["1"]["status"], 1.000; atol=1e-3) end @testset "5-bus nfa mld" begin - mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5.m"); PMD.make_multiconductor!(mp_data, 3) - result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) + result = run_mc_mld(case5, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.1557; atol = 1e-4) @test isapprox(result["solution"]["load"]["1"]["status"], 1.000; atol = 1e-3) end @testset "5-bus storage nfa mld" begin - mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5_strg.m"); PMD.make_multiconductor!(mp_data, 3) - result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) + result = run_mc_mld(case5_strg, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.1557; atol=1e-4) @test isapprox(result["solution"]["load"]["1"]["status"], 1.000; atol=1e-3) end @testset "3-bus nfa mld" begin - mp_data = PMs.parse_file("../test/data/matpower/case3_ml.m"); PMD.make_multiconductor!(mp_data, 3) + mp_data = PM.parse_file("../test/data/matpower/case3_ml.m"); make_multiconductor!(mp_data, 3) result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 24.9; atol = 1e-1) @test isapprox(result["solution"]["load"]["1"]["status"], 0.689; atol = 1e-3) end @testset "3-bus shunt nfa mld" begin - mp_data = PMs.parse_file("../test/data/matpower/case3_ml_s.m"); PMD.make_multiconductor!(mp_data, 3) + mp_data = PM.parse_file("../test/data/matpower/case3_ml_s.m"); make_multiconductor!(mp_data, 3) result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 22.2; atol = 1e-1) @test isapprox(result["solution"]["load"]["1"]["status"], 0.6; atol = 1e-3) end @testset "3-bus line charge nfa mld" begin - mp_data = PMs.parse_file("../test/data/matpower/case3_ml_lc.m"); PMD.make_multiconductor!(mp_data, 3) + mp_data = PM.parse_file("../test/data/matpower/case3_ml_lc.m"); make_multiconductor!(mp_data, 3) result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 43.8; atol = 1e-1) @test isapprox(result["solution"]["load"]["1"]["status"], 0.544; atol = 1e-3) end @testset "transformer nfa mld" begin - mp_data = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; data_model="mathematical") - result = run_mc_mld(mp_data, NFAPowerModel, ipopt_solver) + result = run_mc_mld(ut_trans_2w_yy, NFAPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.411, atol = 1e-3) - @test isapprox(result["solution"]["load"]["1"]["status"], 1.0, atol = 1e-3) + @test isapprox(result["solution"]["load"]["load1"]["status"], 1.0, atol = 1e-3) end @testset "5-bus lpubfdiag mld" begin - mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5.m"); PMD.make_multiconductor!(mp_data, 3) - result = run_mc_mld_bf(mp_data, LPUBFDiagPowerModel, ipopt_solver) + result = run_mc_mld_bf(case5, LPUBFDiagPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.1557; atol = 1e-4) @test isapprox(result["solution"]["load"]["1"]["status"], 1.000; atol = 1e-3) - @test_throws(TESTLOG, MethodError, run_mc_mld_bf(mp_data, NFAPowerModel, ipopt_solver)) + @test_throws(TESTLOG, MethodError, run_mc_mld_bf(case5, NFAPowerModel, ipopt_solver)) end @testset "3-bus lpubfdiag mld" begin - mp_data = PMs.parse_file("../test/data/matpower/case3_ml.m"); PMD.make_multiconductor!(mp_data, 3) - result = run_mc_mld_bf(mp_data, LPUBFDiagPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + result = run_mc_mld_bf(case3_ml, LPUBFDiagPowerModel, ipopt_solver) + + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 24.98; atol = 1e-1) @test isapprox(result["solution"]["load"]["1"]["status"], 0.313; atol = 1e-3) end @testset "transformer case" begin - dss = PowerModelsDistribution.parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; data_model="mathematical") - result = run_mc_mld_bf(dss, LPUBFDiagPowerModel, ipopt_solver) + result = run_mc_mld_bf(ut_trans_2w_yy, LPUBFDiagPowerModel, ipopt_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.0; atol=1e-3) - @test isapprox(result["solution"]["load"]["1"]["status"], 1.0; atol=1e-3) + @test isapprox(result["solution"]["load"]["load1"]["status"], 1.0; atol=1e-3) end @testset "5-bus acp mld_uc" begin - mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5.m"); PMD.make_multiconductor!(mp_data, 3) - result = run_mc_mld_uc(mp_data, ACPPowerModel, juniper_solver) + result = run_mc_mld_uc(case5, ACPPowerModel, juniper_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 0.338; atol=1e-3) @test all_gens_on(result) @test all_voltages_on(result) end @testset "5-bus nfa mld_uc" begin - mp_data = PMs.parse_file("$(pms_path)/test/data/matpower/case5.m"); PMD.make_multiconductor!(mp_data, 3) - result = run_mc_mld_uc(mp_data, NFAPowerModel, juniper_solver) + result = run_mc_mld_uc(case5, NFAPowerModel, juniper_solver) - @test result["termination_status"] == PMs.LOCALLY_SOLVED + @test result["termination_status"] == LOCALLY_SOLVED for (i, gen) in result["solution"]["gen"] if i != "4" @test isapprox(gen["gen_status"], 1.0; atol=1e-5) From e1121d817bc3e929d7a09adcebd830cd8732f0a9 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 16:28:23 -0600 Subject: [PATCH 163/224] REF: misc tests updates --- test/multinetwork.jl | 2 +- test/shunt.jl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/multinetwork.jl b/test/multinetwork.jl index 63fa79349..5c19a70e2 100644 --- a/test/multinetwork.jl +++ b/test/multinetwork.jl @@ -9,7 +9,7 @@ # result = PMD._run_mn_mc_opf_strg(mn_mp_data, PowerModels.ACPPowerModel, ipopt_solver) -# @test result["termination_status"] == PMs.LOCALLY_SOLVED +# @test result["termination_status"] == PM.LOCALLY_SOLVED # @test isapprox(result["objective"], 2.64596e5; atol = 1e2) # for (n, network) in result["solution"]["nw"], c in 1:network["conductors"] diff --git a/test/shunt.jl b/test/shunt.jl index 0116d71c1..f0f4c7486 100644 --- a/test/shunt.jl +++ b/test/shunt.jl @@ -10,10 +10,10 @@ data_diag_shunt = deepcopy(data) data_diag_shunt["shunt"]["1"]["bs"] = shunt["bs"].*[c==d for c in 1:3, d in 1:3] - sol_acp_diag = run_mc_pf(data_diag_shunt, PMs.ACPPowerModel, ipopt_solver) - sol_acp = run_mc_pf(data, PMs.ACPPowerModel, ipopt_solver) - sol_acr = run_mc_pf(data, PMs.ACRPowerModel, ipopt_solver) - sol_iv = run_mc_pf_iv(data, PMs.IVRPowerModel, ipopt_solver) + sol_acp_diag = run_mc_pf(data_diag_shunt, ACPPowerModel, ipopt_solver) + sol_acp = run_mc_pf(data, ACPPowerModel, ipopt_solver) + sol_acr = run_mc_pf(data, ACRPowerModel, ipopt_solver) + sol_iv = run_mc_pf_iv(data, IVRPowerModel, ipopt_solver) # check the results are different with only diagonal elements @test(!isapprox(sol_acp["solution"]["bus"]["2"]["vm"], sol_acp_diag["solution"]["bus"]["2"]["vm"])) From 6c6e3a412290cc3a8394f9eb4367256ec1c78a0a Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 17:07:22 -0600 Subject: [PATCH 164/224] REF: transformer tests to eng data model --- test/common.jl | 6 +-- test/data.jl | 4 +- test/delta_gens.jl | 14 ++--- test/runtests.jl | 2 +- test/shunt.jl | 2 +- test/transformer.jl | 122 ++++++++++++++++++-------------------------- 6 files changed, 65 insertions(+), 85 deletions(-) diff --git a/test/common.jl b/test/common.jl index 1020f95d8..0dfef0c56 100644 --- a/test/common.jl +++ b/test/common.jl @@ -16,7 +16,7 @@ end bus_name2id(pmd_data, name) = [bus["index"] for (_,bus) in pmd_data["bus"] if haskey(bus, "name") && bus["name"]==name][1] va(sol, pmd_data, name) = PMD._wrap_to_pi(sol["solution"]["bus"][string(bus_name2id(pmd_data, name))]["va"][:])*180/pi vm(sol, pmd_data, name) = sol["solution"]["bus"][string(bus_name2id(pmd_data, name))]["vm"] -tap(i, pm) = JuMP.value.(PMs.var(pm, pm.cnw, :tap)[i]) +tap(i, pm) = JuMP.value.(PM.var(pm, pm.cnw, :tap)[i]) vi(sol, pmd_data, name) = sol["solution"]["bus"][string(bus_name2id(pmd_data, name))]["vi"] vr(sol, pmd_data, name) = sol["solution"]["bus"][string(bus_name2id(pmd_data, name))]["vr"] calc_vm_acr(sol, pmd_data, name) = sqrt.(vi(sol, pmd_data, name).^2 .+ vr(sol, pmd_data, name).^2) @@ -35,9 +35,9 @@ qd(sol, name) = sol["solution"]["load"][name]["qd_bus"] # Helper functions for load models tests load_name2id(pmd_data, name) = [load["index"] for (_,load) in pmd_data["load"] if haskey(load, "name") && load["name"]==name][1] -pdvar(pm, pmd_data, name) = [PMs.var(pm, pm.cnw, c, :pd, load_name2id(pmd_data, name)) for c in 1:3] +pdvar(pm, pmd_data, name) = [PM.var(pm, pm.cnw, c, :pd, load_name2id(pmd_data, name)) for c in 1:3] pd(sol, pmd_data, name) = sol["solution"]["load"][string(load_name2id(pmd_data, name))]["pd_bus"] -qdvar(pm, pmd_data, name) = [PMs.var(pm, pm.cnw, c, :qd, load_name2id(pmd_data, name)) for c in 1:3] +qdvar(pm, pmd_data, name) = [PM.var(pm, pm.cnw, c, :qd, load_name2id(pmd_data, name)) for c in 1:3] qd(sol, pmd_data, name) = sol["solution"]["load"][string(load_name2id(pmd_data, name))]["qd_bus"] sd(pm, pmd_data, name) = pd(sol, pmd_data, name)+im*qd(sol, pmd_data, name) diff --git a/test/data.jl b/test/data.jl index eb7e505de..710edec3b 100644 --- a/test/data.jl +++ b/test/data.jl @@ -4,7 +4,7 @@ branch = Dict{String, Any}() branch["br_r"] = [1 2;3 4] branch["br_x"] = [1 2;3 4] - g,b = PMs.calc_branch_y(branch) + g,b = PM.calc_branch_y(branch) @test typeof(g) <: Matrix @test isapprox(g, [-1.0 0.5; 0.75 -0.25]) @@ -12,7 +12,7 @@ branch["br_r"] = [1 2 0;3 4 0; 0 0 0] branch["br_x"] = [1 2 0;3 4 0; 0 0 0] - g,b = PMs.calc_branch_y(branch) + g,b = PM.calc_branch_y(branch) @test typeof(g) <: Matrix @test isapprox(g, [-1.0 0.5 0; 0.75 -0.25 0; 0 0 0]) diff --git a/test/delta_gens.jl b/test/delta_gens.jl index 98b2d81c1..8dc3fa78e 100644 --- a/test/delta_gens.jl +++ b/test/delta_gens.jl @@ -4,7 +4,7 @@ # This test checks the generators are connected properly by comparing them # to equivalent constant-power loads. This is achieved by fixing their bounds. @testset "ACP/ACR tests" begin - pmd_1 = PMD.parse_file("$pmd_path/test/data/opendss/case3_delta_gens.dss"; data_model="mathematical") + pmd_1 = parse_file("$pmd_path/test/data/opendss/case3_delta_gens.dss"; data_model="mathematical") # convert to constant power loads for (_, load) in pmd_1["load"] @@ -32,15 +32,15 @@ # check ACP and ACR for (form, build_method) in zip( - [PMs.ACPPowerModel, PMs.ACRPowerModel, PMs.IVRPowerModel], - [PMD.build_mc_opf, PMD.build_mc_opf, PMD.build_mc_opf_iv] + [ACPPowerModel, ACRPowerModel, IVRPowerModel], + [build_mc_opf, build_mc_opf, build_mc_opf_iv] ) - pm_1 = PMs.instantiate_model(pmd_1, form, build_method, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) - sol_1 = PMs.optimize_model!(pm_1, optimizer=ipopt_solver) + pm_1 = PM.instantiate_model(pmd_1, form, build_method, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) + sol_1 = PM.optimize_model!(pm_1, optimizer=ipopt_solver) @assert(sol_1["termination_status"]==LOCALLY_SOLVED) - pm_2 = PMs.instantiate_model(pmd_2, form, build_method, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) - sol_2 = PMs.optimize_model!(pm_2, optimizer=ipopt_solver) + pm_2 = PM.instantiate_model(pmd_2, form, build_method, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) + sol_2 = PM.optimize_model!(pm_2, optimizer=ipopt_solver) @assert(sol_2["termination_status"]==LOCALLY_SOLVED) # check that gens are equivalent to the loads diff --git a/test/runtests.jl b/test/runtests.jl index 1029b4a62..789a60bec 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,7 +6,7 @@ import Memento import InfrastructureModels import PowerModels -const PMs = PowerModels +const PM = PowerModels # Suppress warnings during testing. const TESTLOG = Memento.getlogger(PowerModels) diff --git a/test/shunt.jl b/test/shunt.jl index f0f4c7486..57bf5b9fb 100644 --- a/test/shunt.jl +++ b/test/shunt.jl @@ -1,7 +1,7 @@ @info "running matrix shunt tests" @testset "matrix shunts ACP/ACR/IVR" begin - data = PMD.parse_file("data/opendss/case_mxshunt_2.dss"; data_model="mathematical") + data = parse_file("data/opendss/case_mxshunt_2.dss"; data_model="mathematical") shunt = data["shunt"]["1"] @test(isa(shunt["gs"], Matrix)) @test(isa(shunt["bs"], Matrix)) diff --git a/test/transformer.jl b/test/transformer.jl index 640cd1a79..44ebe2935 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -1,74 +1,61 @@ @info "running transformer tests" @testset "transformers" begin - @testset "test transformer acp pf" begin @testset "2w transformer acp pf yy" begin - file = "../test/data/opendss/ut_trans_2w_yy.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 + pmd_data = parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + sol = run_ac_mc_pf(pmd_data, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end @testset "2w transformer acp pf dy_lead" begin - file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true, solution_processors=[PMD.sol_polar_voltage!]) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[29.8, -90.4, 149.8], Inf) <= 0.1 + pmd_data = parse_file("../test/data/opendss/ut_trans_2w_dy_lead.dss") + sol = run_ac_mc_pf(pmd_data, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[29.8, -90.4, 149.8], Inf) <= 0.1 end @testset "2w transformer acp pf dy_lag" begin - file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[-30.0, -150.4, 89.8], Inf) <= 0.1 + file = + pmd_data = parse_file("../test/data/opendss/ut_trans_2w_dy_lag.dss") + sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[-30.0, -150.4, 89.8], Inf) <= 0.1 end end @testset "test transformer ivr pf" begin @testset "2w transformer ivr pf yy" begin - file = "../test/data/opendss/ut_trans_2w_yy.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 + pmd_data = parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + sol = run_mc_pf_iv(pmd_data, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lead" begin - file = "../test/data/opendss/ut_trans_2w_dy_lead.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[29.8, -90.4, 149.8], Inf) <= 0.1 + pmd_data = parse_file("../test/data/opendss/ut_trans_2w_dy_lead.dss") + sol = run_mc_pf_iv(pmd_data, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[29.8, -90.4, 149.8], Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lag" begin - file = "../test/data/opendss/ut_trans_2w_dy_lag.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_mc_pf_iv(pmd_data, PMs.IVRPowerModel, ipopt_solver, solution_processors=[PMD.sol_polar_voltage!]) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[-30.0, -150.4, 89.8], Inf) <= 0.1 + pmd_data = parse_file("../test/data/opendss/ut_trans_2w_dy_lag.dss") + sol = run_mc_pf_iv(pmd_data, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[-30.0, -150.4, 89.8], Inf) <= 0.1 end end @testset "2w transformer ac pf yy - banked transformers" begin - file = "../test/data/opendss/ut_trans_2w_yy_bank.dss" - eng1 = PMD.parse_file(file; data_model="engineering") - eng2 = PMD.parse_file(file; bank_transformers=false, data_model="engineering") + eng1 = parse_file("../test/data/opendss/ut_trans_2w_yy_bank.dss"; data_model="engineering") + eng2 = parse_file("../test/data/opendss/ut_trans_2w_yy_bank.dss"; bank_transformers=false, data_model="engineering") result1 = run_ac_mc_pf(eng1, ipopt_solver) result2 = run_ac_mc_pf(eng2, ipopt_solver) - @test result1["termination_status"] == PMs.LOCALLY_SOLVED - @test result2["termination_status"] == PMs.LOCALLY_SOLVED + @test result1["termination_status"] == LOCALLY_SOLVED + @test result2["termination_status"] == LOCALLY_SOLVED # @test result1["solution"]["bus"] == result2["solution"]["bus"] # TODO need a new test, transformer model changed, use voltages on real bus # @test result1["solution"]["gen"] == result2["solution"]["gen"] # TODO need a new test, transformer model changed, use voltages on real bus @@ -76,59 +63,52 @@ @testset "three winding transformer pf" begin @testset "3w transformer ac pf dyy - all non-zero" begin - file = "../test/data/opendss/ut_trans_3w_dyy_1.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[30.1, -90.7, 151.2], Inf) <= 0.1 + file = + pmd_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_1.dss") + sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[30.1, -90.7, 151.2], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - some non-zero" begin - file = "../test/data/opendss/ut_trans_3w_dyy_2.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) + file = + pmd_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_2.dss") + sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) #@test isapprox(vm(sol, pmd_data, "3"), [0.93876, 0.90227, 0.90454], atol=1E-4) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.93876, 0.90227, 0.90454], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[31.6, -88.8, 153.3], Inf) <= 0.1 + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.93876, 0.90227, 0.90454], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[31.6, -88.8, 153.3], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - all zero" begin - file = "../test/data/opendss/ut_trans_3w_dyy_3.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[30.6, -90.0, 151.9], Inf) <= 0.1 + pmd_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_3.dss") + sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[30.6, -90.0, 151.9], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - %loadloss=0" begin - file = "../test/data/opendss/ut_trans_3w_dyy_3_loadloss.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") - sol = PMD.run_ac_mc_pf(pmd_data, ipopt_solver, multiconductor=true) - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) - @test norm(solution["bus"]["3"]["vm"]-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 - @test norm(solution["bus"]["3"]["va"]-[30.7, -90.0, 152.0], Inf) <= 0.1 + pmd_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_3_loadloss.dss") + sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) + @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 + @test norm(sol["solution"]["bus"]["3"]["va"]-[30.7, -90.0, 152.0], Inf) <= 0.1 end end @testset "oltc tests" begin @testset "2w transformer acp opf_oltc yy" begin - file = "../test/data/opendss/ut_trans_2w_yy_oltc.dss" - pmd_data = PMD.parse_file(file; data_model="mathematical") + pmd_data = parse_file("../test/data/opendss/ut_trans_2w_yy_oltc.dss"; data_model="mathematical") # free the taps pmd_data["transformer"]["1"]["fixed"] = zeros(Bool, 3) pmd_data["transformer"]["2"]["fixed"] = zeros(Bool, 3) - pm = PMs.instantiate_model(pmd_data, PMs.ACPPowerModel, PMD.build_mc_opf_oltc, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) - sol = PMs.optimize_model!(pm, optimizer=ipopt_solver) + pm = PM.instantiate_model(pmd_data, ACPPowerModel, build_mc_opf_oltc, ref_extensions=[ref_add_arcs_trans!], multiconductor=true) + sol = PM.optimize_model!(pm, optimizer=ipopt_solver) # check that taps are set as to boost the voltage in the branches as much as possible; # this is trivially optimal if the voltage bounds are not binding # and without significant shunts (both branch and transformer) @test norm(tap(1,pm)-[0.95, 0.95, 0.95], Inf) <= 1E-4 @test norm(tap(2,pm)-[1.05, 1.05, 1.05], Inf) <= 1E-4 # then check whether voltage is what OpenDSS expects for those values - solution = PMD.solution_math2eng(sol["solution"], pmd_data, make_si=false) + solution = solution_math2eng(sol["solution"], pmd_data, make_si=false) @test norm(solution["bus"]["3"]["vm"]-[1.0352, 1.022, 1.0142], Inf) <= 1E-4 @test norm(solution["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end From 2c066d8bc1fe1c4082c8ec1e95463106e1281562 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 21 Apr 2020 17:07:59 -0600 Subject: [PATCH 165/224] ADD: example of usage of eng data model --- examples/engineering_model.ipynb | 1253 ++++++++++++++++++++++++++++++ 1 file changed, 1253 insertions(+) create mode 100644 examples/engineering_model.ipynb diff --git a/examples/engineering_model.ipynb b/examples/engineering_model.ipynb new file mode 100644 index 000000000..42a8a8224 --- /dev/null +++ b/examples/engineering_model.ipynb @@ -0,0 +1,1253 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "MathOptInterface.OptimizerWithAttributes(Ipopt.Optimizer, Pair{MathOptInterface.AbstractOptimizerAttribute,Any}[MathOptInterface.RawParameter(\"tol\") => 1.0e-6, MathOptInterface.RawParameter(\"print_level\") => 0])" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "using PowerModelsDistribution\n", + "using Ipopt\n", + "\n", + "ipopt_solver = optimizer_with_attributes(Ipopt.Optimizer, \"tol\"=>1e-6, \"print_level\"=>0)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Cannot find load object s844.\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Reading Buscoords in \"test2_Buscoords.dss\" on line 67 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b3-1; |2|!=3.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b4; |2|!=3.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Rg,Xg are not fully supported\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor1 like line\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor2 like line\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 18 entries:\n", + " \"xfmrcode\" => Dict{Any,Any}(\"t1\"=>Dict{String,Any}(\"tm_min\"=>Array{Flo…\n", + " \"shunt_reactor\" => Dict{Any,Any}(\"reactor3\"=>Dict{String,Any}(\"source_id\"=>…\n", + " \"bus\" => Dict{String,Any}(\"b3-1\"=>Dict{String,Any}(\"lat\"=>12.8477…\n", + " \"name\" => \"test2\"\n", + " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>100000.…\n", + " \"files\" => Set([\"test2_Linecodes.dss\", \"test2_master.dss\", \"test2_L…\n", + " \"switch\" => Dict{Any,Any}(\"_l4\"=>Dict{String,Any}(\"length\"=>0.001,\"t…\n", + " \"generator\" => Dict{Any,Any}(\"g2\"=>Dict{String,Any}(\"pg\"=>[1.0],\"connec…\n", + " \"loadshape\" => Dict{Any,Any}(\"default\"=>Dict{String,Any}(\"pmult\"=>[0.5,…\n", + " \"shunt_capacitor\" => Dict{Any,Any}(\"c2\"=>Dict{String,Any}(\"source_id\"=>\"capac…\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"v…\n", + " \"line\" => Dict{Any,Any}(\"l7\"=>Dict{String,Any}(\"length\"=>0.0135168…\n", + " \"line_reactor\" => Dict{Any,Any}(\"reactor1\"=>Dict{String,Any}(\"xs\"=>[0.0 0.…\n", + " \"data_model\" => \"engineering\"\n", + " \"xycurve\" => Dict{Any,Any}(\"test_curve3\"=>Dict{String,Any}(\"source_id…\n", + " \"transformer\" => Dict{String,Any}(\"t3a\"=>Dict{String,Any}(\"source_id\"=>\"t…\n", + " \"load\" => Dict{String,Any}(\"ld1\"=>Dict{String,Any}(\"source_id\"=>\"l…\n", + " \"linecode\" => Dict{Any,Any}(\"lc7\"=>Dict{String,Any}(\"b_fr\"=>[0.0082021…" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ex = parse_file(\"../test/data/opendss/test2_master.dss\"; data_model=\"engineering\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 5 entries:\n", + " \"v_var_scalar\" => 1000.0\n", + " \"sbase\" => 100000.0\n", + " \"base_bus\" => \"testsource\"\n", + " \"vbase\" => 66.3953\n", + " \"base_frequency\" => 60.0" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ex[\"settings\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 12 entries:\n", + " \"b3-1\" => Dict{String,Any}(\"lat\"=>12.8477,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", + " \"b8\" => Dict{String,Any}(\"lat\"=>12.6979,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", + " \"b11\" => Dict{String,Any}(\"bus_type\"=>1,\"rg\"=>Float64[],\"grounded\"…\n", + " \"b7\" => Dict{String,Any}(\"lat\"=>12.9187,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", + " \"b9\" => Dict{String,Any}(\"lat\"=>12.757,\"bus_type\"=>1,\"rg\"=>[0.0],…\n", + " \"testsource\" => Dict{String,Any}(\"bus_type\"=>1,\"rg\"=>[0.0],\"grounded\"=>[4…\n", + " \"_b2\" => Dict{String,Any}(\"lat\"=>12.283,\"bus_type\"=>1,\"rg\"=>[0.0],…\n", + " \"b5\" => Dict{String,Any}(\"lat\"=>12.7396,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", + " \"b10\" => Dict{String,Any}(\"lat\"=>12.0982,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", + " \"b6_check-chars\" => Dict{String,Any}(\"lat\"=>12.0391,\"bus_type\"=>1,\"rg\"=>Float…\n", + " \"b4\" => Dict{String,Any}(\"lat\"=>12.371,\"bus_type\"=>1,\"rg\"=>Float6…\n", + " \"b1\" => Dict{String,Any}(\"lat\"=>12.0004,\"bus_type\"=>1,\"rg\"=>[0.0]…" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ex[\"bus\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 6 entries:\n", + " \"b_fr\" => [0.003125]\n", + " \"rs\" => [0.0015]\n", + " \"xs\" => [0.0]\n", + " \"b_to\" => [0.003125]\n", + " \"g_to\" => [0.0]\n", + " \"g_fr\" => [0.0]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ex[\"linecode\"][\"lc1\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 6 entries:\n", + " \"b_fr\" => [0.0082021 -0.00410105; -0.00410105 0.0082021]\n", + " \"rs\" => [0.00164042 0.000410105; 0.000410105 0.00164042]\n", + " \"xs\" => [0.00082021 0.0; 0.0 0.0]\n", + " \"b_to\" => [0.0082021 -0.00410105; -0.00410105 0.0082021]\n", + " \"g_to\" => [0.0 0.0; 0.0 0.0]\n", + " \"g_fr\" => [0.0 0.0; 0.0 0.0]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ex[\"linecode\"][\"lc7\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 10 entries:\n", + " \"source_id\" => \"load.ld1\"\n", + " \"name\" => \"ld1\"\n", + " \"vnom\" => 0.208\n", + " \"qd_nom\" => [0.974926]\n", + " \"status\" => 1\n", + " \"model\" => \"constant_power\"\n", + " \"connections\" => [1, 4]\n", + " \"pd_nom\" => [3.89]\n", + " \"bus\" => \"b7\"\n", + " \"configuration\" => \"wye\"" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ex[\"load\"][\"ld1\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 8 entries:\n", + " \"length\" => 0.001\n", + " \"t_connections\" => [1, 2, 3]\n", + " \"f_bus\" => \"b8\"\n", + " \"source_id\" => \"line._l4\"\n", + " \"status\" => 1\n", + " \"t_bus\" => \"b10\"\n", + " \"linecode\" => \"300\"\n", + " \"f_connections\" => [1, 2, 3]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ex[\"switch\"][\"_l4\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 8 entries:\n", + " \"source_id\" => \"vsource.source\"\n", + " \"rs\" => [0.001 0.0 0.0; 0.0 0.001 0.0; 0.0 0.0 0.001]\n", + " \"va\" => [30.0, -90.0, 150.0]\n", + " \"status\" => 1\n", + " \"connections\" => [1, 2, 3]\n", + " \"vm\" => [73.0348, 73.0348, 73.0348]\n", + " \"xs\" => [0.01 0.0 0.0; 0.0 0.01 0.0; 0.0 0.0 0.01]\n", + " \"bus\" => \"testsource\"" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ex[\"voltage_source\"][\"source\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Cannot find load object s844.\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Reading Buscoords in \"test2_Buscoords.dss\" on line 67 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b3-1; |2|!=3.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b4; |2|!=3.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Rg,Xg are not fully supported\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor1 like line\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor2 like line\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: branch found with non-positive tap value of 0.0, setting a tap to 1.0 on conductor 1\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: branch found with non-positive tap value of 0.0, setting a tap to 1.0 on conductor 3\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: angmin and angmax values are 0, widening these values on branch 7, conductor 1 to +/- 60.0 deg.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: angmin and angmax values are 0, widening these values on branch 7, conductor 3 to +/- 60.0 deg.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 4 cost function with order 3 to a function of order 2: [100.0, 0.0]\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [100.0, 0.0]\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 2 cost function with order 3 to a function of order 2: [100.0, 0.0]\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 3 cost function with order 3 to a function of order 2: [100.0, 0.0]\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 19 entries:\n", + " \"bus\" => Dict{String,Any}(\"24\"=>Dict{String,Any}(\"grounded\"=>Bool[0, …\n", + " \"name\" => \"test2\"\n", + " \"dcline\" => Dict{String,Any}()\n", + " \"map\" => Dict(18=>Dict(:from=>\"l5\",:unmap_function=>:_map_math2eng_li…\n", + " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>100000.0,\"b…\n", + " \"gen\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"pg\"=>[0.0, 0.0, 0.0]…\n", + " \"branch\" => Dict{String,Any}(\"24\"=>Dict{String,Any}(\"source_id\"=>\"_virtu…\n", + " \"storage\" => Dict{String,Any}()\n", + " \"switch\" => Dict{String,Any}()\n", + " \"basekv\" => 66.3953\n", + " \"baseMVA\" => 100.0\n", + " \"sbase\" => 100000.0\n", + " \"conductors\" => 3\n", + " \"per_unit\" => true\n", + " \"data_model\" => \"mathematical\"\n", + " \"shunt\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"source_id\"=>\"reactor…\n", + " \"transformer\" => Dict{String,Any}(\"8\"=>Dict{String,Any}(\"polarity\"=>1,\"tm_min…\n", + " \"bus_lookup\" => Dict{Any,Int64}(\"b3-1\"=>1,\"b8\"=>2,\"b11\"=>3,\"b7\"=>4,\"b9\"=>5,\"…\n", + " \"load\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"model\"=>\"constant_cu…" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_ex = parse_file(\"../test/data/opendss/test2_master.dss\"; data_model=\"mathematical\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{Int64,Dict{Symbol,Any}} with 42 entries:\n", + " 18 => Dict{Symbol,Any}(:from=>\"l5\",:unmap_function=>:_map_math2eng_line!,:to=…\n", + " 30 => Dict{Symbol,Any}(:from=>\"ld1\",:unmap_function=>:_map_math2eng_load!,:to…\n", + " 33 => Dict{Symbol,Any}(:from=>\"ld2\",:unmap_function=>:_map_math2eng_load!,:to…\n", + " 32 => Dict{Symbol,Any}(:from=>\"ld3\",:unmap_function=>:_map_math2eng_load!,:to…\n", + " 41 => Dict{Symbol,Any}(:from=>\"g3\",:unmap_function=>:_map_math2eng_generator!…\n", + " 2 => Dict{Symbol,Any}(:from=>\"b3-1\",:unmap_function=>:_map_math2eng_bus!,:to…\n", + " 40 => Dict{Symbol,Any}(:from=>\"g1\",:unmap_function=>:_map_math2eng_generator!…\n", + " 16 => Dict{Symbol,Any}(:from=>\"l8\",:unmap_function=>:_map_math2eng_line!,:to=…\n", + " 11 => Dict{Symbol,Any}(:from=>\"b6_check-chars\",:unmap_function=>:_map_math2en…\n", + " 21 => Dict{Symbol,Any}(:from=>\"l1\",:unmap_function=>:_map_math2eng_line!,:to=…\n", + " 39 => Dict{Symbol,Any}(:from=>\"g2\",:unmap_function=>:_map_math2eng_generator!…\n", + " 7 => Dict{Symbol,Any}(:from=>\"testsource\",:unmap_function=>:_map_math2eng_bu…\n", + " 9 => Dict{Symbol,Any}(:from=>\"b5\",:unmap_function=>:_map_math2eng_bus!,:to=>…\n", + " 25 => Dict{Symbol,Any}(:from=>\"t2\",:unmap_function=>:_map_math2eng_transforme…\n", + " 10 => Dict{Symbol,Any}(:from=>\"b10\",:unmap_function=>:_map_math2eng_bus!,:to=…\n", + " 26 => Dict{Symbol,Any}(:from=>\"t1\",:unmap_function=>:_map_math2eng_transforme…\n", + " 29 => Dict{Symbol,Any}(:from=>\"reactor2\",:unmap_function=>:_map_math2eng_line…\n", + " 34 => Dict{Symbol,Any}(:from=>\"c2\",:unmap_function=>:_map_math2eng_shunt_capa…\n", + " 35 => Dict{Symbol,Any}(:from=>\"c1\",:unmap_function=>:_map_math2eng_shunt_capa…\n", + " 19 => Dict{Symbol,Any}(:from=>\"l3\",:unmap_function=>:_map_math2eng_line!,:to=…\n", + " 17 => Dict{Symbol,Any}(:from=>\"l2\",:unmap_function=>:_map_math2eng_line!,:to=…\n", + " 42 => Dict{Symbol,Any}(:from=>\"source\",:unmap_function=>:_map_math2eng_voltag…\n", + " 8 => Dict{Symbol,Any}(:from=>\"_b2\",:unmap_function=>:_map_math2eng_bus!,:to=…\n", + " 22 => Dict{Symbol,Any}(:from=>\"_l4\",:unmap_function=>:_map_math2eng_switch!,:…\n", + " 6 => Dict{Symbol,Any}(:from=>\"b9\",:unmap_function=>:_map_math2eng_bus!,:to=>…\n", + " ⋮ => ⋮" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_ex[\"map\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{Symbol,Any} with 3 entries:\n", + " :from => \"source\"\n", + " :unmap_function => :_map_math2eng_voltage_source!\n", + " :to => [\"gen.4\", \"bus.33\", \"branch.27\"]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_ex[\"map\"][42]" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Cannot find load object s844.\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Reading Buscoords in \"test2_Buscoords.dss\" on line 67 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 16 entries:\n", + " \"xfmrcode\" => Dict{String,Any}(\"t1\"=>Dict{String,Any}(\"windings\"=>2,\"name\"…\n", + " \"reactor\" => Dict{String,Any}(\"reactor1\"=>Dict{String,Any}(\"name\"=>\"react…\n", + " \"circuit\" => \"test2\"\n", + " \"generator\" => Dict{String,Any}(\"g2\"=>Dict{String,Any}(\"name\"=>\"g2\",\"bus1\"=…\n", + " \"vsource\" => Dict{String,Any}(\"source\"=>Dict{String,Any}(\"name\"=>\"source\"…\n", + " \"loadshape\" => Dict{String,Any}(\"default\"=>Dict{String,Any}(\"pmult\"=>[0.5, …\n", + " \"line\" => Dict{String,Any}(\"_l4\"=>Dict{String,Any}(\"repair\"=>0.0,\"bus2…\n", + " \"buscoords\" => Dict{String,Any}(\"b10\"=>Dict{String,Any}(\"x\"=>49.2303,\"y\"=>1…\n", + " \"data_model\" => \"dss\"\n", + " \"options\" => Dict{String,Any}(\"voltagebases\"=>[115.0, 12.47, 0.48, 0.208])\n", + " \"xycurve\" => Dict{String,Any}(\"test_curve3\"=>Dict{String,Any}(\"name\"=>\"te…\n", + " \"transformer\" => Dict{String,Any}(\"t3a\"=>Dict{String,Any}(\"xfmrcode\"=>\"t1\",\"w…\n", + " \"filename\" => Set([\"test2_Linecodes.dss\", \"test2_master.dss\", \"test2_Loads…\n", + " \"load\" => Dict{String,Any}(\"ld1\"=>Dict{String,Any}(\"model\"=>1,\"kv\"=>0.…\n", + " \"linecode\" => Dict{String,Any}(\"lc7\"=>Dict{String,Any}(\"nphases\"=>2,\"name\"…\n", + " \"capacitor\" => Dict{String,Any}(\"c2\"=>Dict{String,Any}(\"name\"=>\"c2\",\"bus1\"=…" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dss = parse_dss(\"../test/data/opendss/test2_master.dss\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 7 entries:\n", + " \"name\" => \"l7\"\n", + " \"bus1\" => \"_b2\"\n", + " \"test_param\" => 100.0\n", + " \"prop_order\" => [\"name\", \"like\", \"bus1\", \"bus2\", \"linecode\", \"test_param\"]\n", + " \"linecode\" => \"lc10\"\n", + " \"like\" => \"l2\"\n", + " \"bus2\" => \"b10\"" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dss[\"line\"][\"l7\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Cannot find load object s844.\u001b[39m\n", + "\u001b[32m[info | PowerModels]: Reading Buscoords in \"test2_Buscoords.dss\" on line 67 in \"test2_master.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b3-1; |2|!=3.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b4; |2|!=3.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Rg,Xg are not fully supported\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor1 like line\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor2 like line\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 6 entries:\n", + " \"length\" => 0.0321756\n", + " \"name\" => \"l1-2\"\n", + " \"bus1\" => \"b3-1.2\"\n", + " \"units\" => \"km\"\n", + " \"linecode\" => \"lc1\"\n", + " \"bus2\" => \"b4.2\"" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_import_all = parse_file(\"../test/data/opendss/test2_master.dss\"; data_model=\"engineering\", import_all=true)\n", + "\n", + "eng_import_all[\"line\"][\"l1-2\"][\"dss\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_2w_yy_bank.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 53 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 56 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 10 entries:\n", + " \"xfmrcode\" => Dict{Any,Any}(\"tx\"=>Dict{String,Any}(\"tm_min\"=>Array{Floa…\n", + " \"name\" => \"ut_trans\"\n", + " \"line\" => Dict{Any,Any}(\"line2\"=>Dict{String,Any}(\"xs\"=>[0.3349 0.0…\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"vs…\n", + " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>1000.0,\"…\n", + " \"files\" => Set([\"ut_trans_2w_yy_bank.dss\"])\n", + " \"transformer\" => Dict{String,Any}(\"tx1\"=>Dict{String,Any}(\"polarity\"=>[1, …\n", + " \"load\" => Dict{String,Any}(\"load3\"=>Dict{String,Any}(\"source_id\"=>\"…\n", + " \"bus\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"bus_type\"=>1,\"rg\"…\n", + " \"data_model\" => \"engineering\"" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng = parse_file(\"../test/data/opendss/ut_trans_2w_yy_bank.dss\"; data_model=\"engineering\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [1.0, 0.0]\u001b[39m\n", + "\n", + "******************************************************************************\n", + "This program contains Ipopt, a library for large-scale nonlinear optimization.\n", + " Ipopt is released as open source code under the Eclipse Public License (EPL).\n", + " For more information visit http://projects.coin-or.org/Ipopt\n", + "******************************************************************************\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 10 entries:\n", + " \"solve_time\" => 4.33379\n", + " \"optimizer\" => \"Ipopt\"\n", + " \"termination_status\" => LOCALLY_SOLVED\n", + " \"dual_status\" => FEASIBLE_POINT\n", + " \"primal_status\" => FEASIBLE_POINT\n", + " \"objective\" => 0.0\n", + " \"solution\" => Dict{String,Any}(\"voltage_source\"=>Dict{Any,Any}(\"sou…\n", + " \"data\" => Dict{String,Any}(\"name\"=>\"ut_trans\")\n", + " \"machine\" => Dict(\"cpu\"=>\"Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GH…\n", + " \"objective_lb\" => -Inf" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_eng = run_mc_pf(eng, ACPPowerModel, ipopt_solver; make_si=false)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [1.0, 0.0]\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 19 entries:\n", + " \"bus\" => Dict{String,Any}(\"8\"=>Dict{String,Any}(\"grounded\"=>Bool[0, 0…\n", + " \"name\" => \"ut_trans\"\n", + " \"dcline\" => Dict{String,Any}()\n", + " \"map\" => Dict(2=>Dict(:from=>\"1\",:unmap_function=>:_map_math2eng_bus!…\n", + " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>1000.0,\"bas…\n", + " \"gen\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"pg\"=>[0.0, 0.0, 0.0]…\n", + " \"branch\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"source_id\"=>\"_virtua…\n", + " \"storage\" => Dict{String,Any}()\n", + " \"switch\" => Dict{String,Any}()\n", + " \"basekv\" => 6.35085\n", + " \"baseMVA\" => 1.0\n", + " \"sbase\" => 1000.0\n", + " \"conductors\" => 3\n", + " \"per_unit\" => true\n", + " \"data_model\" => \"mathematical\"\n", + " \"shunt\" => Dict{String,Any}()\n", + " \"transformer\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"polarity\"=>1,\"tm_min…\n", + " \"bus_lookup\" => Dict{Any,Int64}(\"1\"=>1,\"sourcebus\"=>2,\"2\"=>3,\"3\"=>4)\n", + " \"load\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"model\"=>\"constant_po…" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math = transform_data_model(eng)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_2w_yy_bank.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 53 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 56 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [1.0, 0.0]\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "true" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_from_parse = parse_file(\"../test/data/opendss/ut_trans_2w_yy_bank.dss\"; data_model=\"mathematical\")\n", + "\n", + "math_from_parse == math" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 10 entries:\n", + " \"solve_time\" => 0.0145578\n", + " \"optimizer\" => \"Ipopt\"\n", + " \"termination_status\" => LOCALLY_SOLVED\n", + " \"dual_status\" => FEASIBLE_POINT\n", + " \"primal_status\" => FEASIBLE_POINT\n", + " \"objective\" => 0.0\n", + " \"solution\" => Dict{String,Any}(\"baseMVA\"=>1.0,\"branch\"=>Dict{String…\n", + " \"data\" => Dict{String,Any}(\"name\"=>\"ut_trans\")\n", + " \"machine\" => Dict(\"cpu\"=>\"Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GH…\n", + " \"objective_lb\" => -Inf" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_math = run_mc_pf(math, ACPPowerModel, ipopt_solver)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{Any,Any} with 4 entries:\n", + " \"sourcebus\" => Dict{String,Any}(\"va\"=>[-8.75226e-8, -120.0, 120.0],\"vm\"=>[6.3…\n", + " \"1\" => Dict{String,Any}(\"va\"=>[0.765392, -119.423, 120.857],\"vm\"=>[6.…\n", + " \"2\" => Dict{String,Any}(\"va\"=>[-0.249838, -120.484, 119.472],\"vm\"=>[1…\n", + " \"3\" => Dict{String,Any}(\"va\"=>[-0.11431, -120.435, 119.578],\"vm\"=>[1.…" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_eng[\"solution\"][\"bus\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 9 entries:\n", + " \"8\" => Dict{String,Any}(\"va\"=>[0.0133586, -2.08432, 2.10936],\"vm\"=>[0.92195, …\n", + " \"4\" => Dict{String,Any}(\"va\"=>[-0.00199508, -2.10198, 2.08703],\"vm\"=>[0.82944…\n", + " \"1\" => Dict{String,Any}(\"va\"=>[0.0133586, -2.08432, 2.10936],\"vm\"=>[0.968047,…\n", + " \"5\" => Dict{String,Any}(\"va\"=>[-0.0127852, -2.11267, 2.07185],\"vm\"=>[0.897401…\n", + " \"2\" => Dict{String,Any}(\"va\"=>[-1.52756e-9, -2.0944, 2.0944],\"vm\"=>[1.0, 1.0,…\n", + " \"7\" => Dict{String,Any}(\"va\"=>[0.0175895, -2.0794, 2.11601],\"vm\"=>[0.916491, …\n", + " \"6\" => Dict{String,Any}(\"va\"=>[-0.0043605, -2.10284, 2.08518],\"vm\"=>[0.886299…\n", + " \"9\" => Dict{String,Any}(\"va\"=>[0.0, -2.0944, 2.0944],\"vm\"=>[1.0, 1.0, 1.0])\n", + " \"3\" => Dict{String,Any}(\"va\"=>[-0.0043605, -2.10284, 2.08518],\"vm\"=>[0.841984…" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_math[\"solution\"][\"bus\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 4 entries:\n", + " \"qg_bus\" => [138.242, 162.053, 182.485]\n", + " \"qg\" => [138.242, 162.053, 182.485]\n", + " \"pg\" => [133.06, 155.561, 179.012]\n", + " \"pg_bus\" => [133.06, 155.561, 179.012]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_eng[\"solution\"][\"voltage_source\"][\"source\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 4 entries:\n", + " \"qg_bus\" => [0.138242, 0.162053, 0.182485]\n", + " \"qg\" => [0.138242, 0.162053, 0.182485]\n", + " \"pg\" => [0.13306, 0.155561, 0.179012]\n", + " \"pg_bus\" => [0.13306, 0.155561, 0.179012]" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_math[\"solution\"][\"gen\"][\"1\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_2w_yy_bank.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 53 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 56 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 10 entries:\n", + " \"xfmrcode\" => Dict{Any,Any}(\"tx\"=>Dict{String,Any}(\"tm_min\"=>Array{Floa…\n", + " \"name\" => \"ut_trans\"\n", + " \"line\" => Dict{Any,Any}(\"line2\"=>Dict{String,Any}(\"xs\"=>[0.3349 0.0…\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"vs…\n", + " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>1000.0,\"…\n", + " \"files\" => Set([\"ut_trans_2w_yy_bank.dss\"])\n", + " \"transformer\" => Dict{String,Any}(\"tx1\"=>Dict{String,Any}(\"polarity\"=>[1, …\n", + " \"load\" => Dict{String,Any}(\"load3\"=>Dict{String,Any}(\"source_id\"=>\"…\n", + " \"bus\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"bus_type\"=>1,\"rg\"…\n", + " \"data_model\" => \"engineering\"" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# make_lossless!(eng)\n", + "\n", + "eng_lossless = parse_file(\"../test/data/opendss/ut_trans_2w_yy_bank.dss\"; data_model=\"engineering\", transformations=[make_lossless!])" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 8 entries:\n", + " \"source_id\" => \"vsource.source\"\n", + " \"rs\" => [1.69763e-7 1.57319e-8 1.57319e-8; 1.57319e-8 1.69763e-7 1.5…\n", + " \"va\" => [0.0, -120.0, 120.0]\n", + " \"status\" => 1\n", + " \"connections\" => [1, 2, 3]\n", + " \"vm\" => [6.35085, 6.35085, 6.35085]\n", + " \"xs\" => [6.11975e-7 -4.14779e-9 -4.14779e-9; -4.14779e-9 6.11975e-7 …\n", + " \"bus\" => \"sourcebus\"" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng[\"voltage_source\"][\"source\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 6 entries:\n", + " \"source_id\" => \"vsource.source\"\n", + " \"va\" => [0.0, -120.0, 120.0]\n", + " \"status\" => 1\n", + " \"connections\" => [1, 2, 3]\n", + " \"vm\" => [6.35085, 6.35085, 6.35085]\n", + " \"bus\" => \"sourcebus\"" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_lossless[\"voltage_source\"][\"source\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_3w_dyy_1.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 36 in \"ut_trans_3w_dyy_1.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 39 in \"ut_trans_3w_dyy_1.dss\" is not supported, skipping.\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 17 entries:\n", + " \"polarity\" => [1, 1, 1]\n", + " \"connections\" => Array{Int64,1}[[1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4]]\n", + " \"tm_min\" => Array{Float64,1}[[0.9, 0.9, 0.9], [0.9, 0.9, 0.9], [0.9, 0…\n", + " \"tm_step\" => Array{Float64,1}[[0.03125, 0.03125, 0.03125], [0.03125, 0.…\n", + " \"bus\" => [\"1\", \"2\", \"3\"]\n", + " \"configuration\" => [\"delta\", \"wye\", \"wye\"]\n", + " \"noloadloss\" => 0.05\n", + " \"xsc\" => Any[0.05, 0.04, 0.03]\n", + " \"source_id\" => \"transformer.tx1\"\n", + " \"snom\" => [500.0, 500.0, 500.0]\n", + " \"tm_fix\" => Array{Int64,1}[[1, 1, 1], [1, 1, 1], [1, 1, 1]]\n", + " \"tm\" => Array{Float64,1}[[1.01, 1.01, 1.01], [1.02, 1.02, 1.02], […\n", + " \"tm_max\" => Array{Float64,1}[[1.1, 1.1, 1.1], [1.1, 1.1, 1.1], [1.1, 1…\n", + " \"rs\" => [0.01, 0.02, 0.03]\n", + " \"fixed\" => Array{Bool,1}[[1, 1, 1], [1, 1, 1], [1, 1, 1]]\n", + " \"imag\" => 0.11\n", + " \"vnom\" => [11.0, 4.0, 0.4]" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_tr = parse_file(\"../test/data/opendss/ut_trans_3w_dyy_1.dss\"; data_model=\"engineering\")\n", + "\n", + "eng_tr[\"transformer\"][\"tx1\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_3w_dyy_1.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 36 in \"ut_trans_3w_dyy_1.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 39 in \"ut_trans_3w_dyy_1.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.1, 0.0]\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 3 entries:\n", + " \"1\" => Dict{String,Any}(\"polarity\"=>1,\"tm_min\"=>[0.9, 0.9, 0.9],\"t_vbase\"=>0.…\n", + " \"2\" => Dict{String,Any}(\"polarity\"=>1,\"tm_min\"=>[0.9, 0.9, 0.9],\"t_vbase\"=>0.…\n", + " \"3\" => Dict{String,Any}(\"polarity\"=>1,\"tm_min\"=>[0.9, 0.9, 0.9],\"t_vbase\"=>0.…" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_tr = parse_file(\"../test/data/opendss/ut_trans_3w_dyy_1.dss\"; data_model=\"mathematical\")\n", + "\n", + "math_tr[\"transformer\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 17 entries:\n", + " \"polarity\" => 1\n", + " \"tm_min\" => [0.9, 0.9, 0.9]\n", + " \"t_vbase\" => 0.57735\n", + " \"tm_step\" => [0.03125, 0.03125, 0.03125]\n", + " \"f_connections\" => [1, 2, 3]\n", + " \"configuration\" => \"delta\"\n", + " \"name\" => \"_virtual_transformer.tx1.1\"\n", + " \"tm_nom\" => 1.73205\n", + " \"source_id\" => \"_virtual_transformer.transformer.tx1.1\"\n", + " \"t_connections\" => [1, 2, 3, 4]\n", + " \"f_bus\" => 1\n", + " \"tm\" => [1.01, 1.01, 1.01]\n", + " \"t_bus\" => 10\n", + " \"tm_max\" => [1.1, 1.1, 1.1]\n", + " \"index\" => 1\n", + " \"fixed\" => Bool[1, 1, 1]\n", + " \"f_vbase\" => 6.35085" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_tr[\"transformer\"][\"1\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 17 entries:\n", + " \"polarity\" => 1\n", + " \"tm_min\" => [0.9, 0.9, 0.9]\n", + " \"t_vbase\" => 0.57735\n", + " \"tm_step\" => [0.03125, 0.03125, 0.03125]\n", + " \"f_connections\" => [1, 2, 3, 4]\n", + " \"configuration\" => \"wye\"\n", + " \"name\" => \"_virtual_transformer.tx1.2\"\n", + " \"tm_nom\" => 1.0\n", + " \"source_id\" => \"_virtual_transformer.transformer.tx1.2\"\n", + " \"t_connections\" => [1, 2, 3, 4]\n", + " \"f_bus\" => 3\n", + " \"tm\" => [1.02, 1.02, 1.02]\n", + " \"t_bus\" => 6\n", + " \"tm_max\" => [1.1, 1.1, 1.1]\n", + " \"index\" => 2\n", + " \"fixed\" => Bool[1, 1, 1]\n", + " \"f_vbase\" => 2.3094" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_tr[\"transformer\"][\"2\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 17 entries:\n", + " \"polarity\" => 1\n", + " \"tm_min\" => [0.9, 0.9, 0.9]\n", + " \"t_vbase\" => 0.57735\n", + " \"tm_step\" => [0.03125, 0.03125, 0.03125]\n", + " \"f_connections\" => [1, 2, 3, 4]\n", + " \"configuration\" => \"wye\"\n", + " \"name\" => \"_virtual_transformer.tx1.3\"\n", + " \"tm_nom\" => 1.0\n", + " \"source_id\" => \"_virtual_transformer.transformer.tx1.3\"\n", + " \"t_connections\" => [1, 2, 3, 4]\n", + " \"f_bus\" => 4\n", + " \"tm\" => [1.03, 1.03, 1.03]\n", + " \"t_bus\" => 7\n", + " \"tm_max\" => [1.1, 1.1, 1.1]\n", + " \"index\" => 3\n", + " \"fixed\" => Bool[1, 1, 1]\n", + " \"f_vbase\" => 0.23094" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_tr[\"transformer\"][\"3\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [1.0, 0.0]\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 10 entries:\n", + " \"solve_time\" => 0.010365\n", + " \"optimizer\" => \"Ipopt\"\n", + " \"termination_status\" => LOCALLY_SOLVED\n", + " \"dual_status\" => FEASIBLE_POINT\n", + " \"primal_status\" => FEASIBLE_POINT\n", + " \"objective\" => 0.0\n", + " \"solution\" => Dict{String,Any}(\"voltage_source\"=>Dict{Any,Any}(\"sou…\n", + " \"data\" => Dict{String,Any}(\"name\"=>\"ut_trans\")\n", + " \"machine\" => Dict(\"cpu\"=>\"Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GH…\n", + " \"objective_lb\" => -Inf" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_eng_pu = run_mc_pf(eng, ACPPowerModel, ipopt_solver; make_si=false)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 2 entries:\n", + " \"va\" => [-8.75226e-8, -120.0, 120.0]\n", + " \"vm\" => [6.35085, 6.35085, 6.35085]" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_eng[\"solution\"][\"bus\"][\"sourcebus\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 2 entries:\n", + " \"va\" => [-8.75226e-8, -120.0, 120.0]\n", + " \"vm\" => [1.0, 1.0, 1.0]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_eng_pu[\"solution\"][\"bus\"][\"sourcebus\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.3.1", + "language": "julia", + "name": "julia-1.3" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.3.1" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 4f8b68d46d48c5f0d0046967aab5568fcc49e756 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 23 Apr 2020 14:38:10 -0600 Subject: [PATCH 166/224] UPD: PowerModels v0.16 --- src/core/constraint.jl | 4 +-- src/core/ref.jl | 32 ++++++++++++++-------- src/form/acp.jl | 8 +++--- src/form/acr.jl | 6 ++--- src/form/apo.jl | 6 ++--- src/form/bf_mx.jl | 2 +- src/form/bf_mx_lin.jl | 2 +- src/form/dcp.jl | 2 +- src/form/shared.jl | 6 ++--- src/prob/common.jl | 10 +++---- test/delta_gens.jl | 10 +++---- test/transformer.jl | 61 +++++++++++++++++++++++------------------- 12 files changed, 82 insertions(+), 67 deletions(-) diff --git a/src/core/constraint.jl b/src/core/constraint.jl index 0f64b6685..c5b7e7cd2 100644 --- a/src/core/constraint.jl +++ b/src/core/constraint.jl @@ -10,7 +10,7 @@ function constraint_mc_thermal_limit_from(pm::_PM.AbstractPowerModel, n::Int, f_ mu_sm_fr = JuMP.@constraint(pm.model, p_fr.^2 + q_fr.^2 .<= rate_a.^2) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, n, :branch, f_idx[1])[:mu_sm_fr] = mu_sm_fr end end @@ -22,7 +22,7 @@ function constraint_mc_thermal_limit_to(pm::_PM.AbstractPowerModel, n::Int, t_id mu_sm_to = JuMP.@constraint(pm.model, p_to.^2 + q_to.^2 .<= rate_a.^2) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, n, :branch, t_idx[1])[:mu_sm_to] = mu_sm_to end end diff --git a/src/core/ref.jl b/src/core/ref.jl index 2b545a574..70946f84d 100644 --- a/src/core/ref.jl +++ b/src/core/ref.jl @@ -42,20 +42,30 @@ end "Adds arcs for PMD transformers; for dclines and branches this is done in PMs" -function ref_add_arcs_trans!(pm::_PM.AbstractPowerModel) - for nw in nw_ids(pm) - if !haskey(ref(pm, nw), :transformer) +function ref_add_arcs_transformer!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) + if _IM.ismultinetwork(data) + nws_data = data["nw"] + else + nws_data = Dict("0" => data) + end + + for (n, nw_data) in nws_data + nw_id = parse(Int, n) + nw_ref = ref[:nw][nw_id] + + if !haskey(nw_ref, :transformer) # this might happen when parsing data from matlab format # the OpenDSS parser always inserts a trans dict - ref(pm, nw)[:transformer] = Dict{Int, Any}() + nw_ref[:transformer] = Dict{Int, Any}() end - # dirty fix add arcs_from/to_trans and bus_arcs_trans - pm.ref[:nw][nw][:arcs_from_trans] = [(i, trans["f_bus"], trans["t_bus"]) for (i,trans) in ref(pm, nw, :transformer)] - pm.ref[:nw][nw][:arcs_to_trans] = [(i, trans["t_bus"], trans["f_bus"]) for (i,trans) in ref(pm, nw, :transformer)] - pm.ref[:nw][nw][:arcs_trans] = [pm.ref[:nw][nw][:arcs_from_trans]..., pm.ref[:nw][nw][:arcs_to_trans]...] - pm.ref[:nw][nw][:bus_arcs_trans] = Dict{Int64, Array{Any, 1}}() - for i in ids(pm, nw, :bus) - pm.ref[:nw][nw][:bus_arcs_trans][i] = [e for e in pm.ref[:nw][nw][:arcs_trans] if e[2]==i] + + nw_ref[:arcs_from_trans] = [(i, trans["f_bus"], trans["t_bus"]) for (i,trans) in nw_ref[:transformer]] + nw_ref[:arcs_to_trans] = [(i, trans["t_bus"], trans["f_bus"]) for (i,trans) in nw_ref[:transformer]] + nw_ref[:arcs_trans] = [nw_ref[:arcs_from_trans]..., nw_ref[:arcs_to_trans]...] + nw_ref[:bus_arcs_trans] = Dict{Int64, Array{Any, 1}}() + + for (i,bus) in nw_ref[:bus] + nw_ref[:bus_arcs_trans][i] = [e for e in nw_ref[:arcs_trans] if e[2]==i] end end end diff --git a/src/form/acp.jl b/src/form/acp.jl index 18e4c0bca..0760cbde7 100644 --- a/src/form/acp.jl +++ b/src/form/acp.jl @@ -114,7 +114,7 @@ function constraint_mc_power_balance_slack(pm::_PM.AbstractACPModel, nw::Int, i: end - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end @@ -186,7 +186,7 @@ function constraint_mc_power_balance_shed(pm::_PM.AbstractACPModel, nw::Int, i:: end - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end @@ -254,7 +254,7 @@ function constraint_mc_power_balance(pm::_PM.AbstractACPModel, nw::Int, i::Int, push!(cstr_q, cq) end - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end @@ -323,7 +323,7 @@ function constraint_mc_power_balance_load(pm::_PM.AbstractACPModel, nw::Int, i:: push!(cstr_q, cq) end - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end diff --git a/src/form/acr.jl b/src/form/acr.jl index 5aec5d625..0db741f7b 100644 --- a/src/form/acr.jl +++ b/src/form/acr.jl @@ -144,7 +144,7 @@ function constraint_mc_power_balance_slack(pm::_PM.AbstractACRModel, nw::Int, i: push!(cstr_q, cq) end - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end @@ -196,7 +196,7 @@ function constraint_mc_power_balance(pm::_PM.AbstractACRModel, nw::Int, i::Int, - (-vr.*(Gt*vi+Bt*vr) + vi.*(Gt*vr-Bt*vi)) ) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end @@ -262,7 +262,7 @@ function constraint_mc_power_balance_load(pm::_PM.AbstractACRModel, nw::Int, i:: push!(cstr_q, cq) end - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end diff --git a/src/form/apo.jl b/src/form/apo.jl index c7dffaa35..549108a73 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -97,7 +97,7 @@ function constraint_mc_power_balance_load(pm::_PM.AbstractActivePowerModel, nw:: cnds = conductor_ids(pm, nw) ncnds = length(cnds) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = [NaN for i in 1:ncnds] end @@ -188,7 +188,7 @@ function constraint_mc_thermal_limit_from(pm::_PM.AbstractActivePowerModel, n::I end end - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, n, :branch, f_idx[1])[:mu_sm_fr] = mu_sm_fr end end @@ -212,7 +212,7 @@ function constraint_mc_thermal_limit_to(pm::_PM.AbstractActivePowerModel, n::Int end end - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, n, :branch, t_idx[1])[:mu_sm_to] = mu_sm_to end end diff --git a/src/form/bf_mx.jl b/src/form/bf_mx.jl index 00e701317..ad1c4094b 100644 --- a/src/form/bf_mx.jl +++ b/src/form/bf_mx.jl @@ -810,7 +810,7 @@ function constraint_mc_power_balance(pm::KCLMXModels, n::Int, i::Int, bus_arcs, # con(pm, n, :kcl_Q)[i] = cq = JuMP.@constraint(pm.model, sum(Qg[g] for g in bus_gens) .== sum(Q[a] for a in bus_arcs) + sum(Qd[d] for d in bus_loads) + (-Wr*Bt'+Wi*Gt')) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, n, :bus, i)[:lam_kcl_r] = cp sol(pm, n, :bus, i)[:lam_kcl_i] = cq end diff --git a/src/form/bf_mx_lin.jl b/src/form/bf_mx_lin.jl index ebe34d64d..6ae3ad20b 100644 --- a/src/form/bf_mx_lin.jl +++ b/src/form/bf_mx_lin.jl @@ -115,7 +115,7 @@ function constraint_mc_power_balance(pm::LPUBFDiagModel, nw::Int, i, bus_arcs, b + sum(bs.*w for bs in values(bus_bs)) ) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end diff --git a/src/form/dcp.jl b/src/form/dcp.jl index 0dae6a479..2686edba0 100644 --- a/src/form/dcp.jl +++ b/src/form/dcp.jl @@ -59,7 +59,7 @@ function constraint_mc_power_balance_shed(pm::_PM.AbstractDCPModel, nw::Int, i:: - sum(diag(gs)*1.0^2 .*z_shunt[n] for (n,gs) in bus_gs) ) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cp end end diff --git a/src/form/shared.jl b/src/form/shared.jl index 3697a6f22..f5ecf533d 100644 --- a/src/form/shared.jl +++ b/src/form/shared.jl @@ -49,7 +49,7 @@ function constraint_mc_power_balance_slack(pm::_PM.AbstractWModels, nw::Int, i, + q_slack ) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end @@ -125,7 +125,7 @@ function constraint_mc_power_balance_shed(pm::_PM.AbstractWModels, nw::Int, i, b - sum(z_shunt[n].*(-w.*diag(Bt')) for (n,Gs,Bs) in bus_GsBs) ) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end @@ -178,7 +178,7 @@ function constraint_mc_power_balance_load(pm::_PM.AbstractWModels, nw::Int, i, b - diag(-Wr*Bt'+Wi*Gt') ) - if _PM.report_duals(pm) + if _IM.report_duals(pm) sol(pm, nw, :bus, i)[:lam_kcl_r] = cstr_p sol(pm, nw, :bus, i)[:lam_kcl_i] = cstr_q end diff --git a/src/prob/common.jl b/src/prob/common.jl index cd92a931e..ace3a0fba 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -1,13 +1,13 @@ "alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" function run_mc_model(data::Dict{String,<:Any}, model_type, solver, build_mc; ref_extensions=[], make_si=!get(data, "per_unit", false), kwargs...) - if get(data, "data_model", "mathematical") == "engineering" - data_math = transform_data_model(data, make_pu=true) + if get(data, "data_model", MATHEMATICAL) == ENGINEERING + data_math = transform_data_model(data) - result = _PM.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) + result = _PM.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_transformer!, ref_extensions...], multiconductor=true, kwargs...) - result["solution"] = solution_math2eng(result["solution"], data_math; make_si=make_si, make_deg=!get(data, "per_unit", false)) + result["solution"] = transform_solution(result["solution"], data_math; make_si=make_si) else - result = _PM.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_trans!, ref_extensions...], multiconductor=true, kwargs...) + result = _PM.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_transformer!, ref_extensions...], multiconductor=true, kwargs...) end return result diff --git a/test/delta_gens.jl b/test/delta_gens.jl index 8dc3fa78e..dc93ff85e 100644 --- a/test/delta_gens.jl +++ b/test/delta_gens.jl @@ -4,7 +4,7 @@ # This test checks the generators are connected properly by comparing them # to equivalent constant-power loads. This is achieved by fixing their bounds. @testset "ACP/ACR tests" begin - pmd_1 = parse_file("$pmd_path/test/data/opendss/case3_delta_gens.dss"; data_model="mathematical") + pmd_1 = parse_file("$pmd_path/test/data/opendss/case3_delta_gens.dss"; data_model=MATHEMATICAL) # convert to constant power loads for (_, load) in pmd_1["load"] @@ -35,11 +35,11 @@ [ACPPowerModel, ACRPowerModel, IVRPowerModel], [build_mc_opf, build_mc_opf, build_mc_opf_iv] ) - pm_1 = PM.instantiate_model(pmd_1, form, build_method, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) + pm_1 = PM.instantiate_model(pmd_1, form, build_method, ref_extensions=[PMD.ref_add_arcs_transformer!]) sol_1 = PM.optimize_model!(pm_1, optimizer=ipopt_solver) @assert(sol_1["termination_status"]==LOCALLY_SOLVED) - pm_2 = PM.instantiate_model(pmd_2, form, build_method, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) + pm_2 = PM.instantiate_model(pmd_2, form, build_method, ref_extensions=[PMD.ref_add_arcs_transformer!]) sol_2 = PM.optimize_model!(pm_2, optimizer=ipopt_solver) @assert(sol_2["termination_status"]==LOCALLY_SOLVED) @@ -77,11 +77,11 @@ # gen["model"] = 2 # end # - # pm_ivr = PMs.instantiate_model(pmd, PMs.IVRPowerModel, PMD.build_mc_opf_iv, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) + # pm_ivr = PMs.instantiate_model(pmd, PMs.IVRPowerModel, PMD.build_mc_opf_iv, ref_extensions=[PMD.ref_add_arcs_transformer!]) # sol_ivr = PMs.optimize_model!(pm_ivr, optimizer=ipopt_solver) # @assert(sol_1["termination_status"]==LOCALLY_SOLVED) # - # pm_acr = PMs.instantiate_model(pmd, PMs.ACRPowerModel, PMD.build_mc_opf, ref_extensions=[PMD.ref_add_arcs_trans!], multiconductor=true) + # pm_acr = PMs.instantiate_model(pmd, PMs.ACRPowerModel, PMD.build_mc_opf, ref_extensions=[PMD.ref_add_arcs_transformer!]) # sol_acr = PMs.optimize_model!(pm_acr, optimizer=ipopt_solver) # @assert(sol_2["termination_status"]==LOCALLY_SOLVED) # diff --git a/test/transformer.jl b/test/transformer.jl index 44ebe2935..4f1df5398 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -3,23 +3,23 @@ @testset "transformers" begin @testset "test transformer acp pf" begin @testset "2w transformer acp pf yy" begin - pmd_data = parse_file("../test/data/opendss/ut_trans_2w_yy.dss") - sol = run_ac_mc_pf(pmd_data, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + eng = parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + sol = run_ac_mc_pf(eng, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end @testset "2w transformer acp pf dy_lead" begin - pmd_data = parse_file("../test/data/opendss/ut_trans_2w_dy_lead.dss") - sol = run_ac_mc_pf(pmd_data, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + eng = parse_file("../test/data/opendss/ut_trans_2w_dy_lead.dss") + sol = run_ac_mc_pf(eng, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[29.8, -90.4, 149.8], Inf) <= 0.1 end @testset "2w transformer acp pf dy_lag" begin file = - pmd_data = parse_file("../test/data/opendss/ut_trans_2w_dy_lag.dss") - sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) + eng = parse_file("../test/data/opendss/ut_trans_2w_dy_lag.dss") + sol = run_ac_mc_pf(eng, ipopt_solver; make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[-30.0, -150.4, 89.8], Inf) <= 0.1 end @@ -27,30 +27,30 @@ @testset "test transformer ivr pf" begin @testset "2w transformer ivr pf yy" begin - pmd_data = parse_file("../test/data/opendss/ut_trans_2w_yy.dss") - sol = run_mc_pf_iv(pmd_data, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + eng = parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + sol = run_mc_pf_iv(eng, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lead" begin - pmd_data = parse_file("../test/data/opendss/ut_trans_2w_dy_lead.dss") - sol = run_mc_pf_iv(pmd_data, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + eng = parse_file("../test/data/opendss/ut_trans_2w_dy_lead.dss") + sol = run_mc_pf_iv(eng, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[29.8, -90.4, 149.8], Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lag" begin - pmd_data = parse_file("../test/data/opendss/ut_trans_2w_dy_lag.dss") - sol = run_mc_pf_iv(pmd_data, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + eng = parse_file("../test/data/opendss/ut_trans_2w_dy_lag.dss") + sol = run_mc_pf_iv(eng, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[-30.0, -150.4, 89.8], Inf) <= 0.1 end end @testset "2w transformer ac pf yy - banked transformers" begin - eng1 = parse_file("../test/data/opendss/ut_trans_2w_yy_bank.dss"; data_model="engineering") - eng2 = parse_file("../test/data/opendss/ut_trans_2w_yy_bank.dss"; bank_transformers=false, data_model="engineering") + eng1 = parse_file("../test/data/opendss/ut_trans_2w_yy_bank.dss") + eng2 = parse_file("../test/data/opendss/ut_trans_2w_yy_bank.dss"; bank_transformers=false) result1 = run_ac_mc_pf(eng1, ipopt_solver) result2 = run_ac_mc_pf(eng2, ipopt_solver) @@ -64,31 +64,31 @@ @testset "three winding transformer pf" begin @testset "3w transformer ac pf dyy - all non-zero" begin file = - pmd_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_1.dss") - sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) + eng = parse_file("../test/data/opendss/ut_trans_3w_dyy_1.dss") + sol = run_ac_mc_pf(eng, ipopt_solver; make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.9318, 0.88828, 0.88581], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[30.1, -90.7, 151.2], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - some non-zero" begin file = - pmd_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_2.dss") - sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) - #@test isapprox(vm(sol, pmd_data, "3"), [0.93876, 0.90227, 0.90454], atol=1E-4) + eng = parse_file("../test/data/opendss/ut_trans_3w_dyy_2.dss") + sol = run_ac_mc_pf(eng, ipopt_solver; make_si=false) + #@test isapprox(vm(sol, eng, "3"), [0.93876, 0.90227, 0.90454], atol=1E-4) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.93876, 0.90227, 0.90454], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[31.6, -88.8, 153.3], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - all zero" begin - pmd_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_3.dss") - sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) + eng = parse_file("../test/data/opendss/ut_trans_3w_dyy_3.dss") + sol = run_ac_mc_pf(eng, ipopt_solver; make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.97047, 0.93949, 0.946], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[30.6, -90.0, 151.9], Inf) <= 0.1 end @testset "3w transformer ac pf dyy - %loadloss=0" begin - pmd_data = parse_file("../test/data/opendss/ut_trans_3w_dyy_3_loadloss.dss") - sol = run_ac_mc_pf(pmd_data, ipopt_solver; make_si=false) + eng = parse_file("../test/data/opendss/ut_trans_3w_dyy_3_loadloss.dss") + sol = run_ac_mc_pf(eng, ipopt_solver; make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.969531, 0.938369, 0.944748], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[30.7, -90.0, 152.0], Inf) <= 0.1 end @@ -96,19 +96,24 @@ @testset "oltc tests" begin @testset "2w transformer acp opf_oltc yy" begin - pmd_data = parse_file("../test/data/opendss/ut_trans_2w_yy_oltc.dss"; data_model="mathematical") + eng = parse_file("../test/data/opendss/ut_trans_2w_yy_oltc.dss") + # free the taps - pmd_data["transformer"]["1"]["fixed"] = zeros(Bool, 3) - pmd_data["transformer"]["2"]["fixed"] = zeros(Bool, 3) - pm = PM.instantiate_model(pmd_data, ACPPowerModel, build_mc_opf_oltc, ref_extensions=[ref_add_arcs_trans!], multiconductor=true) + eng["transformer"]["tx1"]["tm_fix"] = fill(zeros(Bool, 3), 2) + + math = transform_data_model(eng) + pm = PM.instantiate_model(math, ACPPowerModel, build_mc_opf_oltc, ref_extensions=[ref_add_arcs_transformer!]) sol = PM.optimize_model!(pm, optimizer=ipopt_solver) + # check that taps are set as to boost the voltage in the branches as much as possible; # this is trivially optimal if the voltage bounds are not binding # and without significant shunts (both branch and transformer) @test norm(tap(1,pm)-[0.95, 0.95, 0.95], Inf) <= 1E-4 @test norm(tap(2,pm)-[1.05, 1.05, 1.05], Inf) <= 1E-4 + # then check whether voltage is what OpenDSS expects for those values - solution = solution_math2eng(sol["solution"], pmd_data, make_si=false) + solution = transform_solution(sol["solution"], math, make_si=false) + @test norm(solution["bus"]["3"]["vm"]-[1.0352, 1.022, 1.0142], Inf) <= 1E-4 @test norm(solution["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end From aff5f5622a54a750be2e2d1a82df5079d7dc69c4 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 23 Apr 2020 14:40:48 -0600 Subject: [PATCH 167/224] REF: data_model to enum REF: update transformer fields in eng model REF: map to serializable structure --- src/core/constraint_template.jl | 4 +- src/core/data.jl | 68 ++++++------- src/core/types.jl | 4 + src/core/variable.jl | 8 +- src/data_model/checks.jl | 2 +- src/data_model/components.jl | 19 ++-- src/data_model/eng2math.jl | 168 ++++++++++++++++---------------- src/data_model/math2eng.jl | 108 ++++++++++++-------- src/data_model/units.jl | 14 +-- src/data_model/utils.jl | 4 +- src/io/common.jl | 18 ++-- src/io/dss_parse.jl | 3 +- src/io/json.jl | 48 +++++---- src/io/opendss.jl | 30 +++--- src/io/utils.jl | 2 +- test/data.jl | 2 +- test/opendss.jl | 14 +-- test/pf.jl | 2 +- test/shunt.jl | 2 +- 19 files changed, 275 insertions(+), 245 deletions(-) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 8bbff64a6..c01fae1ce 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -137,8 +137,8 @@ function constraint_mc_trans(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, type = trans["configuration"] f_cnd = trans["f_connections"][1:3] t_cnd = trans["t_connections"][1:3] - tm_set = trans["tm"] - tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : trans["fixed"] + tm_set = trans["tm_set"] + tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : trans["tm_fix"] tm_scale = calculate_tm_scale(trans, ref(pm, nw, :bus, f_bus), ref(pm, nw, :bus, t_bus)) #TODO change data model diff --git a/src/core/data.jl b/src/core/data.jl index d7681d0b1..f1c601684 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -50,36 +50,40 @@ _replace_nan(v) = map(x -> isnan(x) ? zero(x) : x, v) function count_nodes(data::Dict{String,<:Any})::Int n_nodes = 0 - if get(data, "data_model", missing) == "dss" + if get(data, "data_model", missing) == DSS all_nodes = Dict() for obj_type in values(data) - for object in values(obj_type) - if isa(object, Dict) && haskey(object, "buses") - for busname in values(object["buses"]) - name, nodes = _parse_busname(busname) - - if !haskey(all_nodes, name) - all_nodes[name] = Set([]) - end - - for (n, node) in enumerate(nodes[1:3]) - if node - push!(all_nodes[name], n) - end - end - end - elseif isa(object, Dict) - for (prop, val) in object - if startswith(prop, "bus") && prop != "buses" - name, nodes = _parse_busname(val) + if isa(obj_type, Dict) + for object in values(obj_type) + if isa(object, Dict) + if haskey(object, "buses") + for busname in values(object["buses"]) + name, nodes = _parse_busname(busname) + + if !haskey(all_nodes, name) + all_nodes[name] = Set([]) + end - if !haskey(all_nodes, name) - all_nodes[name] = Set([]) + for (n, node) in enumerate(nodes[1:3]) + if node + push!(all_nodes[name], n) + end + end end - - for (n, node) in enumerate(nodes[1:3]) - if node - push!(all_nodes[name], n) + else + for (prop, val) in object + if startswith(prop, "bus") && prop != "buses" + name, nodes = _parse_busname(val) + + if !haskey(all_nodes, name) + all_nodes[name] = Set([]) + end + + for (n, node) in enumerate(nodes[1:3]) + if node + push!(all_nodes[name], n) + end + end end end end @@ -91,22 +95,18 @@ function count_nodes(data::Dict{String,<:Any})::Int for (name, phases) in all_nodes n_nodes += length(phases) end - elseif get(data, "data_model", missing) in ["mathematical", "engineering"] || (haskey(data, "source_type") && data["source_type"] == "matlab") - if get(data, "data_model", missing) == "mathematical" - Memento.info(_LOGGER, "counting nodes from PowerModelsDistribution structure may not be as accurate as directly from `parse_dss` data due to virtual buses, etc.") - end - + elseif get(data, "data_model", missing) in [MATHEMATICAL, ENGINEERING] || (haskey(data, "source_type") && data["source_type"] == "matlab") n_nodes = 0 for (name, bus) in data["bus"] - if get(data, "data_model", missing) == "matpower" || (haskey(data, "source_type") && data["source_type"] == "matlab") + if get(data, "data_model", missing) == MATPOWER || (haskey(data, "source_type") && data["source_type"] == "matlab") n_nodes += sum(bus["vm"] .> 0.0) else - if data["data_model"] == "mathematical" + if data["data_model"] == MATHEMATICAL name = bus["name"] end if all(!occursin(pattern, name) for pattern in [_excluded_count_busname_patterns...]) - if data["data_model"] == "mathematical" + if data["data_model"] == MATHEMATICAL n_nodes += length(bus["terminals"][.!get(bus, "grounded", zeros(length(bus["terminals"])))]) else n_nodes += length([n for n in bus["terminals"] if !(n in get(bus, "grounded", []))]) diff --git a/src/core/types.jl b/src/core/types.jl index b50488406..bc879a949 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -1,3 +1,7 @@ +"Supported data model types" +@enum DataModelType ENGINEERING MATHEMATICAL DSS MATPOWER + + "" abstract type AbstractNLPUBFModel <: _PM.AbstractBFQPModel end diff --git a/src/core/variable.jl b/src/core/variable.jl index 5ed27d6c9..8ed5c748b 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -560,16 +560,16 @@ function variable_mc_oltc_tap(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounde ncnds = length(cnds) nph = 3 - p_oltc_ids = [id for (id,trans) in ref(pm, nw, :transformer) if !all(trans["fixed"])] + p_oltc_ids = [id for (id,trans) in ref(pm, nw, :transformer) if !all(trans["tm_fix"])] tap = var(pm, nw)[:tap] = Dict(i => JuMP.@variable(pm.model, [p in 1:nph], base_name="$(nw)_tm_$(i)", - start=ref(pm, nw, :transformer, i, "tm")[p] + start=ref(pm, nw, :transformer, i, "tm_set")[p] ) for i in p_oltc_ids) if bounded for tr_id in p_oltc_ids, p in 1:nph - set_lower_bound(var(pm, nw)[:tap][tr_id][p], ref(pm, nw, :transformer, tr_id, "tm_min")[p]) - set_upper_bound(var(pm, nw)[:tap][tr_id][p], ref(pm, nw, :transformer, tr_id, "tm_max")[p]) + set_lower_bound(var(pm, nw)[:tap][tr_id][p], ref(pm, nw, :transformer, tr_id, "tm_lb")[p]) + set_upper_bound(var(pm, nw)[:tap][tr_id][p], ref(pm, nw, :transformer, tr_id, "tm_ub")[p]) end end diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index 7fc7f9bf9..089a2e000 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -528,7 +528,7 @@ function _check_transformer(data_eng::Dict{String,<:Any}, name::Any) transformer = data_eng["transformer"][name] nrw = length(transformer["bus"]) - _check_has_size(transformer, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_min", "tm_max", "tm_step"], nrw, context="trans $name") + _check_has_size(transformer, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_lb", "tm_ub", "tm_step"], nrw, context="trans $name") @assert length(transformer["xsc"])==(nrw^2-nrw)/2 diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 2a9c3b531..13463cf91 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -54,12 +54,12 @@ end "Instantiates a PowerModelsDistribution data model" -function Model(model_type::String="engineering"; kwargs...)::Dict{String,Any} +function Model(model_type::String=ENGINEERING; kwargs...)::Dict{String,Any} kwargs = Dict{Symbol,Any}(kwargs) - if model_type == "engineering" + if model_type == ENGINEERING data_model = Dict{String,Any}( - "data_model" => "engineering", + "data_model" => model_type, "per_unit" => false, "settings" => Dict{String,Any}( "v_var_scalar" => get(kwargs, :v_var_scalar, 1e3), @@ -70,7 +70,7 @@ function Model(model_type::String="engineering"; kwargs...)::Dict{String,Any} ) _add_unused_kwargs!(data_model["settings"], kwargs) - elseif model_type == "mathematical" + elseif model_type == MATHEMATICAL Memento.warn(_LOGGER, "There are not currently any helper functions to help build a mathematical model, this will only instantiate required fields.") data_model = Dict{String,Any}( "bus" => Dict{String,Any}(), @@ -84,7 +84,7 @@ function Model(model_type::String="engineering"; kwargs...)::Dict{String,Any} "per_unit" => false, "baseMVA" => 100.0, "basekv" => 1.0, - "data_model" => "mathematical" + "data_model" => model_type ) _add_unused_kwargs!(data_model, kwargs) @@ -239,12 +239,11 @@ function create_transformer(; kwargs...) "xsc" => get(kwargs, :xsc, zeros(n_windings^2-n_windings)), "noloadloss" => get(kwargs, :noloadloss, 0.0), "imag" => get(kwargs, :imag, 0.0), - "tm" => get(kwargs, :tm, fill(fill(1.0, 3), n_windings)), - "tm_min" => get(kwargs, :tm_min, fill(fill(0.9, 3), n_windings)), - "tm_max" => get(kwargs, :tm_max, fill(fill(1.1, 3), n_windings)), + "tm_set" => get(kwargs, :tm, fill(fill(1.0, 3), n_windings)), + "tm_lb" => get(kwargs, :tm_min, fill(fill(0.9, 3), n_windings)), + "tm_ub" => get(kwargs, :tm_max, fill(fill(1.1, 3), n_windings)), "tm_step" => get(kwargs, :tm_step, fill(fill(1/32, 3), n_windings)), - "tm_fix" => get(kwargs, :tm_fix, fill(fill(true, 3), n_windings)), - "fixed" => get(kwargs, :fixed, fill(1, n_windings)), + "tm_fix" => get(kwargs, :tm_fix, fill(ones(Bool, 3), n_windings)), "connections" => get(kwargs, :connections, fill(collect(1:4), n_windings)), ) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 671309a13..8c6fd5a30 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -5,7 +5,7 @@ const _1to1_maps = Dict{String,Vector{String}}( "bus" => ["vm", "va", "terminals", "phases", "neutral", "dss"], "line" => ["f_connections", "t_connections", "source_id", "dss"], "transformer" => ["f_connections", "t_connections", "source_id", "dss"], - "switch" => ["status", "state", "f_connections", "t_connections", "source_id", "dss"], + "switch" => ["status", "f_connections", "t_connections", "source_id", "dss"], "line_reactor" => ["f_connections", "t_connections", "source_id", "dss"], "series_capacitor" => ["f_connections", "t_connections", "source_id", "dss"], "shunt" => ["status", "gs", "bs", "connections", "source_id", "dss"], @@ -144,12 +144,12 @@ const _time_series_parameters = Dict{String,Dict{String,Tuple{Function, String}} "base function for converting engineering model to mathematical model" function _map_eng2math(data_eng; kron_reduced::Bool=true) - @assert get(data_eng, "data_model", "mathematical") == "engineering" + @assert get(data_eng, "data_model", MATHEMATICAL) == ENGINEERING data_math = Dict{String,Any}( "name" => get(data_eng, "name", ""), "per_unit" => get(data_eng, "per_unit", false), - "data_model" => "mathematical", + "data_model" => MATHEMATICAL, "settings" => data_eng["settings"], ) @@ -159,11 +159,9 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) data_math["basekv"] = data_eng["settings"]["vbase"] data_math["baseMVA"] = data_eng["settings"]["sbase"]*data_eng["settings"]["v_var_scalar"]/1E6 - data_math["map"] = Dict{Int,Dict{Symbol,Any}}( - 1 => Dict{Symbol,Any}( - :unmap_function => :_map_math2eng_root!, - ) - ) + data_math["map"] = Vector{Dict{String,Any}}([ + Dict{String,Any}("unmap_function" => "_map_math2eng_root!") + ]) _init_base_components!(data_math) @@ -247,7 +245,7 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, if kron_reduced filter = terminals.!=kr_neutral terminals_kr = terminals[filter] - @assert(all(t in kr_phases for t in terminals_kr)) + @assert all(t in kr_phases for t in terminals_kr) "bus $name has terminals $(terminals), outside of $kr_phases, cannot be kron reduced" _apply_filter!(math_obj, ["vm", "va", "vmin", "vmax"], filter) _pad_properties!(math_obj, ["vm", "va", "vmin", "vmax"], terminals_kr, kr_phases) @@ -261,11 +259,11 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, data_math["bus_lookup"][name] = math_obj["index"] - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "bus.$(math_obj["index"])", - :unmap_function => :_map_math2eng_bus!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "bus.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_bus!", + )) # time series for (fr, (f, to)) in _time_series_parameters["bus"] @@ -330,11 +328,11 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any data_math["branch"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "branch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_line!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "branch.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_line!", + )) # time series # TODO @@ -352,14 +350,13 @@ end function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "transformer", Dict{Any,Dict{String,Any}}()) # Build map first, so we can update it as we decompose the transformer - map_idx = length(data_math["map"])+1 - data_math["map"][map_idx] = Dict{Symbol,Any}( - :from => name, - :to => Vector{String}([]), - :unmap_function => :_map_math2eng_transformer!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => Vector{String}([]), + "unmap_function" => "_map_math2eng_transformer!", + )) - to_map = data_math["map"][map_idx][:to] + to_map = data_math["map"][end]["to"] _apply_xfmrcode!(eng_obj, data_eng) @@ -392,7 +389,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) #TODO remove once moving out kron-reduction - dims = kron_reduced ? 3 : length(eng_obj["tm"][1]) + dims = kron_reduced ? 3 : length(eng_obj["tm_set"][1]) transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh, nphases=dims) for w in 1:nrw @@ -409,12 +406,12 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic "t_connections" => collect(1:dims+1), "configuration" => eng_obj["configuration"][w], "polarity" => eng_obj["polarity"][w], - "tm" => eng_obj["tm"][w], - "fixed" => eng_obj["fixed"][w], + "tm_set" => eng_obj["tm_set"][w], + "tm_fix" => eng_obj["tm_fix"][w], "index" => length(data_math["transformer"])+1 ) - for prop in ["tm_min", "tm_max", "tm_step"] + for prop in ["tm_lb", "tm_ub", "tm_step"] if haskey(eng_obj, prop) transformer_2wa_obj[prop] = eng_obj[prop][w] end @@ -424,7 +421,8 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic # TODO fix how padding works, this is a workaround to get bank working if all(eng_obj["configuration"] .== "wye") f_connections = transformer_2wa_obj["f_connections"] - _pad_properties!(transformer_2wa_obj, ["tm_min", "tm_max", "tm", "fixed"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) + _pad_properties!(transformer_2wa_obj, ["tm_lb", "tm_ub", "tm_set"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) + _pad_properties!(transformer_2wa_obj, ["tm_fix"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=false) transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] end @@ -450,6 +448,8 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["state"] = lowercase(get(eng_obj, "state", "closed")) == "open" ? 0 : 1 + # OPF bounds for (fr_key, to_key) in zip(["cm_ub"], ["c_rating"]) if haskey(eng_obj, fr_key) @@ -476,11 +476,10 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A bus_obj = Dict{String,Any}( "name" => "_virtual_bus.switch.$name", "bus_i" => length(data_math["bus"])+1, - "bus_type" => 1, + "bus_type" => lowercase(get(eng_obj, "state", "closed")) == "open" ? 4 : 1, "vmin" => f_bus["vmin"], "vmax" => f_bus["vmax"], "base_kv" => f_bus["base_kv"], - "status" => 1, "index" => length(data_math["bus"])+1, ) @@ -512,7 +511,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A "shift" => zeros(nphases), "tap" => ones(nphases), "switch" => false, - "br_status" => 1, + "br_status" => lowercase(get(eng_obj, "state", "closed")) == "open" ? 0 : 1, ) merge!(branch_obj, _branch_obj) @@ -551,12 +550,11 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A # data_math["switch"]["$(math_obj["index"])"] = math_obj # TODO enable real switches - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => map_to, - :unmap_function => :_map_math2eng_switch!, - ) - + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => map_to, + "unmap_function" => "_map_math2eng_switch!", + )) end end @@ -613,11 +611,11 @@ function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Di data_math["branch"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "branch.$(math_obj["index"])", - :unmap_function => :_map_math2eng_line_reactor!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "branch.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_line_reactor!", + )) # time series # TODO @@ -655,11 +653,11 @@ function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An data_math["shunt"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_capacitor!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "shunt.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_capacitor!", + )) # time series for (fr, (f, to)) in _time_series_parameters["shunt"] @@ -694,11 +692,11 @@ function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng: data_math["shunt"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_shunt_capacitor!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "shunt.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_shunt_capacitor!", + )) # time series # TODO @@ -734,11 +732,11 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D data_math["shunt"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "shunt.$(math_obj["index"])", - :unmap_function => :_map_math2eng_shunt_reactor!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "shunt.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_shunt_reactor!", + )) # time series # TODO @@ -779,11 +777,11 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any data_math["load"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "load.$(math_obj["index"])", - :unmap_function => :_map_math2eng_load!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "load.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_load!", + )) # time series for (fr, (f, to)) in _time_series_parameters["load"] @@ -838,11 +836,11 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ data_math["gen"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_generator!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "gen.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_generator!", + )) # time series # TODO @@ -892,11 +890,11 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An data_math["gen"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "gen.$(math_obj["index"])", - :unmap_function => :_map_math2eng_solar!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "gen.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_solar!", + )) # time series # TODO @@ -944,11 +942,11 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: data_math["storage"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => "storage.$(math_obj["index"])", - :unmap_function => :_map_math2eng_storage!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => "storage.$(math_obj["index"])", + "unmap_function" => "_map_math2eng_storage!", + )) # time series # TODO @@ -1043,10 +1041,10 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: data_math["gen"]["$(math_obj["index"])"] = math_obj - data_math["map"][length(data_math["map"])+1] = Dict{Symbol,Any}( - :from => name, - :to => map_to, - :unmap_function => :_map_math2eng_voltage_source!, - ) + push!(data_math["map"], Dict{String,Any}( + "from" => name, + "to" => map_to, + "unmap_function" => "_map_math2eng_voltage_source!", + )) end end diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index 92083c1ed..da226a379 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -1,16 +1,15 @@ "" -function solution_math2eng(solution_math::Dict{String,<:Any}, data_math::Dict{String,<:Any}; make_si::Bool=true, make_deg::Bool=true)::Dict{String,Any} +function transform_solution(solution_math::Dict{String,<:Any}, data_math::Dict{String,<:Any}; map::Union{Vector{Dict{String,<:Any}},Missing}=missing, make_si::Bool=true)::Dict{String,Any} + @assert get(data_math, "data_model", MATHEMATICAL) == MATHEMATICAL "provided solution cannot be converted to an engineering model: data_model not recognized" solution_eng = Dict{String, Any}() - if make_deg || make_si - solution_math = solution_make_si(solution_math, data_math; mult_vbase=make_si, mult_sbase=make_si, convert_rad2deg=make_deg) - end + solution_math = solution_make_si(solution_math, data_math; mult_vbase=make_si, mult_sbase=make_si, convert_rad2deg=true) + + map = ismissing(map) ? get(data_math, "map", Vector{Dict{String,Any}}()) : map + @assert !isempty(map) "Map is empty, cannot map solution up to engineering model" - map_keys = sort(collect(keys(data_math["map"])); rev=true) - for map_key in map_keys - map = data_math["map"][map_key] - reverse_bus_lookup = Dict{Int,Any}((bus_math, bus_eng) for (bus_eng, bus_math) in data_math["bus_lookup"]) - getfield(PowerModelsDistribution, map[:unmap_function])(solution_eng, solution_math, map, reverse_bus_lookup) + for map_item in reverse(map) + getfield(PowerModelsDistribution, Symbol(map_item["unmap_function"]))(solution_eng, solution_math, map_item) end for (k,v) in solution_eng @@ -24,14 +23,14 @@ end "" -function _map_math2eng_voltage_source!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_voltage_source!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) if !haskey(data_eng, "voltage_source") data_eng["voltage_source"] = Dict{Any,Any}() end eng_obj = _init_unmap_eng_obj!(data_eng, "voltage_source", map) - for to_id in map[:to] + for to_id in map["to"] math_obj = _get_math_obj(data_math, to_id) if startswith(to_id, "gen") for property in ["pg", "qg", "pg_bus", "qg_bus"] @@ -43,113 +42,142 @@ function _map_math2eng_voltage_source!(data_eng::Dict{String,<:Any}, data_math:: end if !isempty(eng_obj) - data_eng["voltage_source"][map[:from]] = eng_obj + data_eng["voltage_source"][map["from"]] = eng_obj end end "" -function _map_math2eng_bus!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_bus!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "bus", map) - math_obj = _get_math_obj(data_math, map[:to]) + math_obj = _get_math_obj(data_math, map["to"]) merge!(eng_obj, math_obj) if !isempty(eng_obj) - data_eng["bus"][map[:from]] = eng_obj + data_eng["bus"][map["from"]] = eng_obj end end "" -function _map_math2eng_load!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_load!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "load", map) - math_obj = _get_math_obj(data_math, map[:to]) + math_obj = _get_math_obj(data_math, map["to"]) merge!(eng_obj, math_obj) if !isempty(eng_obj) - data_eng["load"][map[:from]] = eng_obj + data_eng["load"][map["from"]] = eng_obj end end -function _map_math2eng_shunt_capacitor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_shunt_capacitor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "shunt_capacitor", map) - math_obj = _get_math_obj(data_math, map[:to]) + math_obj = _get_math_obj(data_math, map["to"]) merge!(eng_obj, math_obj) if !isempty(eng_obj) - data_eng["shunt_capacitor"][map[:from]] = eng_obj + data_eng["shunt_capacitor"][map["from"]] = eng_obj end end -function _map_math2eng_shunt!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_shunt!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "shunt", map) + math_obj = _get_math_obj(data_math, map["to"]) + + merge!(eng_obj, math_obj) + + if !isempty(eng_obj) + data_eng["shunt"][map["from"]] = eng_obj +end end -function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "generator", map) - math_obj = _get_math_obj(data_math, map[:to]) + math_obj = _get_math_obj(data_math, map["to"]) merge!(eng_obj, math_obj) if !isempty(eng_obj) - data_eng["generator"][map[:from]] = eng_obj + data_eng["generator"][map["from"]] = eng_obj end end -function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "solar", map) - math_obj = _get_math_obj(data_math, map[:to]) + math_obj = _get_math_obj(data_math, map["to"]) merge!(eng_obj, math_obj) if !isempty(eng_obj) - data_eng["solar"][map[:from]] = eng_obj + data_eng["solar"][map["from"]] = eng_obj end end -function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "storage", map) - math_obj = _get_math_obj(data_math, map[:to]) + math_obj = _get_math_obj(data_math, map["to"]) merge!(eng_obj, math_obj) if !isempty(eng_obj) - data_eng["storage"][map[:from]] = eng_obj + data_eng["storage"][map["from"]] = eng_obj end end -function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "line", map) - math_obj = _get_math_obj(data_math, map[:to]) + math_obj = _get_math_obj(data_math, map["to"]) merge!(eng_obj, math_obj) if !isempty(eng_obj) - data_eng["line"][map[:from]] = eng_obj + data_eng["line"][map["from"]] = eng_obj end end -function _map_math2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "line_reactor", map) + math_obj = _get_math_obj(data_math, map["to"]) + + merge!(eng_obj, math_obj) + + if !isempty(eng_obj) + data_eng["line_reactor"][map["from"]] = eng_obj + end end -function _map_math2eng_switch!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_switch!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) + eng_obj = _init_unmap_eng_obj!(data_eng, "switch", map) + + + for to_id in map["to"] + if startswith(to_id, "branch") # TODO update math2eng switch for when switches are fully supported + math_obj = _get_math_obj(data_math, to_id) + merge!(eng_obj, math_obj) + end + end + + if !isempty(eng_obj) + data_eng["switch"][map["from"]] = eng_obj + end end -function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "transformer", map) - trans_2wa_ids = [index for (comp_type, index) in split.(map[:to], ".", limit=2) if comp_type=="transformer"] + trans_2wa_ids = [index for (comp_type, index) in split.(map["to"], ".", limit=2) if comp_type=="transformer"] prop_map = Dict("pf"=>"p", "qf"=>"q", "crt_fr"=>"crt", "cit_fr"=>"cit") for (prop_from, prop_to) in prop_map @@ -161,12 +189,12 @@ function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dic end if !isempty(eng_obj) - data_eng["transformer"][map[:from]] = eng_obj + data_eng["transformer"][map["from"]] = eng_obj end end -function _map_math2eng_root!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{Symbol,<:Any}, bus_lookup::Dict{Int,<:Any}) +function _map_math2eng_root!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) data_eng["per_unit"] = data_math["per_unit"] data_eng["settings"] = Dict{String,Any}("sbase" => data_math["baseMVA"]) diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 24154a8d2..5986daa90 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -24,10 +24,8 @@ const _dimensionalize_math = Dict{String,Dict{String,Vector{String}}}( "converts data model between per-unit and SI units" -function make_per_unit!(data::Dict{String,<:Any}) - data_model_type = get(data, "data_model", "mathematical") - - if data_model_type == "mathematical" +function make_per_unit!(data::Dict{String,<:Any}; data_model_type=get(data, "data_model", MATHEMATICAL)) + if data_model_type == MATHEMATICAL if !get(data, "per_unit", false) bus_indexed_id = string(data["bus_lookup"][data["settings"]["base_bus"]]) vbases = Dict(bus_indexed_id=>data["settings"]["vbase"]) @@ -35,13 +33,7 @@ function make_per_unit!(data::Dict{String,<:Any}) _make_math_per_unit!(data, vbases=vbases, sbase=sbase, v_var_scalar=data["settings"]["v_var_scalar"]) else - # make math model si units - end - elseif data_model_type == "engineering" - if !get(data, "per_unit", false) - # make eng model per unit - else - # make eng model si units + # TODO make math model si units end else Memento.warn(_LOGGER, "Data model '$data_model_type' is not recognized, no per-unit transformation performed") diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index e79650fda..a5a3ee94d 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -434,7 +434,7 @@ end "initialization actions for unmapping" -function _init_unmap_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, map::Dict{Symbol,Any})::Dict{String,Any} +function _init_unmap_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, map::Dict{String,<:Any})::Dict{String,Any} if !haskey(data_eng, eng_obj_type) data_eng[eng_obj_type] = Dict{Any,Any}() end @@ -470,7 +470,7 @@ function _apply_xfmrcode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:A for (k, v) in xfmrcode if !haskey(eng_obj, k) eng_obj[k] = v - elseif haskey(eng_obj, k) && k in ["vnom", "snom", "tm", "rs"] + elseif haskey(eng_obj, k) && k in ["vnom", "snom", "tm_set", "rs"] for (w, vw) in enumerate(eng_obj[k]) if ismissing(vw) eng_obj[k][w] = v[w] diff --git a/src/io/common.jl b/src/io/common.jl index 6cb675ad2..9d9289c75 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -1,9 +1,9 @@ """ parse_file(io) -Parses the IOStream of a file into a Three-Phase PowerModels data structure. +Parses the IOStream of a file into a PowerModelsDistribution data structure. """ -function parse_file(io::IO, filetype::AbstractString="json"; data_model::String="engineering", import_all::Bool=false, bank_transformers::Bool=true, transformations::Vector{<:Function}=Vector{Function}([]))::Dict{String,Any} +function parse_file(io::IO, filetype::AbstractString="json"; data_model::DataModelType=ENGINEERING, import_all::Bool=false, bank_transformers::Bool=true, transformations::Vector{<:Function}=Vector{Function}([]))::Dict{String,Any} if filetype == "dss" data_eng = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) @@ -11,7 +11,7 @@ function parse_file(io::IO, filetype::AbstractString="json"; data_model::String= transformation(data_eng) end - if data_model == "mathematical" + if data_model == MATHEMATICAL return transform_data_model(data_eng; make_pu=true) else return data_eng @@ -19,7 +19,7 @@ function parse_file(io::IO, filetype::AbstractString="json"; data_model::String= elseif filetype == "json" pmd_data = parse_json(io; validate=false) - if get(pmd_data, "data_model", "mathematical") != data_model + if pmd_data["data_model"] != data_model && data_model == ENGINEERING return transform_data_model(pmd_data) else return pmd_data @@ -42,16 +42,16 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=true)::Dict{String,Any} - current_data_model = get(data, "data_model", "mathematical") + current_data_model = get(data, "data_model", MATHEMATICAL) - if current_data_model == "engineering" + if current_data_model == ENGINEERING data_math = _map_eng2math(data; kron_reduced=kron_reduced) correct_network_data!(data_math; make_pu=make_pu) return data_math - elseif current_data_model == "mathematical" - Memento.warn(_LOGGER, "A mathematical data model cannot be converted back to an engineering data model, irreversible transformations have been made") + elseif current_data_model == MATHEMATICAL + Memento.warn(_LOGGER, "A MATHEMATICAL data model cannot be converted back to an ENGINEERING data model, irreversible transformations have been made") return data else Memento.warn(_LOGGER, "Data model '$current_data_model' is not recognized, no model type transformation performed") @@ -62,7 +62,7 @@ end "" function correct_network_data!(data::Dict{String,Any}; make_pu::Bool=true) - if get(data, "data_model", "mathematical") == "engineering" + if get(data, "data_model", MATHEMATICAL) == ENGINEERING check_eng_data_model(data) else if make_pu diff --git a/src/io/dss_parse.jl b/src/io/dss_parse.jl index 9037875b0..b8a9e95b9 100644 --- a/src/io/dss_parse.jl +++ b/src/io/dss_parse.jl @@ -764,7 +764,6 @@ Will also parse files defined inside of the originating DSS file via the """ function parse_dss(io::IOStream)::Dict{String,Any} filename = match(r"^$", io.name).captures[1] - # Memento.info(_LOGGER, "Calling parse_dss on $filename") current_file = split(filename, "/")[end] path = join(split(filename, '/')[1:end-1], '/') data_dss = Dict{String,Any}() @@ -904,7 +903,7 @@ function parse_dss(io::IOStream)::Dict{String,Any} _parse_dss_with_dtypes!(data_dss) - data_dss["data_model"] = "dss" + data_dss["data_model"] = DSS return data_dss end diff --git a/src/io/json.jl b/src/io/json.jl index 0ee1a71c9..5eacb6372 100644 --- a/src/io/json.jl +++ b/src/io/json.jl @@ -6,6 +6,7 @@ import JSON.Writer: StructuralContext, show_json struct PMDSerialization <: CommonSerialization end +"converts julia Version into serializable structure" function _jsonver2juliaver!(data::Dict{String,<:Any}) if haskey(data, "source_version") && isa(data["source_version"], Dict) data["source_version"] = "$(data["source_version"]["major"]).$(data["source_version"]["minor"]).$(data["source_version"]["patch"])" @@ -13,6 +14,7 @@ function _jsonver2juliaver!(data::Dict{String,<:Any}) end +"recursive helper function for parsing serialized matrices" function _parse_mats_recursive!(data::Dict{String,<:Any})::Dict{String,Any} parse = haskey(data, "type") && data["type"]=="Matrix" && haskey(data, "eltype") && haskey(data, "value") if parse @@ -31,6 +33,7 @@ function _parse_mats_recursive!(data::Dict{String,<:Any})::Dict{String,Any} end +"parser function for serialized matrices" function _parse_mats!(data::Dict{String,<:Any}) stack = Array{Tuple{Any, Any, Any}}([(data, k, v) for (k, v) in data]) while !isempty(stack) @@ -51,60 +54,71 @@ function _parse_mats!(data::Dict{String,<:Any}) end -"" -function parse_json(file::String; kwargs...) +"Parses a JSON file into a PMD data structure" +function parse_json(file::String; validate::Bool=false) data = open(file) do io - parse_json(io; filetype=split(lowercase(file), '.')[end], kwargs...) + parse_json(io; filetype=split(lowercase(file), '.')[end], validate=validate) end return data end -"Parses json from iostream or string" -function parse_json(io::IO; kwargs...)::Dict{String,Any} +"Parses a JSON file into a PMD data structure" +function parse_json(io::IO; validate::Bool=false)::Dict{String,Any} data = JSON.parse(io) _jsonver2juliaver!(data) _parse_mats!(data) - if haskey(data, "files") - data["files"] = Set(data["files"]) - end + # converts data model in into enum, default is MATHEMATICAL == 1 + data["data_model"] = DataModelType(get(data, "data_model", 1)) - if get(kwargs, :validate, true) - PowerModels.correct_network_data!(data) + if validate + correct_network_data!(data) end return data end -function print_file(path::String, data; kwargs...) +"prints a PMD data structure into a JSON file" +function print_file(path::String, data::Dict{String,<:Any}; indent::Int=2) open(path, "w") do io - print_file(io, data; kwargs...) + print_file(io, data; indent=indent) end end -function print_file(io::IO, data; indent=false) - if indent - JSON.print(io, JSON.parse(sprint(show_json, PMDSerialization(), data)), 4) - else +"prints a PMD data structure into a JSON file" +function print_file(io::IO, data::Dict{String,<:Any}; indent::Int=2) + if indent == 0 JSON.print(io, JSON.parse(sprint(show_json, PMDSerialization(), data))) + else + JSON.print(io, JSON.parse(sprint(show_json, PMDSerialization(), data)), indent) end end +"turns a matrix into a serializable structure" function show_json(io::StructuralContext, ::PMDSerialization, f::Matrix{<:Any}) N, M = size(f) value = string("[", join([join([f[i,j] for j in 1:M], " ") for i in 1:N], "; "), "]") eltyp = isempty(f) ? eltype(f) : typeof(f[1,1]) - out = Dict(:type=>:Matrix, :eltype=>eltyp, :value=>value) + out = Dict(:type=>:Matrix, :eltype=>eltyp, :value=>value) return show_json(io, StandardSerialization(), out) end +function show_json(io::StructuralContext, ::CommonSerialization, f::DataModelType) + return show_json(io, StandardSerialization(), Int(f)) +end + +"custom handling for data model type enums" +JSON.lower(p::DataModelType) = Int(p) + + +"parses in a serialized matrix" function _parse_matrix_value(value::String, eltyp::String) if value=="[]" eltyp = diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 44c35c433..09164be75 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -516,12 +516,11 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, nrw = defaults["windings"] eng_obj = Dict{String,Any}( - "tm" => Vector{Vector{Float64}}([fill(tap, nphases) for tap in defaults["taps"]]), - "tm_min" => Vector{Vector{Float64}}(fill(fill(defaults["mintap"], nphases), nrw)), - "tm_max" => Vector{Vector{Float64}}(fill(fill(defaults["maxtap"], nphases), nrw)), - "tm_fix" => Vector{Vector{Int}}([fill(1, nphases) for w in 1:nrw]), + "tm_set" => Vector{Vector{Float64}}([fill(tap, nphases) for tap in defaults["taps"]]), + "tm_lb" => Vector{Vector{Float64}}(fill(fill(defaults["mintap"], nphases), nrw)), + "tm_ub" => Vector{Vector{Float64}}(fill(fill(defaults["maxtap"], nphases), nrw)), + "tm_fix" => Vector{Vector{Bool}}(fill(ones(Bool, nphases), nrw)), "tm_step" => Vector{Vector{Float64}}(fill(fill(1/32, nphases), nrw)), - "fixed" => Vector{Vector{Int}}(fill(fill(1, nphases), nrw)), "vnom" => Vector{Float64}(defaults["kvs"]), "snom" => Vector{Float64}(defaults["kvas"]), "configuration" => Vector{String}(defaults["conns"]), @@ -586,14 +585,14 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri # taps if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, "taps") && _is_after_xfmrcode(dss_obj["prop_order"], "taps")) || all(haskey(dss_obj, k) && _is_after_xfmrcode(dss_obj["prop_order"], k) for k in ["tap", "tap_2", "tap_3"]) - eng_obj["tm"] = [fill(defaults["taps"][w], ncoils) for w in 1:nrw] + eng_obj["tm_set"] = [fill(defaults["taps"][w], ncoils) for w in 1:nrw] else for (w, key_suffix) in enumerate(["", "_2", "_3"]) if haskey(dss_obj, "tap$(key_suffix)") && _is_after_xfmrcode(dss_obj["prop_order"], "tap$(key_suffix)") - if !haskey(eng_obj, "tm") - eng_obj["tm"] = Vector{Any}(missing, nrw) + if !haskey(eng_obj, "tm_set") + eng_obj["tm_set"] = Vector{Any}(missing, nrw) end - eng_obj["tm"][w] = fill(defaults["taps"][defaults["wdg$(key_suffix)"]], ncoils) + eng_obj["tm_set"][w] = fill(defaults["taps"][defaults["wdg$(key_suffix)"]], ncoils) end end end @@ -615,7 +614,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end # mintap, maxtap - for (fr_key, to_key) in zip(["mintap", "maxtap"], ["tm_min", "tm_max"]) + for (fr_key, to_key) in zip(["mintap", "maxtap"], ["tm_lb", "tm_ub"]) if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, fr_key) && _is_after_xfmrcode(dss_obj["prop_order"], fr_key)) eng_obj[to_key] = fill(fill(defaults[fr_key], ncoils), nrw) end @@ -660,9 +659,8 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri # tm_fix, tm_step don't appear in opendss if isempty(defaults["xfmrcode"]) - eng_obj["tm_fix"] = [fill(1, ncoils) for w in 1:nrw] + eng_obj["tm_fix"] = fill(ones(Bool, ncoils), nrw) eng_obj["tm_step"] = fill(fill(1/32, ncoils), nrw) - eng_obj["fixed"] = fill(fill(true, ncoils), nrw) end # always required @@ -829,7 +827,7 @@ end "Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, bank_transformers::Bool=true)::Dict{String,Any} data_eng = Dict{String,Any}( - "data_model" => "engineering", + "data_model" => ENGINEERING, "settings" => Dict{String,Any}(), ) @@ -842,16 +840,14 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban source_bus = _parse_busname(defaults["bus1"])[1] data_eng["name"] = data_dss["circuit"] - data_eng["data_model"] = "engineering" - - # TODO rename fields data_eng["settings"]["v_var_scalar"] = 1e3 data_eng["settings"]["vbase"] = defaults["basekv"] / sqrt(3) data_eng["settings"]["base_bus"] = source_bus data_eng["settings"]["sbase"] = defaults["basemva"] * 1e3 data_eng["settings"]["base_frequency"] = get(get(data_dss, "options", Dict{String,Any}()), "defaultbasefreq", 60.0) - data_eng["files"] = data_dss["filename"] + # collect turns the Set into Array, making it serializable + data_eng["files"] = collect(data_dss["filename"]) else Memento.error(_LOGGER, "Circuit not defined, not a valid circuit!") end diff --git a/src/io/utils.jl b/src/io/utils.jl index 1833b48a4..7e7c6662f 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -330,7 +330,7 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) # f_connections will be sorted from small to large f_phases_loc = Dict(hcat([[(c,(i,p)) for (p, c) in enumerate(tr["connections"][1][1:end-1])] for (i, tr) in enumerate(trs)]...)) locs = [f_phases_loc[x] for x in sort(collect(keys(f_phases_loc)))] - props_merge = ["connections", "tm", "tm_max", "tm_min", "fixed", "tm_step", "tm_fix"] + props_merge = ["connections", "tm_set", "tm_ub", "tm_lb", "tm_step", "tm_fix"] for prop in props_merge btrans[prop] = [[trs[i][prop][w][p] for (i,p) in locs] for w in 1:nrw] diff --git a/test/data.jl b/test/data.jl index 710edec3b..5b7261022 100644 --- a/test/data.jl +++ b/test/data.jl @@ -72,7 +72,7 @@ end @testset "node counting functions" begin dss = parse_dss("../test/data/opendss/case5_phase_drop.dss") eng = parse_file("../test/data/opendss/case5_phase_drop.dss") - math = parse_file("../test/data/opendss/case5_phase_drop.dss"; data_model="mathematical") + math = parse_file("../test/data/opendss/case5_phase_drop.dss"; data_model=MATHEMATICAL) @test count_nodes(dss) == 10 # stopped excluding source from node count @test count_nodes(dss) == count_nodes(eng) diff --git a/test/opendss.jl b/test/opendss.jl index 722957273..6b46f565f 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -81,7 +81,7 @@ Memento.setlevel!(TESTLOG, "info") @test_throws(TESTLOG, ErrorException, - parse_file("../test/data/opendss/test_simple2.dss"; data_model="mathematical")) + parse_file("../test/data/opendss/test_simple2.dss"; data_model=MATHEMATICAL)) @test_warn(TESTLOG, "Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.", parse_file("../test/data/opendss/test2_master.dss")) @@ -111,7 +111,7 @@ end eng = parse_file("../test/data/opendss/test2_master.dss", import_all=true) - math = parse_file("../test/data/opendss/test2_master.dss"; data_model="mathematical", import_all=true) + math = parse_file("../test/data/opendss/test2_master.dss"; data_model=MATHEMATICAL, import_all=true) @testset "buscoords automatic parsing" begin @test all(haskey(bus, "lon") && haskey(bus, "lat") for bus in values(math["bus"]) if "bus_i" in 1:10) @@ -213,12 +213,12 @@ PMD._apply_like!(dss_data["transformer"]["reg4b"], dss_data, "transformer") @test dss_data["transformer"]["reg4b"]["%loadloss"] == dss_data["transformer"]["reg4a"]["%loadloss"] - eng_data = parse_file("../test/data/opendss/test_transformer_formatting.dss"; data_model="engineering") - @test all(all(eng_data["transformer"]["$n"]["tm"] .== tm) for (n, tm) in zip(["transformer_test", "reg4"], [[fill(1.075, 3), fill(1.5, 3), fill(0.9, 3)], [ones(3), ones(3)]])) + eng_data = parse_file("../test/data/opendss/test_transformer_formatting.dss") + @test all(all(eng_data["transformer"]["$n"]["tm_set"] .== tm) for (n, tm) in zip(["transformer_test", "reg4"], [[fill(1.075, 3), fill(1.5, 3), fill(0.9, 3)], [ones(3), ones(3)]])) end @testset "opendss parse storage" begin - math_storage = parse_file("../test/data/opendss/case3_balanced_battery.dss"; data_model="mathematical") + math_storage = parse_file("../test/data/opendss/case3_balanced_battery.dss"; data_model=MATHEMATICAL) for bat in values(math_storage["storage"]) for key in ["energy", "storage_bus", "energy_rating", "charge_rating", "discharge_rating", "charge_efficiency", "discharge_efficiency", "thermal_rating", "qmin", "qmax", @@ -250,8 +250,8 @@ end @testset "opendss parse verify order of properties on line" begin - math1 = parse_file("../test/data/opendss/case3_balanced.dss"; data_model="mathematical") - math2 = parse_file("../test/data/opendss/case3_balanced_prop-order.dss"; data_model="mathematical") + math1 = parse_file("../test/data/opendss/case3_balanced.dss"; data_model=MATHEMATICAL) + math2 = parse_file("../test/data/opendss/case3_balanced_prop-order.dss"; data_model=MATHEMATICAL) delete!(math1, "map") delete!(math2, "map") diff --git a/test/pf.jl b/test/pf.jl index 026b41c04..91efe1e35 100644 --- a/test/pf.jl +++ b/test/pf.jl @@ -153,7 +153,7 @@ end @testset "virtual sourcebus creation acp pf" begin - pmd = parse_file("../test/data/opendss/virtual_sourcebus.dss"; data_model="mathematical") + pmd = parse_file("../test/data/opendss/virtual_sourcebus.dss"; data_model=MATHEMATICAL) result = run_ac_mc_pf(pmd, ipopt_solver) @test result["termination_status"] == LOCALLY_SOLVED diff --git a/test/shunt.jl b/test/shunt.jl index 57bf5b9fb..e49766d1d 100644 --- a/test/shunt.jl +++ b/test/shunt.jl @@ -1,7 +1,7 @@ @info "running matrix shunt tests" @testset "matrix shunts ACP/ACR/IVR" begin - data = parse_file("data/opendss/case_mxshunt_2.dss"; data_model="mathematical") + data = parse_file("data/opendss/case_mxshunt_2.dss"; data_model=MATHEMATICAL) shunt = data["shunt"]["1"] @test(isa(shunt["gs"], Matrix)) @test(isa(shunt["bs"], Matrix)) From 98c24a23178d323195e37633dfb17fd04280d705 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 23 Apr 2020 14:40:56 -0600 Subject: [PATCH 168/224] ADD: changelog --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7db1de35..9ec80e15f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ ## staged +- none + +## v0.9.0 + +- +- Adds some commonly used InfrastructureModels and PowerModels functions as exports +- Adds model building functions `add_{component}!` to aid in building simple models for testing (experimental) +- Add run_mc_model (adds ref_add_arcs_transformer! to ref_extensions, and sets multiconductor=true by default) (breaking) +- Rename ref_add_arcs_trans -> ref_add_arcs_transformer (breaking) +- Update `count_nodes`, now counts source nodes as well, excludes _virtual objects +- Change _PMs and _IMs to _PM, _IM, respectively +- Add example for PMD usage (see Jupyter notebooks in `/examples`) +- Update transformer mathematical model +- Introduce new data models: ENGINEERING, MATHEMATICAL (see data model documentation) (breaking) +- Update DSS parser to be more robust, and parse into new format (breaking) +- Updates DSS paser to parse more options/commands, moves these into `"options"` dict (breaking) +- Updates how dss `like` is applied to better match opendss (almost all properties are copied with like) (breaking) +- Add support for new OpenDSS components (loadshape, xfmrcode, xycurve) +- Add support for JuMP v0.22 (exports optimizer_with_attributtes by default) +- Add support for InfrastructureModels v0.5 +- Add support for PowerModels v0.16 (breaking) - Add support for Memento v0.13, v1.0 ## v0.8.1 From eadfe1205aee094e0372d12f31b30cf1a471359d Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 23 Apr 2020 14:41:55 -0600 Subject: [PATCH 169/224] UPD: PowerModels v0.16 in Project.toml --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 59792c8c7..dced6aa95 100644 --- a/Project.toml +++ b/Project.toml @@ -2,7 +2,7 @@ name = "PowerModelsDistribution" uuid = "d7431456-977f-11e9-2de3-97ff7677985e" authors = ["David M Fobes ", "Carleton Coffrin"] repo = "https://github.com/lanl-ansi/PowerModelsDistribution.jl.git" -version = "0.8.1" +version = "0.9.0" [deps] Dierckx = "39dd38d3-220a-591b-8e3c-4c3a8c710a94" @@ -25,7 +25,7 @@ JuMP = "~0.19.2, ~0.20, ~0.21" Juniper = ">= 0.4" MathOptInterface = "~0.8, ~0.9" Memento = "~0.10, ~0.11, ~0.12, ~0.13, ~1.0" -PowerModels = "~0.15.4" +PowerModels = "~0.16" SCS = ">= 0.4" julia = "^1" From aac6af56b0370970e113cabdcb56782fc5b9f450 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 23 Apr 2020 14:51:48 -0600 Subject: [PATCH 170/224] UPD: Julia v1.4 travis testing --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 53958e0bc..091430536 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ os: - windows julia: - 1.0 - - 1.3 + - 1.4 - nightly codecov: true jobs: @@ -13,7 +13,7 @@ jobs: - julia: nightly include: - stage: "Documentation" - julia: 1.3 + julia: 1.4 os: linux script: - julia --project=docs/ -e 'using Pkg; Pkg.instantiate(); Pkg.develop(PackageSpec(path=pwd()))' From 4759e12db368fc0e0409adca574314902967643e Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 23 Apr 2020 17:15:05 -0600 Subject: [PATCH 171/224] UPD: PowerModels v0.17 Catalog of name changes will be in an issue Updated storage variables to include qsc --- CHANGELOG.md | 7 +- Project.toml | 6 +- src/core/constraint.jl | 15 +-- src/core/constraint_template.jl | 124 ++++++++++++------------ src/core/objective.jl | 6 +- src/core/variable.jl | 163 ++++++++++++++++++++------------ src/form/acp.jl | 44 +++++---- src/form/acr.jl | 21 ++-- src/form/apo.jl | 59 +++++------- src/form/bf.jl | 4 +- src/form/bf_mx.jl | 43 +++++---- src/form/bf_mx_lin.jl | 12 +-- src/form/bf_mx_sdp.jl | 1 - src/form/dcp.jl | 15 ++- src/form/ivr.jl | 73 +++++++------- src/form/shared.jl | 16 ++-- src/form/wr.jl | 58 ------------ src/prob/debug.jl | 34 +++---- src/prob/mld.jl | 85 ++++++++--------- src/prob/opf.jl | 22 ++--- src/prob/opf_bf.jl | 12 +-- src/prob/opf_bf_lm.jl | 16 ++-- src/prob/opf_iv.jl | 16 ++-- src/prob/opf_oltc.jl | 20 ++-- src/prob/pf.jl | 24 ++--- src/prob/pf_bf.jl | 14 +-- src/prob/pf_iv.jl | 22 ++--- src/prob/test.jl | 123 +++++------------------- test/mld.jl | 3 +- 29 files changed, 488 insertions(+), 570 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec80e15f..389a74f34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,11 @@ ## v0.9.0 -- +- Add support for Memento 1.1 +- Add support for PowerModels v0.17 (breaking) +- Add support for InfrastructureModels v0.5 +- Updates JSON parser to handle enum (`"data_model"` values) +- Adds Dierckx dependency for creation of 1d splines for xycurve object - Adds some commonly used InfrastructureModels and PowerModels functions as exports - Adds model building functions `add_{component}!` to aid in building simple models for testing (experimental) - Add run_mc_model (adds ref_add_arcs_transformer! to ref_extensions, and sets multiconductor=true by default) (breaking) @@ -21,7 +25,6 @@ - Updates how dss `like` is applied to better match opendss (almost all properties are copied with like) (breaking) - Add support for new OpenDSS components (loadshape, xfmrcode, xycurve) - Add support for JuMP v0.22 (exports optimizer_with_attributtes by default) -- Add support for InfrastructureModels v0.5 - Add support for PowerModels v0.16 (breaking) - Add support for Memento v0.13, v1.0 diff --git a/Project.toml b/Project.toml index dced6aa95..ea5242eb2 100644 --- a/Project.toml +++ b/Project.toml @@ -18,14 +18,14 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] Cbc = ">= 0.4" Dierckx = "~0.4" -InfrastructureModels = "~0.4.3" +InfrastructureModels = "~0.5" Ipopt = ">= 0.4" JSON = "~0.18, ~0.19, ~0.20, ~0.21" JuMP = "~0.19.2, ~0.20, ~0.21" Juniper = ">= 0.4" MathOptInterface = "~0.8, ~0.9" -Memento = "~0.10, ~0.11, ~0.12, ~0.13, ~1.0" -PowerModels = "~0.16" +Memento = "~0.10, ~0.11, ~0.12, ~0.13, ~1.0, ~1.1" +PowerModels = "~0.17" SCS = ">= 0.4" julia = "^1" diff --git a/src/core/constraint.jl b/src/core/constraint.jl index c5b7e7cd2..6a6703dd5 100644 --- a/src/core/constraint.jl +++ b/src/core/constraint.jl @@ -2,8 +2,8 @@ function constraint_mc_model_voltage(pm::_PM.AbstractPowerModel, n::Int) end -# Generic thermal limit constraint -"" + +"Generic thermal limit constraint from-side" function constraint_mc_thermal_limit_from(pm::_PM.AbstractPowerModel, n::Int, f_idx, rate_a) p_fr = var(pm, n, :p, f_idx) q_fr = var(pm, n, :q, f_idx) @@ -15,7 +15,8 @@ function constraint_mc_thermal_limit_from(pm::_PM.AbstractPowerModel, n::Int, f_ end end -"" + +"Generic thermal limit constraint to-side" function constraint_mc_thermal_limit_to(pm::_PM.AbstractPowerModel, n::Int, t_idx, rate_a) p_to = var(pm, n, :p, t_idx) q_to = var(pm, n, :q, t_idx) @@ -29,7 +30,7 @@ end "on/off bus voltage magnitude constraint" -function constraint_mc_voltage_magnitude_on_off(pm::_PM.AbstractPowerModel, n::Int, i::Int, vmin, vmax) +function constraint_mc_bus_voltage_magnitude_on_off(pm::_PM.AbstractPowerModel, n::Int, i::Int, vmin, vmax) vm = var(pm, n, :vm, i) z_voltage = var(pm, n, :z_voltage, i) @@ -46,7 +47,7 @@ end "on/off bus voltage magnitude squared constraint for relaxed formulations" -function constraint_mc_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel, n::Int, i::Int, vmin, vmax) +function constraint_mc_bus_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel, n::Int, i::Int, vmin, vmax) w = var(pm, n, :w, i) z_voltage = var(pm, n, :z_voltage, i) @@ -62,14 +63,14 @@ function constraint_mc_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel, end -function constraint_mc_active_gen_setpoint(pm::_PM.AbstractPowerModel, n::Int, i, pg) +function constraint_mc_gen_power_setpoint_real(pm::_PM.AbstractPowerModel, n::Int, i, pg) pg_var = var(pm, n, :pg, i) JuMP.@constraint(pm.model, pg_var .== pg) end "on/off constraint for generators" -function constraint_mc_generation_on_off(pm::_PM.AbstractPowerModel, n::Int, i::Int, pmin, pmax, qmin, qmax) +function constraint_mc_gen_power_on_off(pm::_PM.AbstractPowerModel, n::Int, i::Int, pmin, pmax, qmin, qmax) pg = var(pm, n, :pg, i) qg = var(pm, n, :qg, i) z = var(pm, n, :z_gen, i) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index c01fae1ce..7ce0d4041 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -6,7 +6,7 @@ end "" -function constraint_mc_power_balance_slack(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_slack_power_balance(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) bus = ref(pm, nw, :bus, i) bus_arcs = ref(pm, nw, :bus_arcs, i) bus_arcs_sw = ref(pm, nw, :bus_arcs_sw, i) @@ -22,7 +22,7 @@ function constraint_mc_power_balance_slack(pm::_PM.AbstractPowerModel, i::Int; n bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) - constraint_mc_power_balance_slack(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + constraint_mc_slack_power_balance(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) end @@ -102,7 +102,7 @@ end "" -function constraint_mc_flow_losses(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_power_losses(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] t_bus = branch["t_bus"] @@ -118,40 +118,38 @@ function constraint_mc_flow_losses(pm::_PM.AbstractPowerModel, i::Int; nw::Int=p tm = [1, 1, 1] #TODO - constraint_mc_flow_losses(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm) + constraint_mc_power_losses(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm) end "Transformer constraints, considering winding type, conductor order, polarity and tap settings." -function constraint_mc_trans(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, fix_taps::Bool=true) +function constraint_mc_transformer_power(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, fix_taps::Bool=true) if ref(pm, pm.cnw, :conductors)!=3 Memento.error(_LOGGER, "Transformers only work with networks with three conductors.") end - trans = ref(pm, :transformer, i) - f_bus = trans["f_bus"] - t_bus = trans["t_bus"] + transformer = ref(pm, :transformer, i) + f_bus = transformer["f_bus"] + t_bus = transformer["t_bus"] f_idx = (i, f_bus, t_bus) t_idx = (i, t_bus, f_bus) - config = trans["configuration"] - type = trans["configuration"] - f_cnd = trans["f_connections"][1:3] - t_cnd = trans["t_connections"][1:3] - tm_set = trans["tm_set"] - tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : trans["tm_fix"] - tm_scale = calculate_tm_scale(trans, ref(pm, nw, :bus, f_bus), ref(pm, nw, :bus, t_bus)) + config = transformer["configuration"] + f_cnd = transformer["f_connections"][1:3] + t_cnd = transformer["t_connections"][1:3] + tm_set = transformer["tm_set"] + tm_fixed = fix_taps ? ones(Bool, length(tm_set)) : transformer["tm_fix"] + tm_scale = calculate_tm_scale(transformer, ref(pm, nw, :bus, f_bus), ref(pm, nw, :bus, t_bus)) #TODO change data model # there is redundancy in specifying polarity seperately on from and to side #TODO change this once migrated to new data model - pol = trans["polarity"] + pol = transformer["polarity"] - if config=="wye" - constraint_mc_trans_yy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) - elseif config=="delta" - constraint_mc_trans_dy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) - end - if type=="zig-zag" + if config == "wye" + constraint_mc_transformer_power_yy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) + elseif config == "delta" + constraint_mc_transformer_power_dy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) + elseif config == "zig-zag" Memento.error(_LOGGER, "Zig-zag not yet supported.") end end @@ -190,37 +188,37 @@ For a discussion of sequence components and voltage unbalance factor (VUF), see url={https://molzahn.github.io/pubs/girigoudar_molzahn_roald-2019.pdf} } """ -function constraint_mc_voltage_balance(pm::_PM.AbstractPowerModel, bus_id::Int; nw=pm.cnw) +function constraint_mc_bus_voltage_balance(pm::_PM.AbstractPowerModel, bus_id::Int; nw=pm.cnw) @assert(ref(pm, nw, :conductors)==3) bus = ref(pm, nw, :bus, bus_id) if haskey(bus, "vm_vuf_max") - constraint_mc_vm_vuf(pm, nw, bus_id, bus["vm_vuf_max"]) + constraint_mc_bus_voltage_magnitude_vuf(pm, nw, bus_id, bus["vm_vuf_max"]) end if haskey(bus, "vm_seq_neg_max") - constraint_mc_vm_neg_seq(pm, nw, bus_id, bus["vm_seq_neg_max"]) + constraint_mc_bus_voltage_magnitude_negative_sequence(pm, nw, bus_id, bus["vm_seq_neg_max"]) end if haskey(bus, "vm_seq_pos_max") - constraint_mc_vm_pos_seq(pm, nw, bus_id, bus["vm_seq_pos_max"]) + constraint_mc_bus_voltage_magnitude_positive_sequence(pm, nw, bus_id, bus["vm_seq_pos_max"]) end if haskey(bus, "vm_seq_zero_max") - constraint_mc_vm_zero_seq(pm, nw, bus_id, bus["vm_seq_zero_max"]) + constraint_mc_bus_voltage_magnitude_zero_sequence(pm, nw, bus_id, bus["vm_seq_zero_max"]) end if haskey(bus, "vm_ll_min")|| haskey(bus, "vm_ll_max") vm_ll_min = haskey(bus, "vm_ll_min") ? bus["vm_ll_min"] : fill(0, 3) vm_ll_max = haskey(bus, "vm_ll_max") ? bus["vm_ll_max"] : fill(Inf, 3) - constraint_mc_vm_ll(pm, nw, bus_id, vm_ll_min, vm_ll_max) + constraint_mc_bus_voltage_magnitude_ll(pm, nw, bus_id, vm_ll_min, vm_ll_max) end end "KCL including transformer arcs and load variables." -function constraint_mc_power_balance_load(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_load_power_balance(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) bus = ref(pm, nw, :bus, i) bus_arcs = ref(pm, nw, :bus_arcs, i) bus_arcs_sw = ref(pm, nw, :bus_arcs_sw, i) @@ -233,7 +231,7 @@ function constraint_mc_power_balance_load(pm::_PM.AbstractPowerModel, i::Int; nw bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) - constraint_mc_power_balance_load(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) + constraint_mc_load_power_balance(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) end @@ -271,7 +269,7 @@ sn_a = v_a.conj(i_a) = v_a.(s_ab/(v_a-v_b) - s_ca/(v_c-v_a)) So for delta, sn is constrained indirectly. """ -function constraint_mc_load(pm::_PM.AbstractPowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) +function constraint_mc_load_setpoint(pm::_PM.AbstractPowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) load = ref(pm, nw, :load, id) bus = ref(pm, nw,:bus, load["load_bus"]) @@ -280,9 +278,9 @@ function constraint_mc_load(pm::_PM.AbstractPowerModel, id::Int; nw::Int=pm.cnw, a, alpha, b, beta = _load_expmodel_params(load, bus) if conn=="wye" - constraint_mc_load_wye(pm, nw, id, load["load_bus"], a, alpha, b, beta) + constraint_mc_load_setpoint_wye(pm, nw, id, load["load_bus"], a, alpha, b, beta) else - constraint_mc_load_delta(pm, nw, id, load["load_bus"], a, alpha, b, beta) + constraint_mc_load_setpoint_delta(pm, nw, id, load["load_bus"], a, alpha, b, beta) end end @@ -299,7 +297,7 @@ sn_a = v_a.conj(i_a) = v_a.(s_ab/(v_a-v_b) - s_ca/(v_c-v_a)) So for delta, sn is constrained indirectly. """ -function constraint_mc_generation(pm::_PM.AbstractPowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true, bounded::Bool=true) +function constraint_mc_gen_setpoint(pm::_PM.AbstractPowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true, bounded::Bool=true) generator = ref(pm, nw, :gen, id) bus = ref(pm, nw,:bus, generator["gen_bus"]) @@ -310,15 +308,15 @@ function constraint_mc_generation(pm::_PM.AbstractPowerModel, id::Int; nw::Int=p qmax = get(generator, "qmax", fill( Inf, N)) if get(generator, "configuration", "wye") == "wye" - constraint_mc_generation_wye(pm, nw, id, bus["index"], pmin, pmax, qmin, qmax; report=report, bounded=bounded) + constraint_mc_gen_setpoint_wye(pm, nw, id, bus["index"], pmin, pmax, qmin, qmax; report=report, bounded=bounded) else - constraint_mc_generation_delta(pm, nw, id, bus["index"], pmin, pmax, qmin, qmax; report=report, bounded=bounded) + constraint_mc_gen_setpoint_delta(pm, nw, id, bus["index"], pmin, pmax, qmin, qmax; report=report, bounded=bounded) end end "KCL for load shed problem with transformers" -function constraint_mc_power_balance_shed(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_shed_power_balance(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) bus = ref(pm, nw, :bus, i) bus_arcs = ref(pm, nw, :bus_arcs, i) bus_arcs_sw = ref(pm, nw, :bus_arcs_sw, i) @@ -334,7 +332,7 @@ function constraint_mc_power_balance_shed(pm::_PM.AbstractPowerModel, i::Int; nw bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) - constraint_mc_power_balance_shed(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + constraint_mc_shed_power_balance(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) end @@ -345,18 +343,18 @@ end "on/off voltage magnitude constraint" -function constraint_mc_voltage_magnitude_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_bus_voltage_magnitude_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) bus = ref(pm, nw, :bus, i) - constraint_mc_voltage_magnitude_on_off(pm, nw, i, bus["vmin"], bus["vmax"]) + constraint_mc_bus_voltage_magnitude_on_off(pm, nw, i, bus["vmin"], bus["vmax"]) end "on/off voltage magnitude squared constraint for relaxed formulations" -function constraint_mc_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_bus_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) bus = ref(pm, nw, :bus, i) - constraint_mc_voltage_magnitude_sqr_on_off(pm, nw, i, bus["vmin"], bus["vmax"]) + constraint_mc_bus_voltage_magnitude_sqr_on_off(pm, nw, i, bus["vmin"], bus["vmax"]) end @@ -373,10 +371,10 @@ end "storage loss constraints, delegate to PowerModels" -function constraint_mc_storage_loss(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) +function constraint_mc_storage_losses(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) storage = ref(pm, nw, :storage, i) - _PM.constraint_storage_loss(pm, nw, i, storage["storage_bus"], storage["r"], storage["x"], storage["p_loss"], storage["q_loss"]; + _PM.constraint_storage_losses(pm, nw, i, storage["storage_bus"], storage["r"], storage["x"], storage["p_loss"], storage["q_loss"]; conductors = conductor_ids(pm, nw) ) end @@ -409,17 +407,17 @@ end "voltage magnitude setpoint constraint" -function constraint_mc_voltage_magnitude_setpoint(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) +function constraint_mc_voltage_magnitude_only(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) bus = ref(pm, nw, :bus, i) vmref = bus["vm"] #Not sure why this is needed - constraint_mc_voltage_magnitude_setpoint(pm, nw, i, vmref) + constraint_mc_voltage_magnitude_only(pm, nw, i, vmref) end "generator active power setpoint constraint" -function constraint_mc_active_gen_setpoint(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) +function constraint_mc_gen_power_setpoint_real(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw, kwargs...) pg_set = ref(pm, nw, :gen, i)["pg"] - constraint_mc_active_gen_setpoint(pm, nw, i, pg_set) + constraint_mc_gen_power_setpoint_real(pm, nw, i, pg_set) end @@ -436,7 +434,8 @@ function constraint_mc_voltage_magnitude_bounds(pm::_PM.AbstractPowerModel, i::I end -function constraint_mc_generation_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +"" +function constraint_mc_gen_power_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) gen = ref(pm, nw, :gen, i) ncnds = length(conductor_ids(pm; nw=nw)) @@ -445,21 +444,24 @@ function constraint_mc_generation_on_off(pm::_PM.AbstractPowerModel, i::Int; nw: qmin = get(gen, "qmin", fill(-Inf, ncnds)) qmax = get(gen, "qmax", fill( Inf, ncnds)) - constraint_mc_generation_on_off(pm, nw, i, pmin, pmax, qmin, qmax) + constraint_mc_gen_power_on_off(pm, nw, i, pmin, pmax, qmin, qmax) end + "" function constraint_mc_storage_thermal_limit(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) storage = ref(pm, nw, :storage, i) constraint_mc_storage_thermal_limit(pm, nw, i, storage["thermal_rating"]) end + "" function constraint_mc_storage_current_limit(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) storage = ref(pm, nw, :storage, i) constraint_mc_storage_current_limit(pm, nw, i, storage["storage_bus"], storage["current_rating"]) end + "" function constraint_mc_storage_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) storage = ref(pm, nw, :storage, i) @@ -484,21 +486,25 @@ function constraint_mc_storage_on_off(pm::_PM.AbstractPowerModel, i::Int; nw::In constraint_mc_storage_on_off(pm, nw, i, pmin, pmax, qmin, qmax, charge_ub, discharge_ub) end + "defines limits on active power output of a generator where bounds can't be used" -function constraint_mc_generation_active_power_limits(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_gen_active_bounds(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) gen = ref(pm, nw, :gen, i) bus = gen["gen_bus"] - constraint_mc_generation_active_power_limits(pm, nw, i, bus, gen["pmax"], gen["pmin"]) + constraint_mc_gen_active_bounds(pm, nw, i, bus, gen["pmax"], gen["pmin"]) end + "defines limits on reactive power output of a generator where bounds can't be used" -function constraint_mc_generation_reactive_power_limits(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_gen_reactive_bounds(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) gen = ref(pm, nw, :gen, i) bus = gen["gen_bus"] - constraint_mc_generation_reactive_power_limits(pm, nw, i, bus, gen["qmax"], gen["qmin"]) + constraint_mc_gen_reactive_bounds(pm, nw, i, bus, gen["qmax"], gen["qmin"]) end + + "" -function constraint_mc_current_balance_load(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_load_current_balance(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) bus = ref(pm, nw, :bus, i) bus_arcs = ref(pm, nw, :bus_arcs, i) bus_arcs_sw = ref(pm, nw, :bus_arcs_sw, i) @@ -511,7 +517,7 @@ function constraint_mc_current_balance_load(pm::_PM.AbstractPowerModel, i::Int; bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) - constraint_mc_current_balance_load(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) + constraint_mc_load_current_balance(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) end @@ -530,6 +536,7 @@ function constraint_mc_current_from(pm::_PM.AbstractIVRModel, i::Int; nw::Int=pm constraint_mc_current_from(pm, nw, f_bus, f_idx, g_fr, b_fr, tr, ti, tm) end + "" function constraint_mc_current_to(pm::_PM.AbstractIVRModel, i::Int; nw::Int=pm.cnw) branch = ref(pm, nw, :branch, i) @@ -546,8 +553,9 @@ function constraint_mc_current_to(pm::_PM.AbstractIVRModel, i::Int; nw::Int=pm.c constraint_mc_current_to(pm, nw, t_bus, f_idx, t_idx, g_to, b_to) end + "" -function constraint_mc_voltage_drop(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_bus_voltage_drop(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] t_bus = branch["t_bus"] @@ -558,5 +566,5 @@ function constraint_mc_voltage_drop(pm::_PM.AbstractPowerModel, i::Int; nw::Int= x = branch["br_x"] tm = branch["tap"] - constraint_mc_voltage_drop(pm, nw, i, f_bus, t_bus, f_idx, r, x, tr, ti, tm) + constraint_mc_bus_voltage_drop(pm, nw, i, f_bus, t_bus, f_idx, r, x, tr, ti, tm) end diff --git a/src/core/objective.jl b/src/core/objective.jl index cdd9ff607..71b7b27f6 100644 --- a/src/core/objective.jl +++ b/src/core/objective.jl @@ -1,5 +1,5 @@ "a quadratic penalty for bus power slack variables" -function objective_min_bus_power_slack(pm::_PM.AbstractPowerModel) +function objective_mc_min_slack_bus_power(pm::_PM.AbstractPowerModel) return JuMP.@objective(pm.model, Min, sum( sum( @@ -11,7 +11,7 @@ end "minimum load delta objective (continuous load shed) with storage" -function objective_mc_min_load_delta(pm::_PM.AbstractPowerModel) +function objective_mc_min_load_setpoint_delta(pm::_PM.AbstractPowerModel) for (n, nw_ref) in nws(pm) var(pm, n)[:delta_pg] = Dict(i => JuMP.@variable(pm.model, [c in conductor_ids(pm, n)], base_name="$(n)_$(i)_delta_pg", @@ -54,7 +54,7 @@ end "maximum loadability objective (continuous load shed) with storage" -function objective_mc_max_loadability(pm::_PM.AbstractPowerModel) +function objective_mc_max_load_setpoint(pm::_PM.AbstractPowerModel) load_weight = Dict(n => Dict(i => get(load, "weight", 1.0) for (i,load) in ref(pm, n, :load)) for n in nw_ids(pm)) M = Dict(n => Dict(c => 10*maximum([load_weight[n][i]*abs(load["pd"][c]) for (i,load) in ref(pm, n, :load)]) for c in conductor_ids(pm, n)) for n in nw_ids(pm)) diff --git a/src/core/variable.jl b/src/core/variable.jl index 8ed5c748b..cf3218e1c 100644 --- a/src/core/variable.jl +++ b/src/core/variable.jl @@ -14,7 +14,7 @@ end "" -function variable_mc_voltage_angle(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_bus_voltage_angle(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -28,7 +28,7 @@ function variable_mc_voltage_angle(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, b end "" -function variable_mc_voltage_magnitude(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_bus_voltage_magnitude_only(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -53,7 +53,7 @@ function variable_mc_voltage_magnitude(pm::_PM.AbstractPowerModel; nw::Int=pm.cn end "" -function variable_mc_voltage_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_bus_voltage_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -76,7 +76,7 @@ function variable_mc_voltage_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bo end "" -function variable_mc_voltage_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_bus_voltage_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -100,13 +100,13 @@ end "branch flow variables, delegated back to PowerModels" -function variable_mc_branch_flow(pm::_PM.AbstractPowerModel; kwargs...) - variable_mc_branch_flow_active(pm; kwargs...) - variable_mc_branch_flow_reactive(pm; kwargs...) +function variable_mc_branch_power(pm::_PM.AbstractPowerModel; kwargs...) + variable_mc_branch_power_real(pm; kwargs...) + variable_mc_branch_power_imaginary(pm; kwargs...) end "variable: `p[l,i,j]` for `(l,i,j)` in `arcs`" -function variable_mc_branch_flow_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_branch_power_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -139,7 +139,7 @@ function variable_mc_branch_flow_active(pm::_PM.AbstractPowerModel; nw::Int=pm.c end "variable: `q[l,i,j]` for `(l,i,j)` in `arcs`" -function variable_mc_branch_flow_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_branch_power_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -223,7 +223,7 @@ end "variable: `csr[l]` for `l` in `branch`" -function variable_mc_branch_series_current_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_branch_current_series_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) branch = ref(pm, nw, :branch) bus = ref(pm, nw, :bus) cnds = conductor_ids(pm; nw=nw) @@ -248,7 +248,7 @@ end "variable: `csi[l]` for `l` in `branch`" -function variable_mc_branch_series_current_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_branch_current_series_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) branch = ref(pm, nw, :branch) bus = ref(pm, nw, :bus) cnds = conductor_ids(pm; nw=nw) @@ -332,16 +332,8 @@ function variable_mc_transformer_current_imaginary(pm::_PM.AbstractPowerModel; n end - -# "voltage variables, relaxed form" -# function variable_mc_voltage(pm::_PM.AbstractWRModel; kwargs...) -# variable_mc_voltage_magnitude_sqr(pm; kwargs...) -# variable_mc_voltage_product(pm; kwargs...) -# end - - "variable: `w[i] >= 0` for `i` in `buses" -function variable_mc_voltage_magnitude_sqr(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_bus_voltage_magnitude_sqr(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -368,16 +360,62 @@ end "variables for modeling storage units, includes grid injection and internal variables" -function variable_mc_storage(pm::_PM.AbstractPowerModel; kwargs...) - variable_mc_storage_active(pm; kwargs...) - variable_mc_storage_reactive(pm; kwargs...) +function variable_mc_storage_power(pm::_PM.AbstractPowerModel; kwargs...) + variable_mc_storage_power_real(pm; kwargs...) + variable_mc_storage_power_imaginary(pm; kwargs...) + variable_mc_storage_power_control_imaginary(pm; kwargs...) + _PM.variable_storage_current(pm; kwargs...) # TODO storage current variable for multiconductor + _PM.variable_storage_energy(pm; kwargs...) + _PM.variable_storage_charge(pm; kwargs...) + _PM.variable_storage_discharge(pm; kwargs...) +end + +"" +function variable_mc_storage_power_mi(pm::_PM.AbstractPowerModel; relax::Bool=false, kwargs...) + _PM.variable_storage_current(pm; kwargs...) # TODO storage current variable for multiconductor _PM.variable_storage_energy(pm; kwargs...) _PM.variable_storage_charge(pm; kwargs...) _PM.variable_storage_discharge(pm; kwargs...) + variable_mc_storage_indicator(pm; relax=relax, kwargs...) + variable_mc_storage_power_on_off(pm; kwargs...) + variable_mc_storage_power_control_imaginary(pm; kwargs...) +end + + +""" +a reactive power slack variable that enables the storage device to inject or +consume reactive power at its connecting bus, subject to the injection limits +of the device. +""" +function variable_mc_storage_power_control_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + # TODO properly adapt this new control variable to multiconductor + cnds = conductor_ids(pm; nw=nw) + ncnds = length(cnds) + + qsc = var(pm, nw)[:qsc] = JuMP.@variable(pm.model, + [i in ids(pm, nw, :storage)], base_name="$(nw)_qsc_$(i)", + start = _PM.comp_start_value(ref(pm, nw, :storage, i), "qsc_start") + ) + + if bounded + inj_lb, inj_ub = _PM.ref_calc_storage_injection_bounds(ref(pm, nw, :storage), ref(pm, nw, :bus)) + for (i,storage) in ref(pm, nw, :storage) + if !isinf(sum(inj_lb[i])) || haskey(storage, "qmin") + set_lower_bound(qsc[i], max(sum(inj_lb[i]), sum(get(storage, "qmin", -Inf)))) + end + if !isinf(sum(inj_ub[i])) || haskey(storage, "qmax") + set_upper_bound(qsc[i], min(sum(inj_ub[i]), sum(get(storage, "qmax", Inf)))) + end + end + end + + report && _IM.sol_component_value(pm, nw, :storage, :qsc, ids(pm, nw, :storage), qsc) end -function variable_mc_storage_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + +"" +function variable_mc_storage_power_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -405,7 +443,9 @@ function variable_mc_storage_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, report && _IM.sol_component_value(pm, nw, :storage, :ps, ids(pm, nw, :storage), ps) end -function variable_mc_storage_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) + +"" +function variable_mc_storage_power_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -434,16 +474,15 @@ function variable_mc_storage_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw end - "generates variables for both `active` and `reactive` slack at each bus" -function variable_mc_bus_power_slack(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) - variable_mc_active_bus_power_slack(pm; nw=nw, kwargs...) - variable_mc_reactive_bus_power_slack(pm; nw=nw, kwargs...) +function variable_mc_slack_bus_power(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) + variable_mc_slack_bus_power_real(pm; nw=nw, kwargs...) + variable_mc_slack_bus_power_imaginary(pm; nw=nw, kwargs...) end "" -function variable_mc_active_bus_power_slack(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) +function variable_mc_slack_bus_power_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -458,7 +497,7 @@ end "" -function variable_mc_reactive_bus_power_slack(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) +function variable_mc_slack_bus_power_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -473,14 +512,14 @@ end "Creates variables for both `active` and `reactive` power flow at each transformer." -function variable_mc_transformer_flow(pm::_PM.AbstractPowerModel; kwargs...) - variable_mc_transformer_flow_active(pm; kwargs...) - variable_mc_transformer_flow_reactive(pm; kwargs...) +function variable_mc_transformer_power(pm::_PM.AbstractPowerModel; kwargs...) + variable_mc_transformer_power_real(pm; kwargs...) + variable_mc_transformer_power_imaginary(pm; kwargs...) end "Create variables for the active power flowing into all transformer windings." -function variable_mc_transformer_flow_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_transformer_power_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -516,7 +555,7 @@ end "Create variables for the reactive power flowing into all transformer windings." -function variable_mc_transformer_flow_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_transformer_power_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -554,7 +593,7 @@ end "Create tap variables." -function variable_mc_oltc_tap(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_oltc_transformer_tap(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) # when extending to 4-wire, this should iterate only over the phase conductors cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -582,7 +621,7 @@ Create a dictionary with values of type Any for the load. Depending on the load model, this can be a parameter or a NLexpression. These will be inserted into KCL. """ -function variable_mc_load(pm::_PM.AbstractPowerModel; nw=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_load_setpoint(pm::_PM.AbstractPowerModel; nw=pm.cnw, bounded::Bool=true, report::Bool=true) var(pm, nw)[:pd] = Dict{Int, Any}() var(pm, nw)[:qd] = Dict{Int, Any}() var(pm, nw)[:pd_bus] = Dict{Int, Any}() @@ -591,7 +630,7 @@ end "Create variables for demand status" -function variable_mc_indicator_demand(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) +function variable_mc_load_indicator(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) # this is not indexedon cnd; why used in start value? cnd = 1 if relax @@ -622,7 +661,7 @@ end "Create variables for shunt status" -function variable_mc_indicator_shunt(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax=false, report::Bool=true) +function variable_mc_shunt_indicator(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax=false, report::Bool=true) # this is not indexedon cnd; why used in start value? cnd = 1 if relax @@ -645,7 +684,7 @@ end "Create variables for bus status" -function variable_mc_indicator_bus_voltage(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) +function variable_mc_bus_voltage_indicator(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) if !relax z_voltage = var(pm, nw)[:z_voltage] = JuMP.@variable(pm.model, [i in ids(pm, nw, :bus)], base_name="$(nw)_z_voltage", @@ -666,7 +705,7 @@ end "Create variables for generator status" -function variable_mc_indicator_generation(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) +function variable_mc_gen_indicator(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) if !relax z_gen = var(pm, nw)[:z_gen] = JuMP.@variable(pm.model, [i in ids(pm, nw, :gen)], base_name="$(nw)_z_gen", @@ -687,7 +726,7 @@ end "Create variables for storage status" -function variable_mc_indicator_storage(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) +function variable_mc_storage_indicator(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, relax::Bool=false, report::Bool=true) if !relax z_storage = var(pm, nw)[:z_storage] = JuMP.@variable(pm.model, [i in ids(pm, nw, :storage)], base_name="$(nw)-z_storage", @@ -708,14 +747,14 @@ end "Create variables for `active` and `reactive` storage injection" -function variable_mc_on_off_storage(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) - variable_mc_on_off_storage_active(pm; nw=nw, kwargs...) - variable_mc_on_off_storage_reactive(pm; nw=nw, kwargs...) +function variable_mc_storage_power_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, kwargs...) + variable_mc_storage_power_real_on_off(pm; nw=nw, kwargs...) + variable_mc_storage_power_imaginary_on_off(pm; nw=nw, kwargs...) end "Create variables for `active` storage injection" -function variable_mc_on_off_storage_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_storage_power_real_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -740,7 +779,7 @@ end "Create variables for `reactive` storage injection" -function variable_mc_on_off_storage_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_storage_power_imaginary_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -766,7 +805,7 @@ end "voltage variable magnitude squared (relaxed form)" -function variable_mc_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_bus_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -790,7 +829,7 @@ end "on/off voltage magnitude variable" -function variable_mc_voltage_magnitude_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_bus_voltage_magnitude_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -815,13 +854,13 @@ end "create variables for generators, delegate to PowerModels" -function variable_mc_generation(pm::_PM.AbstractPowerModel; kwargs...) - variable_mc_generation_active(pm; kwargs...) - variable_mc_generation_reactive(pm; kwargs...) +function variable_mc_gen_power_setpoint(pm::_PM.AbstractPowerModel; kwargs...) + variable_mc_gen_power_setpoint_real(pm; kwargs...) + variable_mc_gen_power_setpoint_imaginary(pm; kwargs...) end -function variable_mc_generation_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_gen_power_setpoint_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -848,7 +887,7 @@ function variable_mc_generation_active(pm::_PM.AbstractPowerModel; nw::Int=pm.cn end -function variable_mc_generation_reactive(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_gen_power_setpoint_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -876,7 +915,7 @@ end "variable: `crg[j]` for `j` in `gen`" -function variable_mc_generation_current_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_gen_current_setpoint_real(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) gen = ref(pm, nw, :gen) bus = ref(pm, nw, :bus) cnds = conductor_ids(pm; nw=nw) @@ -899,7 +938,7 @@ function variable_mc_generation_current_real(pm::_PM.AbstractPowerModel; nw::Int end "variable: `cig[j]` for `j` in `gen`" -function variable_mc_generation_current_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_gen_current_setpoint_imaginary(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) gen = ref(pm, nw, :gen) bus = ref(pm, nw, :bus) cnds = conductor_ids(pm; nw=nw) @@ -922,13 +961,13 @@ function variable_mc_generation_current_imaginary(pm::_PM.AbstractPowerModel; nw end -function variable_mc_generation_on_off(pm::_PM.AbstractPowerModel; kwargs...) - variable_mc_active_generation_on_off(pm; kwargs...) - variable_mc_reactive_generation_on_off(pm; kwargs...) +function variable_mc_gen_power_setpoint_on_off(pm::_PM.AbstractPowerModel; kwargs...) + variable_mc_gen_power_setpoint_real_on_off(pm; kwargs...) + variable_mc_gen_power_setpoint_imaginary_on_off(pm; kwargs...) end -function variable_mc_active_generation_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_gen_power_setpoint_real_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(conductor_ids(pm, nw)) @@ -953,7 +992,7 @@ function variable_mc_active_generation_on_off(pm::_PM.AbstractPowerModel; nw::In end -function variable_mc_reactive_generation_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_gen_power_setpoint_imaginary_on_off(pm::_PM.AbstractPowerModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) diff --git a/src/form/acp.jl b/src/form/acp.jl index 0760cbde7..cec2ed9f0 100644 --- a/src/form/acp.jl +++ b/src/form/acp.jl @@ -1,7 +1,7 @@ "" -function variable_mc_voltage(pm::_PM.AbstractACPModel; nw=pm.cnw, kwargs...) - variable_mc_voltage_angle(pm; nw=nw, kwargs...) - variable_mc_voltage_magnitude(pm; nw=nw, kwargs...) +function variable_mc_bus_voltage(pm::_PM.AbstractACPModel; nw=pm.cnw, kwargs...) + variable_mc_bus_voltage_angle(pm; nw=nw, kwargs...) + variable_mc_bus_voltage_magnitude_only(pm; nw=nw, kwargs...) # This is needed for delta loads, where division occurs by the difference # of voltage phasors. If the voltage phasors at one bus are initialized @@ -28,8 +28,8 @@ end "" function variable_mc_bus_voltage_on_off(pm::_PM.AbstractACPModel; kwargs...) - variable_mc_voltage_angle(pm; kwargs...) - variable_mc_voltage_magnitude_on_off(pm; kwargs...) + variable_mc_bus_voltage_angle(pm; kwargs...) + variable_mc_bus_voltage_magnitude_on_off(pm; kwargs...) nw = get(kwargs, :nw, pm.cnw) @@ -50,7 +50,7 @@ end "" -function constraint_mc_power_balance_slack(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) +function constraint_mc_slack_power_balance(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) vm = var(pm, nw, :vm, i) va = var(pm, nw, :va, i) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") @@ -122,7 +122,7 @@ end "" -function constraint_mc_power_balance_shed(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) +function constraint_mc_shed_power_balance(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) vm = var(pm, nw, :vm, i) va = var(pm, nw, :va, i) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") @@ -262,7 +262,7 @@ end "" -function constraint_mc_power_balance_load(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) +function constraint_mc_load_power_balance(pm::_PM.AbstractACPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) vm = var(pm, nw, :vm, i) va = var(pm, nw, :va, i) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") @@ -398,7 +398,8 @@ function constraint_mc_ohms_yt_to(pm::_PM.AbstractACPModel, n::Int, f_bus, t_bus end -function constraint_mc_trans_yy(pm::_PM.AbstractACPModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) +"" +function constraint_mc_transformer_power_yy(pm::_PM.AbstractACPModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) vm_fr = var(pm, nw, :vm, f_bus)[f_cnd] vm_to = var(pm, nw, :vm, t_bus)[t_cnd] va_fr = var(pm, nw, :va, f_bus)[f_cnd] @@ -428,7 +429,8 @@ function constraint_mc_trans_yy(pm::_PM.AbstractACPModel, nw::Int, trans_id::Int end -function constraint_mc_trans_dy(pm::_PM.AbstractACPModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) +"" +function constraint_mc_transformer_power_dy(pm::_PM.AbstractACPModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) vm_fr = [var(pm, nw, :vm, f_bus)[p] for p in f_cnd] vm_to = [var(pm, nw, :vm, t_bus)[p] for p in t_cnd] va_fr = [var(pm, nw, :va, f_bus)[p] for p in f_cnd] @@ -494,7 +496,7 @@ vuf = |U-|/|U+| |U-| <= vufmax*|U+| |U-|^2 <= vufmax^2*|U+|^2 """ -function constraint_mc_vm_vuf(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vufmax::Float64) +function constraint_mc_bus_voltage_magnitude_vuf(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vufmax::Float64) if !haskey(var(pm, pm.cnw), :vmpossqr) var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() @@ -541,7 +543,7 @@ vuf = |U-|/|U+| |U-| <= vufmax*|U+| |U-|^2 <= vufmax^2*|U+|^2 """ -function constraint_mc_vm_neg_seq(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vmnegmax::Float64) +function constraint_mc_bus_voltage_magnitude_negative_sequence(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vmnegmax::Float64) if !haskey(var(pm, pm.cnw), :vmpossqr) var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() @@ -576,7 +578,7 @@ vuf = |U-|/|U+| |U-| <= vufmax*|U+| |U-|^2 <= vufmax^2*|U+|^2 """ -function constraint_mc_vm_pos_seq(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vmposmax::Float64) +function constraint_mc_bus_voltage_magnitude_positive_sequence(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vmposmax::Float64) if !haskey(var(pm, pm.cnw), :vmpossqr) var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() @@ -611,7 +613,7 @@ vuf = |U-|/|U+| |U-| <= vufmax*|U+| |U-|^2 <= vufmax^2*|U+|^2 """ -function constraint_mc_vm_zero_seq(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vmzeromax::Float64) +function constraint_mc_bus_voltage_magnitude_zero_sequence(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vmzeromax::Float64) if !haskey(var(pm, pm.cnw), :vmpossqr) var(pm, pm.cnw)[:vmpossqr] = Dict{Int, Any}() var(pm, pm.cnw)[:vmnegsqr] = Dict{Int, Any}() @@ -682,7 +684,7 @@ end "" -function constraint_mc_vm_ll(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vm_ll_min::Vector, vm_ll_max::Vector) +function constraint_mc_bus_voltage_magnitude_ll(pm::_PM.AbstractACPModel, nw::Int, bus_id::Int, vm_ll_min::Vector, vm_ll_max::Vector) # 3 conductors asserted in template already vm_ln = [var(pm, nw, i, :vm, bus_id) for i in 1:3] va_ln = [var(pm, nw, i, :va, bus_id) for i in 1:3] @@ -708,17 +710,19 @@ end "bus voltage on/off constraint for load shed problem" function constraint_mc_bus_voltage_on_off(pm::_PM.AbstractACPModel; nw::Int=pm.cnw, kwargs...) for (i,bus) in ref(pm, nw, :bus) - constraint_mc_voltage_magnitude_on_off(pm, i; nw=nw) + constraint_mc_bus_voltage_magnitude_on_off(pm, i; nw=nw) end end + "`vm[i] == vmref`" -function constraint_mc_voltage_magnitude_setpoint(pm::_PM.AbstractACPModel, n::Int, i::Int, vmref) +function constraint_mc_voltage_magnitude_only(pm::_PM.AbstractACPModel, n::Int, i::Int, vmref) vm = var(pm, n, :vm, i) JuMP.@constraint(pm.model, vm .== vmref) end + "" function constraint_mc_storage_current_limit(pm::_PM.AbstractACPModel, n::Int, i, bus, rating) vm = var(pm, n, :vm, bus) @@ -730,7 +734,7 @@ end "" -function constraint_mc_load_wye(pm::_PM.AbstractACPModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) +function constraint_mc_load_setpoint_wye(pm::_PM.AbstractACPModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) vm = var(pm, nw, :vm, bus_id) va = var(pm, nw, :va, bus_id) @@ -770,7 +774,7 @@ end "" -function constraint_mc_load_delta(pm::_PM.AbstractACPModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) +function constraint_mc_load_setpoint_delta(pm::_PM.AbstractACPModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) vm = var(pm, nw, :vm, bus_id) va = var(pm, nw, :va, bus_id) @@ -812,7 +816,7 @@ end "" -function constraint_mc_generation_delta(pm::_PM.AbstractACPModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) +function constraint_mc_gen_setpoint_delta(pm::_PM.AbstractACPModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) vm = var(pm, nw, :vm, bus_id) va = var(pm, nw, :va, bus_id) pg = var(pm, nw, :pg, id) diff --git a/src/form/acr.jl b/src/form/acr.jl index 0db741f7b..14e54b3d8 100644 --- a/src/form/acr.jl +++ b/src/form/acr.jl @@ -1,7 +1,7 @@ "" -function variable_mc_voltage(pm::_PM.AbstractACRModel; nw=pm.cnw, bounded::Bool=true, kwargs...) - variable_mc_voltage_real(pm; nw=nw, bounded=bounded, kwargs...) - variable_mc_voltage_imaginary(pm; nw=nw, bounded=bounded, kwargs...) +function variable_mc_bus_voltage(pm::_PM.AbstractACRModel; nw=pm.cnw, bounded::Bool=true, kwargs...) + variable_mc_bus_voltage_real(pm; nw=nw, bounded=bounded, kwargs...) + variable_mc_bus_voltage_imaginary(pm; nw=nw, bounded=bounded, kwargs...) # local infeasbility issues without proper initialization; # convergence issues start when the equivalent angles of the starting point @@ -80,6 +80,8 @@ function constraint_mc_theta_ref(pm::_PM.AbstractACRModel, n::Int, d::Int, va_re end end + +"" function constraint_mc_voltage_angle_difference(pm::_PM.AbstractACRModel, n::Int, f_idx, angmin, angmax) i, f_bus, t_bus = f_idx @@ -95,9 +97,8 @@ function constraint_mc_voltage_angle_difference(pm::_PM.AbstractACRModel, n::Int end - "" -function constraint_mc_power_balance_slack(pm::_PM.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) +function constraint_mc_slack_power_balance(pm::_PM.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) vr = var(pm, nw, :vr, i) vi = var(pm, nw, :vi, i) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") @@ -204,7 +205,7 @@ end "" -function constraint_mc_power_balance_load(pm::_PM.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) +function constraint_mc_load_power_balance(pm::_PM.AbstractACRModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) vr = var(pm, nw, :vr, i) vi = var(pm, nw, :vi, i) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") @@ -344,7 +345,7 @@ end "" -function constraint_mc_load_wye(pm::_PM.AbstractACRModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) +function constraint_mc_load_setpoint_wye(pm::_PM.AbstractACRModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) @@ -384,7 +385,7 @@ end "" -function constraint_mc_load_delta(pm::_PM.AbstractACRModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) +function constraint_mc_load_setpoint_delta(pm::_PM.AbstractACRModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) @@ -426,7 +427,7 @@ end "`vm[i] == vmref`" -function constraint_mc_voltage_magnitude_setpoint(pm::_PM.AbstractACRModel, n::Int, i::Int, vmref) +function constraint_mc_voltage_magnitude_only(pm::_PM.AbstractACRModel, n::Int, i::Int, vmref) vr = var(pm, n, :vr, i) vi = var(pm, n, :vi, i) @@ -435,7 +436,7 @@ end "" -function constraint_mc_generation_delta(pm::_PM.AbstractACRModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) +function constraint_mc_gen_setpoint_delta(pm::_PM.AbstractACRModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) pg = var(pm, nw, :pg, id) diff --git a/src/form/apo.jl b/src/form/apo.jl index 549108a73..0aa3de113 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -1,18 +1,18 @@ -### generic features that apply to all active-power-only (apo) approximations import LinearAlgebra: diag + "apo models ignore reactive power flows" -function variable_mc_generation_reactive(pm::_PM.AbstractActivePowerModel; kwargs...) +function variable_mc_gen_power_setpoint_imaginary(pm::_PM.AbstractActivePowerModel; kwargs...) end "apo models ignore reactive power flows" -function variable_mc_reactive_generation_on_off(pm::_PM.AbstractActivePowerModel; kwargs...) +function variable_mc_gen_power_setpoint_imaginary_on_off(pm::_PM.AbstractActivePowerModel; kwargs...) end "on/off constraint for generators" -function constraint_mc_generation_on_off(pm::_PM.AbstractActivePowerModel, n::Int, i::Int, pmin, pmax, qmin, qmax) +function constraint_mc_gen_power_on_off(pm::_PM.AbstractActivePowerModel, n::Int, i::Int, pmin, pmax, qmin, qmax) pg = var(pm, n, :pg, i) z = var(pm, n, :z_gen, i) @@ -29,17 +29,17 @@ end "apo models ignore reactive power flows" -function variable_mc_storage_reactive(pm::_PM.AbstractActivePowerModel; kwargs...) +function variable_mc_storage_power_imaginary(pm::_PM.AbstractActivePowerModel; kwargs...) end "apo models ignore reactive power flows" -function variable_mc_on_off_storage_reactive(pm::_PM.AbstractActivePowerModel; kwargs...) +function variable_mc_storage_power_imaginary_on_off(pm::_PM.AbstractActivePowerModel; kwargs...) end "apo models ignore reactive power flows" -function variable_mc_branch_flow_reactive(pm::_PM.AbstractActivePowerModel; kwargs...) +function variable_mc_branch_power_imaginary(pm::_PM.AbstractActivePowerModel; kwargs...) end @@ -48,13 +48,8 @@ function variable_mc_branch_flow_ne_reactive(pm::_PM.AbstractActivePowerModel; k end -# "do nothing, apo models do not have reactive variables" -# function constraint_mc_gen_setpoint_reactive(pm::_PM.AbstractActivePowerModel, n::Int, c::Int, i, qg) -# end - - "nothing to do, these models do not have complex voltage variables" -function variable_mc_voltage(pm::_PM.AbstractNFAModel; nw=pm.cnw, kwargs...) +function variable_mc_bus_voltage(pm::_PM.AbstractNFAModel; nw=pm.cnw, kwargs...) end "nothing to do, these models do not have angle difference constraints" @@ -62,15 +57,13 @@ function constraint_mc_voltage_angle_difference(pm::_PM.AbstractNFAModel, n::Int end - - "apo models ignore reactive power flows" -function variable_mc_transformer_flow_reactive(pm::_PM.AbstractActivePowerModel; nw::Int=pm.cnw, bounded=true) +function variable_mc_transformer_power_imaginary(pm::_PM.AbstractActivePowerModel; nw::Int=pm.cnw, bounded=true) end "power balanace constraint with line shunts and transformers, active power only" -function constraint_mc_power_balance_load(pm::_PM.AbstractActivePowerModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) +function constraint_mc_load_power_balance(pm::_PM.AbstractActivePowerModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") pg = get(var(pm, nw), :pg_bus, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") @@ -105,8 +98,9 @@ end ######## Lossless Models ######## + "Create variables for the active power flowing into all transformer windings" -function variable_mc_transformer_flow_active(pm::_PM.AbstractAPLossLessModels; nw::Int=pm.cnw, bounded=true) +function variable_mc_transformer_power_real(pm::_PM.AbstractAPLossLessModels; nw::Int=pm.cnw, bounded=true) ncnds = length(conductor_ids(pm)) pt = var(pm, nw)[:pt] = Dict((l,i,j) => JuMP.@variable(pm.model, @@ -148,6 +142,7 @@ function constraint_mc_ohms_yt_to(pm::_PM.AbstractAPLossLessModels, n::Int, f_bu end ### Network Flow Approximation ### + "nothing to do, no voltage angle variables" function constraint_mc_theta_ref(pm::_PM.AbstractNFAModel, n::Int, d::Int, va_ref) end @@ -164,10 +159,9 @@ end "nothing to do, this model is symmetric" -function constraint_mc_trans(pm::_PM.AbstractNFAModel, i::Int; nw::Int=pm.cnw) +function constraint_mc_transformer_power(pm::_PM.AbstractNFAModel, i::Int; nw::Int=pm.cnw) end -## From PowerModels "`-rate_a <= p[f_idx] <= rate_a`" function constraint_mc_thermal_limit_from(pm::_PM.AbstractActivePowerModel, n::Int, f_idx, rate_a) @@ -193,6 +187,7 @@ function constraint_mc_thermal_limit_from(pm::_PM.AbstractActivePowerModel, n::I end end + "" function constraint_mc_thermal_limit_to(pm::_PM.AbstractActivePowerModel, n::Int, t_idx, rate_a) cnds = conductor_ids(pm, n) @@ -249,6 +244,7 @@ function constraint_mc_thermal_limit_to_on_off(pm::_PM.AbstractActivePowerModel, JuMP.@constraint(pm.model, p_to .>= -rate_a.*z) end + "" function constraint_mc_thermal_limit_from_ne(pm::_PM.AbstractActivePowerModel, n::Int, i, f_idx, rate_a) p_fr =var(pm, n, :p_ne, f_idx) @@ -258,6 +254,7 @@ function constraint_mc_thermal_limit_from_ne(pm::_PM.AbstractActivePowerModel, n JuMP.@constraint(pm.model, p_fr .>= -rate_a.*z) end + "" function constraint_mc_thermal_limit_to_ne(pm::_PM.AbstractActivePowerModel, n::Int, i, t_idx, rate_a) p_to =var(pm, n, :p_ne, t_idx) @@ -295,6 +292,7 @@ function constraint_mc_storage_thermal_limit(pm::_PM.AbstractActivePowerModel, n end end + "" function constraint_mc_storage_current_limit(pm::_PM.AbstractActivePowerModel, n::Int, i, bus, rating) ps =var(pm, n, :ps, i) @@ -308,8 +306,9 @@ function constraint_mc_storage_current_limit(pm::_PM.AbstractActivePowerModel, n end end + "" -function constraint_mc_storage_loss(pm::_PM.AbstractActivePowerModel, n::Int, i, bus, conductors, r, x, p_loss, q_loss) +function constraint_mc_storage_losses(pm::_PM.AbstractActivePowerModel, n::Int, i, bus, conductors, r, x, p_loss, q_loss) ps = var(pm, n, :ps, i) sc = var(pm, n, :sc, i) sd = var(pm, n, :sd, i) @@ -321,6 +320,8 @@ function constraint_mc_storage_loss(pm::_PM.AbstractActivePowerModel, n::Int, i, ) end + +"" function constraint_mc_storage_on_off(pm::_PM.AbstractActivePowerModel, n::Int, i, pmin, pmax, qmin, qmax, charge_ub, discharge_ub) z_storage =var(pm, n, :z_storage, i) @@ -331,25 +332,15 @@ function constraint_mc_storage_on_off(pm::_PM.AbstractActivePowerModel, n::Int, end -# -# "" -# function add_setpoint_switch_flow!(sol, pm::_PM.AbstractActivePowerModel) -# add_setpoint!(sol, pm, "switch", "psw", :psw, var_key = (idx,item) -> (idx, item["f_bus"], item["t_bus"])) -# add_setpoint_fixed!(sol, pm, "switch", "qsw") -# end - - -""" -Only support wye-connected generators. -""" -function constraint_mc_generation(pm::_PM.AbstractActivePowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) +"Only support wye-connected generators." +function constraint_mc_gen_setpoint(pm::_PM.AbstractActivePowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) var(pm, nw, :pg_bus)[id] = var(pm, nw, :pg, id) end "Only support wye-connected, constant-power loads." -function constraint_mc_load(pm::_PM.AbstractActivePowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) +function constraint_mc_load_setpoint(pm::_PM.AbstractActivePowerModel, id::Int; nw::Int=pm.cnw, report::Bool=true) load = ref(pm, nw, :load, id) pd = load["pd"] diff --git a/src/form/bf.jl b/src/form/bf.jl index 7df88c8f8..3532f2cc6 100644 --- a/src/form/bf.jl +++ b/src/form/bf.jl @@ -40,10 +40,10 @@ end "Create voltage variables for branch flow model" function variable_mc_bus_voltage_on_off(pm::LPUBFDiagModel; kwargs...) - variable_mc_voltage_magnitude_sqr_on_off(pm; kwargs...) + variable_mc_bus_voltage_magnitude_sqr_on_off(pm; kwargs...) end "nothing to do, this model is symmetric" -function constraint_mc_trans_yy(pm::LPUBFDiagModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) +function constraint_mc_transformer_power_yy(pm::LPUBFDiagModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) end diff --git a/src/form/bf_mx.jl b/src/form/bf_mx.jl index ad1c4094b..4d477cb9f 100644 --- a/src/form/bf_mx.jl +++ b/src/form/bf_mx.jl @@ -1,19 +1,20 @@ import LinearAlgebra: diag, diagm + "" function variable_mc_branch_current(pm::AbstractUBFModels; kwargs...) - variable_mc_branch_series_current_prod_hermitian(pm; kwargs...) + constraint_mc_branch_current_series_product_hermitian(pm; kwargs...) end "" -function variable_mc_voltage(pm::AbstractUBFModels; kwargs...) - variable_mc_voltage_prod_hermitian(pm; kwargs...) +function variable_mc_bus_voltage(pm::AbstractUBFModels; kwargs...) + variable_mc_bus_voltage_prod_hermitian(pm; kwargs...) end "" -function variable_mc_voltage_prod_hermitian(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_bus_voltage_prod_hermitian(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) bus_ids = collect(ids(pm, nw, :bus)) if bounded @@ -41,7 +42,7 @@ end "" -function variable_mc_branch_series_current_prod_hermitian(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function constraint_mc_branch_current_series_product_hermitian(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) branches = ref(pm, nw, :branch) buses = ref(pm, nw, :bus) @@ -75,7 +76,7 @@ end "" -function variable_mc_branch_flow(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_branch_power(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) @assert n_cond<=5 # calculate S bound @@ -120,7 +121,7 @@ end "Defines branch flow model power flow equations" -function constraint_mc_flow_losses(pm::AbstractUBFModels, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm) +function constraint_mc_power_losses(pm::AbstractUBFModels, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm) P_to = var(pm, n, :P)[t_idx] Q_to = var(pm, n, :Q)[t_idx] @@ -193,9 +194,9 @@ end For the matrix KCL formulation, the generator needs an explicit current and power variable. """ -function variable_mc_generation(pm::SDPUBFKCLMXModel; nw=pm.cnw) - variable_mc_generation_current(pm; nw=nw) - variable_mc_generation_power(pm; nw=nw) +function variable_mc_gen_power_setpoint(pm::SDPUBFKCLMXModel; nw=pm.cnw) + variable_mc_gen_current(pm; nw=nw) + variable_mc_gen_power(pm; nw=nw) end @@ -203,7 +204,7 @@ end For the matrix KCL formulation, the generator needs an explicit power variable. """ -function variable_mc_generation_power(pm::SDPUBFKCLMXModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_gen_power(pm::SDPUBFKCLMXModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) @assert(bounded) gen_ids = collect(ids(pm, nw, :gen)) @@ -245,7 +246,7 @@ end For the matrix KCL formulation, the generator needs an explicit current variable. """ -function variable_mc_generation_current(pm::AbstractUBFModels; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_gen_current(pm::AbstractUBFModels; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) @assert(bounded) gen_ids = collect(ids(pm, nw, :gen)) @@ -278,7 +279,7 @@ Wye loads however, don't need any variables when the load is modelled as constant power or constant impedance. In all other cases (e.g. when a cone is used to constrain the power), variables need to be created. """ -function variable_mc_load(pm::AbstractUBFModels; nw=pm.cnw) +function variable_mc_load_setpoint(pm::AbstractUBFModels; nw=pm.cnw) load_wye_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="wye"] load_del_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="delta"] load_cone_ids = [id for (id, load) in ref(pm, nw, :load) if _check_load_needs_cone(load)] @@ -288,7 +289,7 @@ function variable_mc_load(pm::AbstractUBFModels; nw=pm.cnw) var(pm, nw)[:pl] = Dict() var(pm, nw)[:ql] = Dict() # now, create auxilary power variable X for delta loads - variable_mc_load_delta_aux(pm, load_del_ids) + variable_mc_load_power_delta_aux(pm, load_del_ids) # only delta loads need a current variable variable_mc_load_current(pm, load_del_ids) # for wye loads with a cone inclusion constraint, we need to create a variable @@ -309,7 +310,7 @@ Delta loads only need a current variable and auxilary power variable (X), and all other load model variables are then linear transformations of these (linear Expressions). """ -function variable_mc_load(pm::SDPUBFKCLMXModel; nw=pm.cnw) +function variable_mc_load_setpoint(pm::SDPUBFKCLMXModel; nw=pm.cnw) load_wye_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="wye"] load_del_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="delta"] load_cone_ids = [id for (id, load) in ref(pm, nw, :load) if _check_load_needs_cone(load)] @@ -319,7 +320,7 @@ function variable_mc_load(pm::SDPUBFKCLMXModel; nw=pm.cnw) var(pm, nw)[:pl] = Dict{Int, Any}() var(pm, nw)[:ql] = Dict{Int, Any}() # now, create auxilary power variable X for delta loads - variable_mc_load_delta_aux(pm, load_del_ids) + variable_mc_load_power_delta_aux(pm, load_del_ids) # all loads need a current variable now variable_mc_load_current(pm, collect(ids(pm, nw, :load))) # for all wye-connected loads, we need variables for the off-diagonals of Pd/Qd @@ -418,7 +419,7 @@ See the paper by Zhao et al. for the first convex relaxation of delta transforma See upcoming paper for discussion of bounds. [reference added when accepted] """ -function variable_mc_load_delta_aux(pm::AbstractUBFModels, load_ids::Array{Int,1}; nw=pm.cnw, eps=0.1, bounded::Bool=true, report::Bool=true) +function variable_mc_load_power_delta_aux(pm::AbstractUBFModels, load_ids::Array{Int,1}; nw=pm.cnw, eps=0.1, bounded::Bool=true, report::Bool=true) @assert(bounded) ncnds = length(conductor_ids(pm, nw)) # calculate bounds @@ -474,7 +475,7 @@ end """ Only KCLModels need to further constrain the generator variables. """ -function constraint_mc_generation(pm::AbstractUBFModels, gen_id::Int; nw::Int=pm.cnw) +function constraint_mc_gen_setpoint(pm::AbstractUBFModels, gen_id::Int; nw::Int=pm.cnw) # do nothing end @@ -483,7 +484,7 @@ end Link the current and power withdrawn by a generator at the bus through a PSD constraint. The rank-1 constraint is dropped in this formulation. """ -function constraint_mc_generation(pm::SDPUBFKCLMXModel, gen_id::Int; nw::Int=pm.cnw) +function constraint_mc_gen_setpoint(pm::SDPUBFKCLMXModel, gen_id::Int; nw::Int=pm.cnw) Pg = var(pm, nw, :Pg, gen_id) Qg = var(pm, nw, :Qg, gen_id) bus_id = ref(pm, nw, :gen, gen_id)["gen_bus"] @@ -561,7 +562,7 @@ end """ Creates the constraints modelling the (relaxed) voltage-dependent loads. """ -function constraint_mc_load(pm::AbstractUBFModels, load_id::Int; nw::Int=pm.cnw) +function constraint_mc_load_setpoint(pm::AbstractUBFModels, load_id::Int; nw::Int=pm.cnw) # shared variables and parameters load = ref(pm, nw, :load, load_id) pd0 = load["pd"] @@ -645,7 +646,7 @@ end Creates the constraints modelling the (relaxed) voltage-dependent loads for the matrix KCL formulation. """ -function constraint_mc_load(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int=pm.cnw) +function constraint_mc_load_setpoint(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int=pm.cnw) # shared variables and parameters load = ref(pm, nw, :load, load_id) pd0 = load["pd"] diff --git a/src/form/bf_mx_lin.jl b/src/form/bf_mx_lin.jl index 6ae3ad20b..4d5c41fbc 100644 --- a/src/form/bf_mx_lin.jl +++ b/src/form/bf_mx_lin.jl @@ -12,21 +12,21 @@ end "" -function variable_mc_voltage_prod_hermitian(pm::LPUBFDiagModel; n_cond::Int=3, nw::Int=pm.cnw, bounded = true) - variable_mc_voltage_magnitude_sqr(pm, nw=nw) +function variable_mc_bus_voltage_prod_hermitian(pm::LPUBFDiagModel; n_cond::Int=3, nw::Int=pm.cnw, bounded = true) + variable_mc_bus_voltage_magnitude_sqr(pm, nw=nw) end "" -function variable_mc_branch_flow(pm::LPUBFDiagModel; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_branch_power(pm::LPUBFDiagModel; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) @assert(n_cond == 3) - variable_mc_branch_flow_active(pm, nw=nw, bounded=bounded) - variable_mc_branch_flow_reactive(pm, nw=nw, bounded=bounded) + variable_mc_branch_power_real(pm, nw=nw, bounded=bounded) + variable_mc_branch_power_imaginary(pm, nw=nw, bounded=bounded) end "Defines branch flow model power flow equations" -function constraint_mc_flow_losses(pm::LPUBFDiagModel, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm) +function constraint_mc_power_losses(pm::LPUBFDiagModel, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm) p_fr = var(pm, n, :p)[f_idx] q_fr = var(pm, n, :q)[f_idx] diff --git a/src/form/bf_mx_sdp.jl b/src/form/bf_mx_sdp.jl index 7cde2c6ab..3b75769e9 100644 --- a/src/form/bf_mx_sdp.jl +++ b/src/form/bf_mx_sdp.jl @@ -28,5 +28,4 @@ function constraint_mc_model_current(pm::SDPUBFModel, n::Int, i, f_bus, f_idx, g mat_real -mat_imag; mat_imag mat_real ] in JuMP.PSDCone()) - end diff --git a/src/form/dcp.jl b/src/form/dcp.jl index 2686edba0..2ce0cb375 100644 --- a/src/form/dcp.jl +++ b/src/form/dcp.jl @@ -1,13 +1,11 @@ -### simple active power only approximations (e.g. DC Power Flow) - - "" -function variable_mc_voltage(pm::_PM.AbstractDCPModel; nw=pm.cnw, kwargs...) - variable_mc_voltage_angle(pm; nw=nw, kwargs...) +function variable_mc_bus_voltage(pm::_PM.AbstractDCPModel; nw=pm.cnw, kwargs...) + variable_mc_bus_voltage_angle(pm; nw=nw, kwargs...) end ######## AbstractDCPForm Models (has va but assumes vm is 1.0) ######## + "nothing to do, these models do not have complex voltage constraints" function constraint_mc_model_voltage(pm::_PM.AbstractDCPModel, n::Int, c::Int) end @@ -15,11 +13,12 @@ end "" function variable_mc_bus_voltage_on_off(pm::_PM.AbstractDCPModel; kwargs...) - variable_mc_voltage_angle(pm; kwargs...) + variable_mc_bus_voltage_angle(pm; kwargs...) end ### DC Power Flow Approximation ### + """ Creates Ohms constraints (yt post fix indicates that Y and T values are in rectangular form) @@ -39,7 +38,7 @@ end "power balance constraint with line shunts and transformers for load shed problem, DCP formulation" -function constraint_mc_power_balance_shed(pm::_PM.AbstractDCPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) +function constraint_mc_shed_power_balance(pm::_PM.AbstractDCPModel, nw::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") @@ -71,7 +70,7 @@ end "" -function variable_mc_branch_flow_active(pm::_PM.AbstractAPLossLessModels; nw::Int=pm.cnw, bounded::Bool = true, report::Bool = true) +function variable_mc_branch_power_real(pm::_PM.AbstractAPLossLessModels; nw::Int=pm.cnw, bounded::Bool = true, report::Bool = true) cnds = conductor_ids(pm) ncnds = length(cnds) diff --git a/src/form/ivr.jl b/src/form/ivr.jl index fb906cc91..e8ae56bc6 100644 --- a/src/form/ivr.jl +++ b/src/form/ivr.jl @@ -32,8 +32,8 @@ function variable_mc_branch_current(pm::_PM.AbstractIVRModel; nw::Int=pm.cnw, bo report && _IM.sol_component_value_edge(pm, nw, :branch, :pf, :pt, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), p) report && _IM.sol_component_value_edge(pm, nw, :branch, :qf, :qt, ref(pm, nw, :arcs_from), ref(pm, nw, :arcs_to), q) - variable_mc_branch_series_current_real(pm, nw=nw, bounded=bounded, report=report; kwargs...) - variable_mc_branch_series_current_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...) + variable_mc_branch_current_series_real(pm, nw=nw, bounded=bounded, report=report; kwargs...) + variable_mc_branch_current_series_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...) end @@ -73,17 +73,18 @@ end "" -function variable_mc_load(pm::_PM.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) +function variable_mc_load_setpoint(pm::_PM.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) var(pm, nw)[:crd] = Dict{Int, Any}() var(pm, nw)[:cid] = Dict{Int, Any}() var(pm, nw)[:crd_bus] = Dict{Int, Any}() var(pm, nw)[:cid_bus] = Dict{Int, Any}() end + "" -function variable_mc_generation(pm::_PM.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) - variable_mc_generation_current_real(pm, nw=nw, bounded=bounded, report=report; kwargs...) - variable_mc_generation_current_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...) +function variable_mc_gen_power_setpoint(pm::_PM.AbstractIVRModel; nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true, kwargs...) + variable_mc_gen_current_setpoint_real(pm, nw=nw, bounded=bounded, report=report; kwargs...) + variable_mc_gen_current_setpoint_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...) cnds = conductor_ids(pm; nw=nw) ncnds = length(cnds) @@ -98,9 +99,9 @@ end "" -function variable_mc_voltage(pm::_PM.AbstractIVRModel; nw=pm.cnw, bounded::Bool=true, kwargs...) - variable_mc_voltage_real(pm; nw=nw, bounded=bounded, kwargs...) - variable_mc_voltage_imaginary(pm; nw=nw, bounded=bounded, kwargs...) +function variable_mc_bus_voltage(pm::_PM.AbstractIVRModel; nw=pm.cnw, bounded::Bool=true, kwargs...) + variable_mc_bus_voltage_real(pm; nw=nw, bounded=bounded, kwargs...) + variable_mc_bus_voltage_imaginary(pm; nw=nw, bounded=bounded, kwargs...) # local infeasbility issues without proper initialization; # convergence issues start when the equivalent angles of the starting point @@ -132,9 +133,8 @@ function variable_mc_voltage(pm::_PM.AbstractIVRModel; nw=pm.cnw, bounded::Bool= end end -""" -Defines how current distributes over series and shunt impedances of a pi-model branch -""" + +"Defines how current distributes over series and shunt impedances of a pi-model branch" function constraint_mc_current_from(pm::_PM.AbstractIVRModel, n::Int, f_bus, f_idx, g_sh_fr, b_sh_fr, tr, ti, tm) vr_fr = var(pm, n, :vr, f_bus) vi_fr = var(pm, n, :vi, f_bus) @@ -155,9 +155,8 @@ function constraint_mc_current_from(pm::_PM.AbstractIVRModel, n::Int, f_bus, f_i JuMP.@constraint(pm.model, ci_fr .== (tr.*csi_fr + ti.*csr_fr + g_sh_fr*vi_fr + b_sh_fr*vr_fr)./tm.^2) end -""" -Defines how current distributes over series and shunt impedances of a pi-model branch -""" + +"Defines how current distributes over series and shunt impedances of a pi-model branch" function constraint_mc_current_to(pm::_PM.AbstractIVRModel, n::Int, t_bus, f_idx, t_idx, g_sh_to, b_sh_to) vr_to = var(pm, n, :vr, t_bus) vi_to = var(pm, n, :vi, t_bus) @@ -176,10 +175,8 @@ function constraint_mc_current_to(pm::_PM.AbstractIVRModel, n::Int, t_bus, f_idx end -""" -Defines voltage drop over a branch, linking from and to side complex voltage -""" -function constraint_mc_voltage_drop(pm::_PM.AbstractIVRModel, n::Int, i, f_bus, t_bus, f_idx, r, x, tr, ti, tm) +"Defines voltage drop over a branch, linking from and to side complex voltage" +function constraint_mc_bus_voltage_drop(pm::_PM.AbstractIVRModel, n::Int, i, f_bus, t_bus, f_idx, r, x, tr, ti, tm) vr_fr = var(pm, n, :vr, f_bus) vi_fr = var(pm, n, :vi, f_bus) @@ -196,9 +193,8 @@ function constraint_mc_voltage_drop(pm::_PM.AbstractIVRModel, n::Int, i, f_bus, JuMP.@constraint(pm.model, vi_to .== (vi_fr.*tr - vr_fr.*ti)./tm.^2 - r*csi_fr - x*csr_fr) end -""" -Bounds the voltage angle difference between bus pairs -""" + +"Bounds the voltage angle difference between bus pairs" function constraint_mc_voltage_angle_difference(pm::_PM.AbstractIVRModel, n::Int, f_idx, angmin, angmax) i, f_bus, t_bus = f_idx @@ -213,11 +209,12 @@ function constraint_mc_voltage_angle_difference(pm::_PM.AbstractIVRModel, n::Int JuMP.@constraint(pm.model, tan.(angmax).*vvr .>= vvi) end + """ Kirchhoff's current law applied to buses `sum(cr + im*ci) = 0` """ -function constraint_mc_current_balance_load(pm::_PM.AbstractIVRModel, n::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) +function constraint_mc_load_current_balance(pm::_PM.AbstractIVRModel, n::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) vr = var(pm, n, :vr, i) vi = var(pm, n, :vi, i) @@ -262,6 +259,7 @@ function constraint_mc_current_balance_load(pm::_PM.AbstractIVRModel, n::Int, i, end end + "`p[f_idx]^2 + q[f_idx]^2 <= rate_a^2`" function constraint_mc_thermal_limit_from(pm::_PM.AbstractIVRModel, n::Int, f_idx, rate_a) (l, f_bus, t_bus) = f_idx @@ -279,6 +277,7 @@ function constraint_mc_thermal_limit_from(pm::_PM.AbstractIVRModel, n::Int, f_id end end + "`p[t_idx]^2 + q[t_idx]^2 <= rate_a^2`" function constraint_mc_thermal_limit_to(pm::_PM.AbstractIVRModel, n::Int, t_idx, rate_a) (l, t_bus, f_bus) = t_idx @@ -296,6 +295,7 @@ function constraint_mc_thermal_limit_to(pm::_PM.AbstractIVRModel, n::Int, t_idx, end end + """ Bounds the current magnitude at both from and to side of a branch `cr[f_idx]^2 + ci[f_idx]^2 <= c_rating_a^2` @@ -315,10 +315,11 @@ function constraint_mc_current_limit(pm::_PM.AbstractIVRModel, n::Int, f_idx, c_ JuMP.@constraint(pm.model, crt.^2 + cit.^2 .<= c_rating_a.^2) end + """ `pmin <= Re(v*cg') <= pmax` """ -function constraint_mc_generation_active_power_limits(pm::_PM.AbstractIVRModel, n::Int, i, bus, pmax, pmin) +function constraint_mc_gen_active_bounds(pm::_PM.AbstractIVRModel, n::Int, i, bus, pmax, pmin) @assert pmin <= pmax vr = var(pm, n, :vr, bus) @@ -330,10 +331,11 @@ function constraint_mc_generation_active_power_limits(pm::_PM.AbstractIVRModel, JuMP.@constraint(pm.model, pmax .>= vr.*cr + vi.*ci) end + """ `qmin <= Im(v*cg') <= qmax` """ -function constraint_mc_generation_reactive_power_limits(pm::_PM.AbstractIVRModel, n::Int, i, bus, qmax, qmin) +function constraint_mc_gen_reactive_bounds(pm::_PM.AbstractIVRModel, n::Int, i, bus, qmax, qmin) @assert qmin <= qmax vr = var(pm, n, :vr, bus) @@ -345,8 +347,9 @@ function constraint_mc_generation_reactive_power_limits(pm::_PM.AbstractIVRModel JuMP.@constraint(pm.model, qmax .>= vi.*cr - vr.*ci) end + "`pg[i] == pg`" -function constraint_mc_active_gen_setpoint(pm::_PM.AbstractIVRModel, n::Int, i, pgref) +function constraint_mc_gen_power_setpoint_real(pm::_PM.AbstractIVRModel, n::Int, i, pgref) gen = ref(pm, n, :gen, i) bus = gen["gen_bus"] vr = var(pm, n, :vr, bus) @@ -357,8 +360,9 @@ function constraint_mc_active_gen_setpoint(pm::_PM.AbstractIVRModel, n::Int, i, JuMP.@constraint(pm.model, pgref .== vr.*cr + vi.*ci) end + "`qq[i] == qq`" -function constraint_mc_reactive_gen_setpoint(pm::_PM.AbstractIVRModel, n::Int, i, qgref) +function constraint_mc_regen_setpoint_active(pm::_PM.AbstractIVRModel, n::Int, i, qgref) gen = ref(pm, n, :gen, i) bus = gen["gen_bus"] vr = var(pm, n, :vr, bus) @@ -370,6 +374,7 @@ function constraint_mc_reactive_gen_setpoint(pm::_PM.AbstractIVRModel, n::Int, i end +"" function _PM._objective_min_fuel_cost_polynomial_linquad(pm::_PM.AbstractIVRModel; report::Bool=true) gen_cost = Dict() dcline_cost = Dict() @@ -426,7 +431,8 @@ function objective_variable_pg_cost(pm::_PM.AbstractIVRModel; report::Bool=true) end -function constraint_mc_trans_yy(pm::_PM.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) +"" +function constraint_mc_transformer_power_yy(pm::_PM.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) vr_fr_P = var(pm, nw, :vr, f_bus)[f_cnd] vi_fr_P = var(pm, nw, :vi, f_bus)[f_cnd] vr_fr_n = 0 @@ -452,7 +458,8 @@ function constraint_mc_trans_yy(pm::_PM.AbstractIVRModel, nw::Int, trans_id::Int end -function constraint_mc_trans_dy(pm::_PM.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) +"" +function constraint_mc_transformer_power_dy(pm::_PM.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) vr_fr_P = var(pm, nw, :vr, f_bus)[f_cnd] vi_fr_P = var(pm, nw, :vi, f_bus)[f_cnd] vr_to_P = var(pm, nw, :vr, t_bus)[t_cnd] @@ -481,7 +488,7 @@ end "" -function constraint_mc_load_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) +function constraint_mc_load_setpoint_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) @@ -518,7 +525,7 @@ end "" -function constraint_mc_load_delta(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) +function constraint_mc_load_setpoint_delta(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) @@ -560,7 +567,7 @@ end "" -function constraint_mc_generation_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) +function constraint_mc_gen_setpoint_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) crg = var(pm, nw, :crg, id) @@ -604,7 +611,7 @@ end "" -function constraint_mc_generation_delta(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) +function constraint_mc_gen_setpoint_delta(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) crg = var(pm, nw, :crg, id) diff --git a/src/form/shared.jl b/src/form/shared.jl index f5ecf533d..672987f32 100644 --- a/src/form/shared.jl +++ b/src/form/shared.jl @@ -1,13 +1,15 @@ import LinearAlgebra: diag + "`vm[i] == vmref`" -function constraint_mc_voltage_magnitude_setpoint(pm::_PM.AbstractWModels, n::Int, i::Int, vmref) +function constraint_mc_voltage_magnitude_only(pm::_PM.AbstractWModels, n::Int, i::Int, vmref) w = var(pm, n, :w, i) JuMP.@constraint(pm.model, w .== vmref.^2) end + "" -function constraint_mc_power_balance_slack(pm::_PM.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) +function constraint_mc_slack_power_balance(pm::_PM.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) w = var(pm, nw, :w, i) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") @@ -87,7 +89,7 @@ end "KCL for load shed problem with transformers (AbstractWForms)" -function constraint_mc_power_balance_shed(pm::_PM.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) +function constraint_mc_shed_power_balance(pm::_PM.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) w = var(pm, nw, :w, i) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") q = get(var(pm, nw), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") @@ -133,7 +135,7 @@ end "" -function constraint_mc_power_balance_load(pm::_PM.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) +function constraint_mc_load_power_balance(pm::_PM.AbstractWModels, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) Wr = var(pm, nw, :Wr, i) Wi = var(pm, nw, :Wi, i) P = get(var(pm, nw), :P, Dict()); _PM._check_var_keys(P, bus_arcs, "active power", "branch") @@ -200,11 +202,12 @@ end "on/off bus voltage constraint for relaxed forms" function constraint_mc_bus_voltage_on_off(pm::_PM.AbstractWModels, n::Int; kwargs...) for (i, bus) in ref(pm, n, :bus) - constraint_mc_voltage_magnitude_sqr_on_off(pm, i, nw=n) + constraint_mc_bus_voltage_magnitude_sqr_on_off(pm, i, nw=n) end end +"" function constraint_mc_voltage_angle_difference(pm::_PM.AbstractPolarModels, n::Int, f_idx, angmin, angmax) i, f_bus, t_bus = f_idx @@ -238,6 +241,7 @@ function constraint_mc_voltage_angle_difference(pm::_PM.AbstractWModels, n::Int, end +"" function constraint_mc_storage_on_off(pm::_PM.AbstractPowerModel, n::Int, i, pmin, pmax, qmin, qmax, charge_ub, discharge_ub) z_storage =var(pm, n, :z_storage, i) ps =var(pm, n, :ps, i) @@ -252,7 +256,7 @@ end "" -function constraint_mc_generation_wye(pm::_PM.AbstractPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) +function constraint_mc_gen_setpoint_wye(pm::_PM.AbstractPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) var(pm, nw, :pg_bus)[id] = var(pm, nw, :pg, id) var(pm, nw, :qg_bus)[id] = var(pm, nw, :qg, id) diff --git a/src/form/wr.jl b/src/form/wr.jl index 94c4fab6a..e69de29bb 100644 --- a/src/form/wr.jl +++ b/src/form/wr.jl @@ -1,58 +0,0 @@ -# "" -# function constraint_mc_model_voltage(pm::_PM.AbstractWRModel, n::Int) -# w = var(pm, n, :w) -# wr = var(pm, n, :wr) -# wi = var(pm, n, :wi) -# -# for c in 1:length(conductor_ids(pm, n)) -# for d in c:length(conductor_ids(pm)) -# for (i,j) in ids(pm, n, :buspairs) -# InfrastructureModels.relaxation_complex_product(pm.model, w[i][d], w[j][c], wr[(i,j,c,d)], wi[(i,j,c,d)]) -# end -# -# if d != c -# for i in ids(pm, n, :bus) -# InfrastructureModels.relaxation_complex_product(pm.model, w[i][d], w[i][c], wr[(i,i,c,d)], wi[(i,i,c,d)]) -# end -# end -# end -# end -# end - - -# "power balance constraint with line shunts and transformers for relaxed WR forms" -# function constraint_mc_power_balance(pm::_PM.AbstractWRModel, nw::Int, c::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) -# w = var(pm, nw, c, :w, i) -# p = get(var(pm, nw, c), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") -# q = get(var(pm, nw, c), :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch") -# pg = get(var(pm, nw, c), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") -# qg = get(var(pm, nw, c), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") -# ps = get(var(pm, nw, c), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") -# qs = get(var(pm, nw, c), :qs, Dict()); _PM._check_var_keys(qs, bus_storage, "reactive power", "storage") -# psw = get(var(pm, nw, c), :psw, Dict()); _PM._check_var_keys(psw, bus_arcs_sw, "active power", "switch") -# qsw = get(var(pm, nw, c), :qsw, Dict()); _PM._check_var_keys(qsw, bus_arcs_sw, "reactive power", "switch") -# pt = get(var(pm, nw, c), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") -# qt = get(var(pm, nw, c), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") -# -# -# con(pm, nw, c, :kcl_p)[i] = JuMP.@constraint(pm.model, -# sum(p[a] for a in bus_arcs) -# + sum(psw[a_sw] for a_sw in bus_arcs_sw) -# + sum(pt[a_trans] for a_trans in bus_arcs_trans) -# == -# sum(pg[g] for g in bus_gens) -# - sum(ps[s] for s in bus_storage) -# - sum(pd for pd in values(bus_pd)) -# - sum(gs for gs in values(bus_gs))*w -# ) -# con(pm, nw, c, :kcl_q)[i] = JuMP.@constraint(pm.model, -# sum(q[a] for a in bus_arcs) -# + sum(qsw[a_sw] for a_sw in bus_arcs_sw) -# + sum(qt[a_trans] for a_trans in bus_arcs_trans) -# == -# sum(qg[g] for g in bus_gens) -# - sum(qs[s] for s in bus_storage) -# - sum(qd for qd in values(bus_qd)) -# + sum(bs for bs in values(bus_bs))*w -# ) -# end diff --git a/src/prob/debug.jl b/src/prob/debug.jl index aea63e94b..1aea52bac 100644 --- a/src/prob/debug.jl +++ b/src/prob/debug.jl @@ -26,14 +26,14 @@ end "OPF problem with slack power at every bus" function build_mc_opf_pbs(pm::_PM.AbstractPowerModel) - variable_mc_voltage(pm) + variable_mc_bus_voltage(pm) - variable_mc_branch_flow(pm) - variable_mc_transformer_flow(pm) + variable_mc_branch_power(pm) + variable_mc_transformer_power(pm) - variable_mc_generation(pm) + variable_mc_gen_power_setpoint(pm) - variable_mc_bus_power_slack(pm) + variable_mc_slack_bus_power(pm) constraint_mc_model_voltage(pm) @@ -42,7 +42,7 @@ function build_mc_opf_pbs(pm::_PM.AbstractPowerModel) end for i in ids(pm, :bus) - constraint_mc_power_balance_slack(pm, i) + constraint_mc_slack_power_balance(pm, i) end for i in ids(pm, :branch) @@ -55,20 +55,20 @@ function build_mc_opf_pbs(pm::_PM.AbstractPowerModel) constraint_mc_thermal_limit_to(pm, i) end - objective_min_bus_power_slack(pm) + objective_mc_min_slack_bus_power(pm) end "PF problem with slack power at every bus" function build_mc_pf_pbs(pm::_PM.AbstractPowerModel) - variable_mc_voltage(pm; bounded=false) + variable_mc_bus_voltage(pm; bounded=false) - variable_mc_branch_flow(pm; bounded=false) - variable_mc_transformer_flow(pm; bounded=false) + variable_mc_branch_power(pm; bounded=false) + variable_mc_transformer_power(pm; bounded=false) - variable_mc_generation(pm; bounded=false) + variable_mc_gen_power_setpoint(pm; bounded=false) - variable_mc_bus_power_slack(pm) + variable_mc_slack_bus_power(pm) constraint_mc_model_voltage(pm) @@ -76,20 +76,20 @@ function build_mc_pf_pbs(pm::_PM.AbstractPowerModel) constraint_mc_theta_ref(pm, i) @assert bus["bus_type"] == 3 - constraint_mc_voltage_magnitude_setpoint(pm, i) + constraint_mc_voltage_magnitude_only(pm, i) end for (i,bus) in ref(pm, :bus) - constraint_mc_power_balance_slack(pm, i) + constraint_mc_slack_power_balance(pm, i) # PV Bus Constraints if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) # this assumes inactive generators are filtered out of bus_gens @assert bus["bus_type"] == 2 - constraint_mc_voltage_magnitude_setpoint(pm, i) + constraint_mc_voltage_magnitude_only(pm, i) for j in ref(pm, :bus_gens, i) - constraint_mc_active_gen_setpoint(pm, j) + constraint_mc_gen_power_setpoint_real(pm, j) end end end @@ -99,5 +99,5 @@ function build_mc_pf_pbs(pm::_PM.AbstractPowerModel) constraint_mc_ohms_yt_to(pm, i) end - objective_min_bus_power_slack(pm) + objective_mc_min_slack_bus_power(pm) end diff --git a/src/prob/mld.jl b/src/prob/mld.jl index 225e2acc3..1481f526f 100644 --- a/src/prob/mld.jl +++ b/src/prob/mld.jl @@ -36,24 +36,19 @@ end "Load shedding problem including storage (snap-shot)" function build_mc_mld(pm::_PM.AbstractPowerModel) - variable_mc_indicator_bus_voltage(pm; relax=true) + variable_mc_bus_voltage_indicator(pm; relax=true) variable_mc_bus_voltage_on_off(pm) - variable_mc_branch_flow(pm) - variable_mc_transformer_flow(pm) + variable_mc_branch_power(pm) + variable_mc_transformer_power(pm) - variable_mc_indicator_generation(pm; relax=true) - variable_mc_generation_on_off(pm) + variable_mc_gen_indicator(pm; relax=true) + variable_mc_gen_power_setpoint_on_off(pm) - # variable_mc_storage(pm) - _PM.variable_storage_energy(pm) - _PM.variable_storage_charge(pm) - _PM.variable_storage_discharge(pm) - variable_mc_indicator_storage(pm; relax=true) - variable_mc_on_off_storage(pm) + variable_mc_storage_power_mi(pm; relax=true) - variable_mc_indicator_demand(pm; relax=true) - variable_mc_indicator_shunt(pm; relax=true) + variable_mc_load_indicator(pm; relax=true) + variable_mc_shunt_indicator(pm; relax=true) constraint_mc_model_voltage(pm) @@ -64,17 +59,17 @@ function build_mc_mld(pm::_PM.AbstractPowerModel) constraint_mc_bus_voltage_on_off(pm) for i in ids(pm, :gen) - constraint_mc_generation_on_off(pm, i) + constraint_mc_gen_power_on_off(pm, i) end for i in ids(pm, :bus) - constraint_mc_power_balance_shed(pm, i) + constraint_mc_shed_power_balance(pm, i) end for i in ids(pm, :storage) _PM.constraint_storage_state(pm, i) _PM.constraint_storage_complementarity_nl(pm, i) - constraint_mc_storage_loss(pm, i) + constraint_mc_storage_losses(pm, i) constraint_mc_storage_thermal_limit(pm, i) end @@ -89,27 +84,27 @@ function build_mc_mld(pm::_PM.AbstractPowerModel) end for i in ids(pm, :transformer) - constraint_mc_trans(pm, i) + constraint_mc_transformer_power(pm, i) end - objective_mc_min_load_delta(pm) + objective_mc_min_load_setpoint_delta(pm) end "Load shedding problem for Branch Flow model" function build_mc_mld_bf(pm::_PM.AbstractPowerModel) - variable_mc_indicator_bus_voltage(pm; relax=true) + variable_mc_bus_voltage_indicator(pm; relax=true) variable_mc_bus_voltage_on_off(pm) variable_mc_branch_current(pm) - variable_mc_branch_flow(pm) - variable_mc_transformer_flow(pm) + variable_mc_branch_power(pm) + variable_mc_transformer_power(pm) - variable_mc_indicator_generation(pm; relax=true) - variable_mc_generation_on_off(pm) + variable_mc_gen_indicator(pm; relax=true) + variable_mc_gen_power_setpoint_on_off(pm) - variable_mc_indicator_demand(pm; relax=true) - variable_mc_indicator_shunt(pm; relax=true) + variable_mc_load_indicator(pm; relax=true) + variable_mc_shunt_indicator(pm; relax=true) constraint_mc_model_current(pm) @@ -120,15 +115,15 @@ function build_mc_mld_bf(pm::_PM.AbstractPowerModel) constraint_mc_bus_voltage_on_off(pm) for i in ids(pm, :gen) - constraint_mc_generation_on_off(pm, i) + constraint_mc_gen_power_on_off(pm, i) end for i in ids(pm, :bus) - constraint_mc_power_balance_shed(pm, i) + constraint_mc_shed_power_balance(pm, i) end for i in ids(pm, :branch) - constraint_mc_flow_losses(pm, i) + constraint_mc_power_losses(pm, i) constraint_mc_model_voltage_magnitude_difference(pm, i) constraint_mc_voltage_angle_difference(pm, i) @@ -138,30 +133,30 @@ function build_mc_mld_bf(pm::_PM.AbstractPowerModel) end for i in ids(pm, :transformer) - constraint_mc_trans(pm, i) + constraint_mc_transformer_power(pm, i) end - objective_mc_min_load_delta(pm) + objective_mc_min_load_setpoint_delta(pm) end "Standard unit commitment (!relaxed) load shedding problem" function build_mc_mld_uc(pm::_PM.AbstractPowerModel) - variable_mc_indicator_bus_voltage(pm; relax=false) + variable_mc_bus_voltage_indicator(pm; relax=false) variable_mc_bus_voltage_on_off(pm) - variable_mc_branch_flow(pm) - variable_mc_transformer_flow(pm) + variable_mc_branch_power(pm) + variable_mc_transformer_power(pm) - variable_mc_indicator_generation(pm; relax=false) - variable_mc_generation_on_off(pm) + variable_mc_gen_indicator(pm; relax=false) + variable_mc_gen_power_setpoint_on_off(pm) - variable_mc_storage(pm) - variable_mc_indicator_storage(pm; relax=false) - variable_mc_on_off_storage(pm) + variable_mc_storage_power(pm) + variable_mc_storage_indicator(pm; relax=false) + variable_mc_storage_power_on_off(pm) - variable_mc_indicator_demand(pm; relax=false) - variable_mc_indicator_shunt(pm; relax=false) + variable_mc_load_indicator(pm; relax=false) + variable_mc_shunt_indicator(pm; relax=false) constraint_mc_model_voltage(pm) @@ -172,17 +167,17 @@ function build_mc_mld_uc(pm::_PM.AbstractPowerModel) constraint_mc_bus_voltage_on_off(pm) for i in ids(pm, :gen) - constraint_mc_generation_on_off(pm, i) + constraint_mc_gen_power_on_off(pm, i) end for i in ids(pm, :bus) - constraint_mc_power_balance_shed(pm, i) + constraint_mc_shed_power_balance(pm, i) end for i in ids(pm, :storage) _PM.constraint_storage_state(pm, i) _PM.constraint_storage_complementarity_nl(pm, i) - constraint_mc_storage_loss(pm, i) + constraint_mc_storage_losses(pm, i) constraint_mc_storage_thermal_limit(pm, i) end @@ -197,8 +192,8 @@ function build_mc_mld_uc(pm::_PM.AbstractPowerModel) end for i in ids(pm, :transformer) - constraint_mc_trans(pm, i) + constraint_mc_transformer_power(pm, i) end - objective_mc_min_load_delta(pm) + objective_mc_min_load_setpoint_delta(pm) end diff --git a/src/prob/opf.jl b/src/prob/opf.jl index b11175ff9..b309d3656 100644 --- a/src/prob/opf.jl +++ b/src/prob/opf.jl @@ -18,12 +18,12 @@ end "" function build_mc_opf(pm::_PM.AbstractPowerModel) - variable_mc_voltage(pm) - variable_mc_branch_flow(pm) - variable_mc_transformer_flow(pm) - variable_mc_generation(pm) - variable_mc_load(pm) - variable_mc_storage(pm) + variable_mc_bus_voltage(pm) + variable_mc_branch_power(pm) + variable_mc_transformer_power(pm) + variable_mc_gen_power_setpoint(pm) + variable_mc_load_setpoint(pm) + variable_mc_storage_power(pm) constraint_mc_model_voltage(pm) @@ -33,22 +33,22 @@ function build_mc_opf(pm::_PM.AbstractPowerModel) # generators should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :gen) - constraint_mc_generation(pm, id) + constraint_mc_gen_setpoint(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :load) - constraint_mc_load(pm, id) + constraint_mc_load_setpoint(pm, id) end for i in ids(pm, :bus) - constraint_mc_power_balance_load(pm, i) + constraint_mc_load_power_balance(pm, i) end for i in ids(pm, :storage) _PM.constraint_storage_state(pm, i) _PM.constraint_storage_complementarity_nl(pm, i) - constraint_mc_storage_loss(pm, i) + constraint_mc_storage_losses(pm, i) constraint_mc_storage_thermal_limit(pm, i) end @@ -63,7 +63,7 @@ function build_mc_opf(pm::_PM.AbstractPowerModel) end for i in ids(pm, :transformer) - constraint_mc_trans(pm, i) + constraint_mc_transformer_power(pm, i) end _PM.objective_min_fuel_cost(pm) diff --git a/src/prob/opf_bf.jl b/src/prob/opf_bf.jl index 75ea9f7f9..00a4de469 100644 --- a/src/prob/opf_bf.jl +++ b/src/prob/opf_bf.jl @@ -13,11 +13,11 @@ end "" function build_mc_opf_bf(pm::_PM.AbstractPowerModel) # Variables - variable_mc_voltage(pm) + variable_mc_bus_voltage(pm) variable_mc_branch_current(pm) - variable_mc_branch_flow(pm) - variable_mc_transformer_flow(pm) - variable_mc_generation(pm) + variable_mc_branch_power(pm) + variable_mc_transformer_power(pm) + variable_mc_gen_power_setpoint(pm) # Constraints constraint_mc_model_current(pm) @@ -31,7 +31,7 @@ function build_mc_opf_bf(pm::_PM.AbstractPowerModel) end for i in ids(pm, :branch) - constraint_mc_flow_losses(pm, i) + constraint_mc_power_losses(pm, i) constraint_mc_model_voltage_magnitude_difference(pm, i) constraint_mc_voltage_angle_difference(pm, i) @@ -41,7 +41,7 @@ function build_mc_opf_bf(pm::_PM.AbstractPowerModel) end for i in ids(pm, :transformer) - constraint_mc_trans(pm, i) + constraint_mc_transformer_power(pm, i) end # Objective diff --git a/src/prob/opf_bf_lm.jl b/src/prob/opf_bf_lm.jl index b560c017c..f3524991f 100644 --- a/src/prob/opf_bf_lm.jl +++ b/src/prob/opf_bf_lm.jl @@ -1,5 +1,5 @@ # This problem includes load models beyond simple constant power ones; this is -# handled in variable_mc_load and constraint_mc_load. +# handled in variable_mc_load_setpoint and constraint_mc_load_setpoint. "" @@ -17,12 +17,12 @@ end "" function build_mc_opf_bf_lm(pm::_PM.AbstractPowerModel) # Variables - variable_mc_voltage(pm) + variable_mc_bus_voltage(pm) variable_mc_branch_current(pm) - variable_mc_branch_flow(pm) + variable_mc_branch_power(pm) - variable_mc_generation(pm) - variable_mc_load(pm) + variable_mc_gen_power_setpoint(pm) + variable_mc_load_setpoint(pm) # Constraints constraint_mc_model_current(pm) @@ -32,7 +32,7 @@ function build_mc_opf_bf_lm(pm::_PM.AbstractPowerModel) end for i in ids(pm, :branch) - constraint_mc_flow_losses(pm, i) + constraint_mc_power_losses(pm, i) constraint_mc_model_voltage_magnitude_difference(pm, i) constraint_mc_voltage_angle_difference(pm, i) @@ -42,11 +42,11 @@ function build_mc_opf_bf_lm(pm::_PM.AbstractPowerModel) end for i in ids(pm, :load) - constraint_mc_load(pm, i) + constraint_mc_load_setpoint(pm, i) end for i in ids(pm, :gen) - constraint_mc_generation(pm, i) + constraint_mc_gen_setpoint(pm, i) end for i in ids(pm, :bus) diff --git a/src/prob/opf_iv.jl b/src/prob/opf_iv.jl index 310b8a6e6..91509852a 100644 --- a/src/prob/opf_iv.jl +++ b/src/prob/opf_iv.jl @@ -13,11 +13,11 @@ end "" function build_mc_opf_iv(pm::_PM.AbstractPowerModel) # Variables - variable_mc_voltage(pm) + variable_mc_bus_voltage(pm) variable_mc_branch_current(pm) variable_mc_transformer_current(pm) - variable_mc_generation(pm) - variable_mc_load(pm) + variable_mc_gen_power_setpoint(pm) + variable_mc_load_setpoint(pm) # Constraints for i in ids(pm, :ref_buses) @@ -26,23 +26,23 @@ function build_mc_opf_iv(pm::_PM.AbstractPowerModel) # gens should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :gen) - constraint_mc_generation(pm, id) + constraint_mc_gen_setpoint(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :load) - constraint_mc_load(pm, id) + constraint_mc_load_setpoint(pm, id) end for i in ids(pm, :bus) - constraint_mc_current_balance_load(pm, i) + constraint_mc_load_current_balance(pm, i) end for i in ids(pm, :branch) constraint_mc_current_from(pm, i) constraint_mc_current_to(pm, i) - constraint_mc_voltage_drop(pm, i) + constraint_mc_bus_voltage_drop(pm, i) constraint_mc_voltage_angle_difference(pm, i) @@ -51,7 +51,7 @@ function build_mc_opf_iv(pm::_PM.AbstractPowerModel) end for i in ids(pm, :transformer) - constraint_mc_trans(pm, i) + constraint_mc_transformer_power(pm, i) end # Objective diff --git a/src/prob/opf_oltc.jl b/src/prob/opf_oltc.jl index bdf33836b..8fecf5bc8 100644 --- a/src/prob/opf_oltc.jl +++ b/src/prob/opf_oltc.jl @@ -18,12 +18,12 @@ end "" function build_mc_opf_oltc(pm::_PM.AbstractPowerModel) - variable_mc_voltage(pm) - variable_mc_branch_flow(pm) - variable_mc_generation(pm) - variable_mc_load(pm) - variable_mc_transformer_flow(pm) - variable_mc_oltc_tap(pm) + variable_mc_bus_voltage(pm) + variable_mc_branch_power(pm) + variable_mc_gen_power_setpoint(pm) + variable_mc_load_setpoint(pm) + variable_mc_transformer_power(pm) + variable_mc_oltc_transformer_tap(pm) constraint_mc_model_voltage(pm) @@ -33,16 +33,16 @@ function build_mc_opf_oltc(pm::_PM.AbstractPowerModel) # generators should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :gen) - constraint_mc_generation(pm, id) + constraint_mc_gen_setpoint(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :load) - constraint_mc_load(pm, id) + constraint_mc_load_setpoint(pm, id) end for i in ids(pm, :bus) - constraint_mc_power_balance_load(pm, i) + constraint_mc_load_power_balance(pm, i) end for i in ids(pm, :branch) @@ -56,7 +56,7 @@ function build_mc_opf_oltc(pm::_PM.AbstractPowerModel) end for i in ids(pm, :transformer) - constraint_mc_trans(pm, i, fix_taps=false) + constraint_mc_transformer_power(pm, i, fix_taps=false) end _PM.objective_min_fuel_cost(pm) diff --git a/src/prob/pf.jl b/src/prob/pf.jl index d8ac4025b..ba5cddf6d 100644 --- a/src/prob/pf.jl +++ b/src/prob/pf.jl @@ -24,11 +24,11 @@ end "" function build_mc_pf(pm::_PM.AbstractPowerModel) - variable_mc_voltage(pm; bounded=false) - variable_mc_branch_flow(pm; bounded=false) - variable_mc_transformer_flow(pm; bounded=false) - variable_mc_generation(pm; bounded=false) - variable_mc_load(pm; bounded=false) + variable_mc_bus_voltage(pm; bounded=false) + variable_mc_branch_power(pm; bounded=false) + variable_mc_transformer_power(pm; bounded=false) + variable_mc_gen_power_setpoint(pm; bounded=false) + variable_mc_load_setpoint(pm; bounded=false) constraint_mc_model_voltage(pm) @@ -36,30 +36,30 @@ function build_mc_pf(pm::_PM.AbstractPowerModel) @assert bus["bus_type"] == 3 constraint_mc_theta_ref(pm, i) - constraint_mc_voltage_magnitude_setpoint(pm, i) + constraint_mc_voltage_magnitude_only(pm, i) end # gens should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :gen) - constraint_mc_generation(pm, id) + constraint_mc_gen_setpoint(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :load) - constraint_mc_load(pm, id) + constraint_mc_load_setpoint(pm, id) end for (i,bus) in ref(pm, :bus) - constraint_mc_power_balance_load(pm, i) + constraint_mc_load_power_balance(pm, i) # PV Bus Constraints if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) # this assumes inactive generators are filtered out of bus_gens @assert bus["bus_type"] == 2 - constraint_mc_voltage_magnitude_setpoint(pm, i) + constraint_mc_voltage_magnitude_only(pm, i) for j in ref(pm, :bus_gens, i) - constraint_mc_active_gen_setpoint(pm, j) + constraint_mc_gen_power_setpoint_real(pm, j) end end end @@ -70,6 +70,6 @@ function build_mc_pf(pm::_PM.AbstractPowerModel) end for i in ids(pm, :transformer) - constraint_mc_trans(pm, i) + constraint_mc_transformer_power(pm, i) end end diff --git a/src/prob/pf_bf.jl b/src/prob/pf_bf.jl index 8eb01b20a..643218c90 100644 --- a/src/prob/pf_bf.jl +++ b/src/prob/pf_bf.jl @@ -13,10 +13,10 @@ end "" function build_mc_pf_bf(pm::_PM.AbstractPowerModel) # Variables - variable_mc_voltage(pm; bounded=false) + variable_mc_bus_voltage(pm; bounded=false) variable_mc_branch_current(pm) - variable_mc_branch_flow(pm) - variable_mc_generation(pm; bounded=false) + variable_mc_branch_power(pm) + variable_mc_gen_power_setpoint(pm; bounded=false) # Constraints constraint_mc_model_current(pm) @@ -25,7 +25,7 @@ function build_mc_pf_bf(pm::_PM.AbstractPowerModel) constraint_mc_theta_ref(pm, i) @assert bus["bus_type"] == 3 - constraint_mc_voltage_magnitude_setpoint(pm, i) + constraint_mc_voltage_magnitude_only(pm, i) end for i in ids(pm, :bus) @@ -36,15 +36,15 @@ function build_mc_pf_bf(pm::_PM.AbstractPowerModel) # this assumes inactive generators are filtered out of bus_gens @assert bus["bus_type"] == 2 - constraint_mc_voltage_magnitude_setpoint(pm, i) + constraint_mc_voltage_magnitude_only(pm, i) for j in ref(pm, :bus_gens, i) - constraint_mc_active_gen_setpoint(pm, j) + constraint_mc_gen_power_setpoint_real(pm, j) end end end for i in ids(pm, :branch) - constraint_mc_flow_losses(pm, i) + constraint_mc_power_losses(pm, i) constraint_mc_model_voltage_magnitude_difference(pm, i) constraint_mc_voltage_angle_difference(pm, i) diff --git a/src/prob/pf_iv.jl b/src/prob/pf_iv.jl index 545b5c2cb..2f9098e91 100644 --- a/src/prob/pf_iv.jl +++ b/src/prob/pf_iv.jl @@ -13,39 +13,39 @@ end "" function build_mc_pf_iv(pm::_PM.AbstractPowerModel) # Variables - variable_mc_voltage(pm, bounded = false) + variable_mc_bus_voltage(pm, bounded = false) variable_mc_branch_current(pm, bounded = false) variable_mc_transformer_current(pm, bounded = false) - variable_mc_generation(pm, bounded = false) - variable_mc_load(pm, bounded = false) + variable_mc_gen_power_setpoint(pm, bounded = false) + variable_mc_load_setpoint(pm, bounded = false) # Constraints for (i,bus) in ref(pm, :ref_buses) @assert bus["bus_type"] == 3 constraint_mc_theta_ref(pm, i) - constraint_mc_voltage_magnitude_setpoint(pm, i) + constraint_mc_voltage_magnitude_only(pm, i) end # gens should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :gen) - constraint_mc_generation(pm, id) + constraint_mc_gen_setpoint(pm, id) end # loads should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :load) - constraint_mc_load(pm, id) + constraint_mc_load_setpoint(pm, id) end for (i,bus) in ref(pm, :bus) - constraint_mc_current_balance_load(pm, i) + constraint_mc_load_current_balance(pm, i) # PV Bus Constraints if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) # this assumes inactive generators are filtered out of bus_gens @assert bus["bus_type"] == 2 - constraint_mc_voltage_magnitude_setpoint(pm, i) + constraint_mc_voltage_magnitude_only(pm, i) for j in ref(pm, :bus_gens, i) - constraint_mc_active_gen_setpoint(pm, j) + constraint_mc_gen_power_setpoint_real(pm, j) end end end @@ -54,10 +54,10 @@ function build_mc_pf_iv(pm::_PM.AbstractPowerModel) constraint_mc_current_from(pm, i) constraint_mc_current_to(pm, i) - constraint_mc_voltage_drop(pm, i) + constraint_mc_bus_voltage_drop(pm, i) end for i in ids(pm, :transformer) - constraint_mc_trans(pm, i) + constraint_mc_transformer_power(pm, i) end end diff --git a/src/prob/test.jl b/src/prob/test.jl index ac39b3c79..2937f8060 100644 --- a/src/prob/test.jl +++ b/src/prob/test.jl @@ -1,78 +1,3 @@ -###### -# -# These are toy problem formulations used to test advanced features -# such as storage devices -# -###### -# "multi-network opf with storage" -# function _run_mn_mc_opf(data::Dict{String,Any}, model_type, solver; kwargs...) -# return run_mc_model(data, model_type, solver, _build_mn_mc_strg_opf; multinetwork=true, kwargs...) -# end -# -# -# "multi-network opf with storage" -# function _run_mn_mc_opf(file::String, model_type, solver; kwargs...) -# return run_mn_mc_opf(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -# end -# -# -# "multi-network opf with storage" -# function _build_mn_mc_strg_opf(pm::_PM.AbstractPowerModel) -# for (n, network) in nws(pm) -# variable_mc_voltage(pm; nw=n) -# constraint_mc_model_voltage(pm; nw=n) -# variable_mc_branch_flow(pm; nw=n) -# variable_mc_generation(pm; nw=n) -# variable_mc_storage(pm; nw=n) -# -# for i in ids(pm, :ref_buses; nw=n) -# constraint_mc_theta_ref(pm, i; nw=n) -# end -# -# for i in ids(pm, :bus; nw=n) -# constraint_mc_power_balance(pm, i; nw=n) -# end -# -# for i in ids(pm, :storage; nw=n) -# _PM.constraint_storage_state(pm, i; nw=n) -# _PM.constraint_storage_complementarity_nl(pm, i; nw=n) -# constraint_mc_storage_loss(pm, i; nw=n) -# constraint_mc_storage_thermal_limit(pm, i; nw=n) -# end -# -# for i in ids(pm, :branch; nw=n) -# constraint_mc_ohms_yt_from(pm, i; nw=n) -# constraint_mc_ohms_yt_to(pm, i; nw=n) -# -# constraint_mc_voltage_angle_difference(pm, i; nw=n) -# -# constraint_mc_thermal_limit_from(pm, i; nw=n) -# constraint_mc_thermal_limit_to(pm, i; nw=n) -# end -# -# for i in ids(pm, :transformer; nw=n) -# constraint_mc_trans(pm, i; nw=n) -# end -# end -# -# network_ids = sort(collect(nw_ids(pm))) -# -# n_1 = network_ids[1] -# for i in ids(pm, :storage; nw=n_1) -# _PM.constraint_storage_state(pm, i; nw=n_1) -# end -# -# for n_2 in network_ids[2:end] -# for i in ids(pm, :storage; nw=n_2) -# _PM.constraint_storage_state(pm, i, n_1, n_2) -# end -# n_1 = n_2 -# end -# -# _PM.objective_min_fuel_cost(pm) -# end - - ###### # # Formulations from PowerModels @@ -87,19 +12,19 @@ end "" function _build_mc_ucopf(pm::_PM.AbstractPowerModel) for (n, network) in nws(pm) - variable_mc_voltage(pm, nw=n) - variable_mc_branch_flow(pm, nw=n) - variable_mc_transformer_flow(pm, nw=n) + variable_mc_bus_voltage(pm, nw=n) + variable_mc_branch_power(pm, nw=n) + variable_mc_transformer_power(pm, nw=n) - variable_mc_indicator_generation(pm, nw=n) - variable_mc_generation_on_off(pm, nw=n) + variable_mc_gen_indicator(pm, nw=n) + variable_mc_gen_power_setpoint_on_off(pm, nw=n) constraint_mc_model_voltage(pm, nw=n) - variable_mc_on_off_storage(pm, nw=n) + variable_mc_storage_power_on_off(pm, nw=n) _PM.variable_storage_energy(pm, nw=n) _PM.variable_storage_charge(pm, nw=n) _PM.variable_storage_discharge(pm, nw=n) @@ -128,7 +53,7 @@ function _build_mc_ucopf(pm::_PM.AbstractPowerModel) for i in ids(pm, :transformer, nw=n) - constraint_mc_trans(pm, i, nw=n) + constraint_mc_transformer_power(pm, i, nw=n) end # for i in ids(pm, :dcline, nw=n) @@ -138,7 +63,7 @@ function _build_mc_ucopf(pm::_PM.AbstractPowerModel) for i in ids(pm, :storage; nw=n) # _PM.constraint_storage_state(pm, i; nw=n) _PM.constraint_storage_complementarity_mi(pm, i; nw=n) - constraint_mc_storage_loss(pm, i; nw=n) + constraint_mc_storage_losses(pm, i; nw=n) constraint_mc_storage_thermal_limit(pm, i; nw=n) constraint_mc_storage_on_off(pm, i; nw=n) @@ -171,10 +96,10 @@ end "" function _build_mn_mc_opf(pm::_PM.AbstractPowerModel) for (n, network) in nws(pm) - variable_mc_voltage(pm, nw=n) - variable_mc_branch_flow(pm, nw=n) - variable_mc_transformer_flow(pm, nw=n) - variable_mc_generation(pm, nw=n) + variable_mc_bus_voltage(pm, nw=n) + variable_mc_branch_power(pm, nw=n) + variable_mc_transformer_power(pm, nw=n) + variable_mc_gen_power_setpoint(pm, nw=n) constraint_mc_model_voltage(pm, nw=n) @@ -199,7 +124,7 @@ function _build_mn_mc_opf(pm::_PM.AbstractPowerModel) for i in ids(pm, :transformer, nw=n) - constraint_mc_trans(pm, i, nw=n) + constraint_mc_transformer_power(pm, i, nw=n) end # for i in ids(pm, :dcline, nw=n) @@ -219,13 +144,13 @@ end function _build_mn_mc_opf_strg(pm::_PM.AbstractPowerModel) for (n, network) in nws(pm) - variable_mc_voltage(pm, nw=n) - variable_mc_branch_flow(pm, nw=n) - variable_mc_transformer_flow(pm, nw=n) - variable_mc_generation(pm, nw=n) - variable_mc_load(pm, nw=n) + variable_mc_bus_voltage(pm, nw=n) + variable_mc_branch_power(pm, nw=n) + variable_mc_transformer_power(pm, nw=n) + variable_mc_gen_power_setpoint(pm, nw=n) + variable_mc_load_setpoint(pm, nw=n) - variable_mc_storage(pm, nw=n) + variable_mc_storage_power(pm, nw=n) constraint_mc_model_voltage(pm, nw=n) @@ -235,16 +160,16 @@ function _build_mn_mc_opf_strg(pm::_PM.AbstractPowerModel) # generators should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :gen) - constraint_mc_generation(pm, id, nw=n) + constraint_mc_gen_setpoint(pm, id, nw=n) end # loads should be constrained before KCL, or Pd/Qd undefined for id in ids(pm, :load) - constraint_mc_load(pm, id, nw=n) + constraint_mc_load_setpoint(pm, id, nw=n) end for i in ids(pm, :bus, nw=n) - constraint_mc_power_balance_load(pm, i, nw=n) + constraint_mc_load_power_balance(pm, i, nw=n) end @@ -260,12 +185,12 @@ function _build_mn_mc_opf_strg(pm::_PM.AbstractPowerModel) for i in ids(pm, :transformer, nw=n) - constraint_mc_trans(pm, i, nw=n) + constraint_mc_transformer_power(pm, i, nw=n) end for i in ids(pm, :storage, nw=n) _PM.constraint_storage_complementarity_nl(pm, i; nw=n) - constraint_mc_storage_loss(pm, i; nw=n) + constraint_mc_storage_losses(pm, i; nw=n) constraint_mc_storage_thermal_limit(pm, i, nw=n) end diff --git a/test/mld.jl b/test/mld.jl index c03826014..83bb2908b 100644 --- a/test/mld.jl +++ b/test/mld.jl @@ -18,11 +18,10 @@ end @testset "5-bus storage acp mld" begin - mp_data = result = run_mc_mld(case5_strg, ACPPowerModel, ipopt_solver) @test result["termination_status"] == LOCALLY_SOLVED - @test isapprox(result["objective"], 0.3553; atol=1e-2) + @test isapprox(result["objective"], 0.3432; atol=1e-2) @test isapprox(result["solution"]["load"]["1"]["status"], 1.000; atol=1e-3) end From 88d723e3672c02e71874d5f90f96049c8c7c2a81 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 27 Apr 2020 16:00:05 -0600 Subject: [PATCH 172/224] ADD: stub for eng2math documentation --- docs/src/eng2math.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 docs/src/eng2math.md diff --git a/docs/src/eng2math.md b/docs/src/eng2math.md new file mode 100644 index 000000000..6b8474970 --- /dev/null +++ b/docs/src/eng2math.md @@ -0,0 +1,31 @@ +# Engineering to Mathematical Data Model Mapping + +In this document we define the mapping from the engineering data model down to the mathematical data model for each physical component. + +## `bus` + +## `line` + +## `switch` + +## `transformer` + +## `line_reactor` + +## `series_capacitor` + +## `shunt` + +### `shunt_capacitor` + +### `shunt_reactor` + +### `fault` + +## `load` + +## `generator` + +## `solar` + +## `voltage_source` From 6802cf629d737c0b852863707a216e9c17d0ad55 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 27 Apr 2020 15:59:43 -0600 Subject: [PATCH 173/224] WIP: add enums switch state configuration generator control mode load model --- examples/data_model_test.jl | 28 ++++++------- src/core/constraint_template.jl | 10 ++--- src/core/data.jl | 24 +++++------ src/core/ref.jl | 8 ++-- src/core/types.jl | 21 ++++++++-- src/data_model/checks.jl | 20 ++++----- src/data_model/components.jl | 16 ++++---- src/data_model/eng2math.jl | 20 ++++----- src/data_model/units.jl | 2 +- src/form/bf_mx.jl | 34 ++++++++-------- src/io/common.jl | 2 +- src/io/dss_structs.jl | 18 ++++----- src/io/json.jl | 45 ++++++++++++++++++--- src/io/opendss.jl | 60 +++++++++------------------ src/io/utils.jl | 72 ++++++++++++++++++++++++++++----- test/delta_gens.jl | 2 +- test/opendss.jl | 6 +-- 17 files changed, 236 insertions(+), 152 deletions(-) diff --git a/examples/data_model_test.jl b/examples/data_model_test.jl index d9815f243..1e6c77a52 100644 --- a/examples/data_model_test.jl +++ b/examples/data_model_test.jl @@ -41,20 +41,20 @@ function make_test_data_model() # add_load!(data_model, "1", bus="7", connections=[2,4], pd=[1.0], qd=[1.0]) - add_load!(data_model, "2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model="constant_current", vnom=[230*sqrt(3)]) - add_load!(data_model, "3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model="constant_impedance", vnom=[230]) - add_load!(data_model, "4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model="exponential", vnom=[230], alpha=[1.2], beta=[1.5]) - add_load!(data_model, "5", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3)) - add_load!(data_model, "6", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_current", vnom=fill(230, 3)) - add_load!(data_model, "7", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="constant_impedance", vnom=fill(230, 3)) - add_load!(data_model, "8", bus="4", configuration="wye", pd=fill(1.0, 3), qd=fill(1.0, 3), model="exponential", vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]) - add_load!(data_model, "9", bus="5", configuration="delta", pd=fill(1.0, 3), qd=fill(1.0, 3)) + add_load!(data_model, "2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model=CURRENT, vnom=[230*sqrt(3)]) + add_load!(data_model, "3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model=IMPEDANCE, vnom=[230]) + add_load!(data_model, "4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model=EXPONENTIAL, vnom=[230], alpha=[1.2], beta=[1.5]) + add_load!(data_model, "5", bus="4", configuration=WYE, pd=fill(1.0, 3), qd=fill(1.0, 3)) + add_load!(data_model, "6", bus="4", configuration=WYE, pd=fill(1.0, 3), qd=fill(1.0, 3), model=CURRENT, vnom=fill(230, 3)) + add_load!(data_model, "7", bus="4", configuration=WYE, pd=fill(1.0, 3), qd=fill(1.0, 3), model=IMPEDANCE, vnom=fill(230, 3)) + add_load!(data_model, "8", bus="4", configuration=WYE, pd=fill(1.0, 3), qd=fill(1.0, 3), model=EXPONENTIAL, vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]) + add_load!(data_model, "9", bus="5", configuration=DELTA, pd=fill(1.0, 3), qd=fill(1.0, 3)) - add_generator!(data_model, "1", bus="1", configuration="wye") + add_generator!(data_model, "1", bus="1", configuration=WYE) add_transformer_nw!(data_model, "1", bus=["5", "9", "10"], connections=[[1,2,3], [1,2,3,4], [1,2,3]], vnom=[0.230, 0.230, 0.230], snom=[0.230, 0.230, 0.230], - configuration=["delta", "wye", "delta"], + configuration=[DELTA, WYE, DELTA], xsc=[0.0, 0.0, 0.0], rs=[0.0, 0.0, 1.0], noloadloss=0.05, @@ -62,7 +62,7 @@ function make_test_data_model() ) add_capacitor!(data_model, "cap_3ph", bus="3", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3]) - add_capacitor!(data_model, "cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration="delta", connections=[1,2,3]) + add_capacitor!(data_model, "cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration=DELTA, connections=[1,2,3]) add_capacitor!(data_model, "cap_2ph_yg", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-grounded") add_capacitor!(data_model, "cap_2ph_yfl", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-floating") add_capacitor!(data_model, "cap_2ph_y", bus="5", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4]) @@ -93,7 +93,7 @@ function make_3wire_data_model() # add!(data_model, "transformer_nw", create_transformer("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], # [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], - # configuration=["delta", "wye", "delta"], + # configuration=[DELTA, WYE, DELTA], # xsc=[0.0, 0.0, 0.0], # rs=[0.0, 0.0, 0.0], # loadloss=0.00, @@ -102,7 +102,7 @@ function make_3wire_data_model() add_transformer!(data_model, "1", ["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], vnom=[0.230, 0.230], snom=[0.230, 0.230], - configuration=["wye", "wye"], + configuration=[WYE, WYE], xsc=[0.0], rs=[0.0, 0.0], noloadloss=0.00, @@ -122,7 +122,7 @@ function make_3wire_data_model() # qg_max=fill( 100, 3), # )) - #add!(data_model, "capacitor", create_capacitor(1, "tr_sec", 0.230*sqrt(3), qd_ref=[1.0, 1.0, 1.0]*1E-3, connections=[1,2,3], configuration="delta")) + #add!(data_model, "capacitor", create_capacitor(1, "tr_sec", 0.230*sqrt(3), qd_ref=[1.0, 1.0, 1.0]*1E-3, connections=[1,2,3], configuration=DELTA)) return data_model end diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 7ce0d4041..03b42e389 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -145,9 +145,9 @@ function constraint_mc_transformer_power(pm::_PM.AbstractPowerModel, i::Int; nw: #TODO change this once migrated to new data model pol = transformer["polarity"] - if config == "wye" + if config == WYE constraint_mc_transformer_power_yy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) - elseif config == "delta" + elseif config == DELTA constraint_mc_transformer_power_dy(pm, nw, i, f_bus, t_bus, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) elseif config == "zig-zag" Memento.error(_LOGGER, "Zig-zag not yet supported.") @@ -273,11 +273,11 @@ function constraint_mc_load_setpoint(pm::_PM.AbstractPowerModel, id::Int; nw::In load = ref(pm, nw, :load, id) bus = ref(pm, nw,:bus, load["load_bus"]) - conn = haskey(load, "configuration") ? load["configuration"] : "wye" + conn = haskey(load, "configuration") ? load["configuration"] : WYE a, alpha, b, beta = _load_expmodel_params(load, bus) - if conn=="wye" + if conn==WYE constraint_mc_load_setpoint_wye(pm, nw, id, load["load_bus"], a, alpha, b, beta) else constraint_mc_load_setpoint_delta(pm, nw, id, load["load_bus"], a, alpha, b, beta) @@ -307,7 +307,7 @@ function constraint_mc_gen_setpoint(pm::_PM.AbstractPowerModel, id::Int; nw::Int qmin = get(generator, "qmin", fill(-Inf, N)) qmax = get(generator, "qmax", fill( Inf, N)) - if get(generator, "configuration", "wye") == "wye" + if get(generator, "configuration", WYE) == WYE constraint_mc_gen_setpoint_wye(pm, nw, id, bus["index"], pmin, pmax, qmin, qmax; report=report, bounded=bounded) else constraint_mc_gen_setpoint_delta(pm, nw, id, bus["index"], pmin, pmax, qmin, qmax; report=report, bounded=bounded) diff --git a/src/core/data.jl b/src/core/data.jl index f1c601684..db0166d3a 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -131,7 +131,7 @@ function calculate_tm_scale(trans::Dict{String,Any}, bus_fr::Dict{String,Any}, b config = trans["configuration"] tm_scale = tm_nom*(t_vbase/f_vbase) - if config == "delta" + if config == DELTA #TODO is this still needed? tm_scale *= sqrt(3) elseif config == "zig-zag" @@ -223,17 +223,17 @@ function _load_expmodel_params(load::Dict, bus::Dict) pd = load["pd"] qd = load["qd"] ncnds = length(pd) - if load["model"]=="constant_power" + if load["model"]==POWER return (pd, zeros(ncnds), qd, zeros(ncnds)) else # get exponents - if load["model"]=="constant_current" + if load["model"]==CURRENT alpha = ones(ncnds) beta =ones(ncnds) - elseif load["model"]=="constant_impedance" + elseif load["model"]==IMPEDANCE alpha = ones(ncnds)*2 beta =ones(ncnds)*2 - elseif load["model"]=="exponential" + elseif load["model"]==EXPONENTIAL alpha = load["alpha"] @assert(all(alpha.>=0)) beta = load["beta"] @@ -255,10 +255,10 @@ multiphase load. These are inferred from vmin/vmax for wye loads and from _calc_bus_vm_ll_bounds for delta loads. """ function _calc_load_vbounds(load::Dict, bus::Dict) - if load["configuration"]=="wye" + if load["configuration"]==WYE vmin = bus["vmin"] vmax = bus["vmax"] - elseif load["configuration"]=="delta" + elseif load["configuration"]==DELTA vmin, vmax = _calc_bus_vm_ll_bounds(bus) end return vmin, vmax @@ -269,9 +269,9 @@ Returns a Bool, indicating whether the convex hull of the voltage-dependent relationship needs a cone inclusion constraint. """ function _check_load_needs_cone(load::Dict) - if load["model"]=="constant_current" + if load["model"]==CURRENT return true - elseif load["model"]=="exponential" + elseif load["model"]==EXPONENTIAL return true else return false @@ -507,12 +507,12 @@ function _make_multiconductor!(data::Dict{String,<:Any}, conductors::Real) end for (_, load) in data["load"] - load["model"] = "constant_power" - load["configuration"] = "wye" + load["model"] = POWER + load["configuration"] = WYE end for (_, load) in data["gen"] - load["configuration"] = "wye" + load["configuration"] = WYE end end diff --git a/src/core/ref.jl b/src/core/ref.jl index 70946f84d..c5ae43ce9 100644 --- a/src/core/ref.jl +++ b/src/core/ref.jl @@ -85,7 +85,7 @@ function _calc_mc_transformer_Tvi(pm::_PM.AbstractPowerModel, i::Int; nw=pm.cnw) end end # make sure the secondary is y+123 - if trans["config_to"]["type"]!="wye" + if trans["config_to"]["type"]!=WYE Memento.error(_LOGGER, "Secondary should always be of wye type.") end if trans["config_to"]["cnd"]!=[1,2,3] @@ -104,7 +104,7 @@ function _calc_mc_transformer_Tvi(pm::_PM.AbstractPowerModel, i::Int; nw=pm.cnw) Memento.error(_LOGGER, "The polarity should be either \'+\' or \'-\', but got \'$polarity\'.") end dyz = trans["config_fr"]["type"] - if !(dyz in ["delta", "wye"]) + if !(dyz in [DELTA, WYE]) Memento.error(_LOGGER, "The winding type should be either delta or wye, but got \'$dyz\'.") end # for now, grounded by default @@ -119,7 +119,7 @@ function _calc_mc_transformer_Tvi(pm::_PM.AbstractPowerModel, i::Int; nw=pm.cnw) Tw = (polarity=='+') ? Tw : -Tw #Tw = diagm(0=>ones(Float64, 3)) vmult = 1.0 # compensate for change in LN - if dyz=="wye" + if dyz==WYE Tv_fr = Tw Tv_im = diagm(0=>ones(Float64, 3)) Ti_fr = Tw @@ -129,7 +129,7 @@ function _calc_mc_transformer_Tvi(pm::_PM.AbstractPowerModel, i::Int; nw=pm.cnw) # Ti_fr = [Ti_fr; ones(1,3)] # Ti_im = [Ti_im; zeros(1,3)] # end - elseif dyz=="delta" + elseif dyz==DELTA Tv_fr = Tdelt*Tw Tv_im = diagm(0=>ones(Float64, 3)) Ti_fr = Tw diff --git a/src/core/types.jl b/src/core/types.jl index bc879a949..1d34010b0 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -1,15 +1,28 @@ "Supported data model types" -@enum DataModelType ENGINEERING MATHEMATICAL DSS MATPOWER +@enum DataModel ENGINEERING MATHEMATICAL DSS MATPOWER +"Load Models" +@enum LoadModel POWER CURRENT IMPEDANCE EXPONENTIAL ZIP -"" +"Switch States" +@enum SwitchState OPEN CLOSED FIXED_OPEN FIXED_CLOSED + +"Generator Control Modes" +@enum GeneratorControlMode PQ PV Z INVERTER # TODO needs better names + +"Configurations" +@enum ConnectionConfiguration WYE DELTA + + +"Base Abstract NLP Unbalanced Branch Flow Model" abstract type AbstractNLPUBFModel <: _PM.AbstractBFQPModel end -"" +"Base Abstract Conic Unbalanced Branch Flow Model" abstract type AbstractConicUBFModel <: _PM.AbstractBFConicModel end +"Collection of Unbalanced Branch Flow Models" AbstractUBFModels = Union{AbstractNLPUBFModel, AbstractConicUBFModel} @@ -21,6 +34,7 @@ abstract type SDPUBFModel <: AbstractConicUBFModel end abstract type SDPUBFKCLMXModel <: SDPUBFModel end +"Collection of Semidefinite Models" # TODO Better documentation, name? KCLMXModels = Union{SDPUBFKCLMXModel} @@ -32,6 +46,7 @@ abstract type SOCNLPUBFModel <: AbstractNLPUBFModel end abstract type SOCConicUBFModel <: AbstractConicUBFModel end +"Collection of Second Order Cone Models" SOCUBFModels = Union{SOCNLPUBFModel, SOCConicUBFModel} diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index 089a2e000..e99a8b438 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -429,9 +429,9 @@ end "checks the connection configuration and infers the dimensions of the connection (number of connected terminals)" function _check_configuration_infer_dim(object::Dict{String,<:Any}; context::Union{String,Missing}=missing)::Int conf = object["configuration"] - @assert conf in ["delta", "wye"] "$context: the configuration should be \'delta\' or \'wye\', not \'$conf\'." + @assert conf in [DELTA, WYE] "$context: the configuration should be \'delta\' or \'wye\', not \'$conf\'." - return conf=="wye" ? length(object["connections"])-1 : length(object["connections"]) + return conf==WYE ? length(object["connections"])-1 : length(object["connections"]) end @@ -457,12 +457,12 @@ function _check_load(data_eng::Dict{String,<:Any}, name::Any) N = _check_configuration_infer_dim(load; context="load $name") model = load["model"] - @assert model in ["constant_power", "constant_impedance", "constant_current", "exponential"] + @assert model in [POWER, IMPEDANCE, CURRENT, EXPONENTIAL] - if model=="constant_power" + if model==POWER _check_has_keys(load, ["pd", "qd"], context="load $name, $model:") _check_has_size(load, ["pd", "qd"], N, context="load $name, $model:") - elseif model=="exponential" + elseif model==EXPONENTIAL _check_has_keys(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], context="load $name, $model") _check_has_size(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], N, context="load $name, $model:") else @@ -534,11 +534,11 @@ function _check_transformer(data_eng::Dict{String,<:Any}, name::Any) nphs = [] for w in 1:nrw - @assert transformer["configuration"][w] in ["wye", "delta"] + @assert transformer["configuration"][w] in [WYE, DELTA] conf = transformer["configuration"][w] conns = transformer["connections"][w] - nph = conf=="wye" ? length(conns)-1 : length(conns) + nph = conf==WYE ? length(conns)-1 : length(conns) @assert all(nph.==nphs) "transformer $name: winding $w has a different number of phases than the previous ones." push!(nphs, nph) @@ -555,15 +555,15 @@ function _check_shunt_capacitor(data_eng::Dict{String,<:Any}, name::Any) N = length(shunt_capacitor["connections"]) config = shunt_capacitor["configuration"] - if config=="wye" + if config==WYE @assert length(shunt_capacitor["qd_ref"])==N-1 "capacitor $name: qd_ref should have $(N-1) elements." else @assert length(shunt_capacitor["qd_ref"])==N "capacitor $name: qd_ref should have $N elements." end - @assert config in ["delta", "wye", "wye-grounded", "wye-floating"] + @assert config in [DELTA, WYE, "wye-grounded", "wye-floating"] - if config=="delta" + if config==DELTA @assert N>=3 "Capacitor $name: delta-connected capacitors should have at least 3 elements." end diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 13463cf91..67053035e 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -186,13 +186,13 @@ function create_load(; kwargs...) load = Dict{String,Any}( "status" => get(kwargs, :status, 1), - "configuration" => get(kwargs, :configuration, "wye"), - "model" => get(kwargs, :model, "constant_power"), - "connections" => get(kwargs, :connections, get(kwargs, :configuration, "wye")=="wye" ? [1, 2, 3, 4] : [1, 2, 3]), + "configuration" => get(kwargs, :configuration, WYE), + "model" => get(kwargs, :model, POWER), + "connections" => get(kwargs, :connections, get(kwargs, :configuration, WYE)==WYE ? [1, 2, 3, 4] : [1, 2, 3]), "vnom" => get(kwargs, :vnom, 1.0) ) - if load["model"]=="constant_power" + if load["model"]==POWER load["pd"] = get(kwargs, :pd, fill(0.0, 3)) load["qd"] = get(kwargs, :qd, fill(0.0, 3)) else @@ -212,11 +212,11 @@ function create_generator(; kwargs...) generator = Dict{String,Any}( "status" => get(kwargs, :status, 1), - "configuration" => get(kwargs, :configuration, "wye"), + "configuration" => get(kwargs, :configuration, WYE), "cost" => get(kwargs, :cost, [1.0, 0.0]*1E-3), ) - generator["connections"] = get(kwargs, :connections, generator["configuration"]=="wye" ? [1, 2, 3, 4] : [1, 2, 3]) + generator["connections"] = get(kwargs, :connections, generator["configuration"]==WYE ? [1, 2, 3, 4] : [1, 2, 3]) _add_unused_kwargs!(generator, kwargs) @@ -233,7 +233,7 @@ function create_transformer(; kwargs...) transformer = Dict{String,Any}( "status" => get(kwargs, :status, 1), - "configuration" => get(kwargs, :configuration, fill("wye", n_windings)), + "configuration" => get(kwargs, :configuration, fill(WYE, n_windings)), "polarity" => get(kwargs, :polarity, fill(true, n_windings)), "rs" => get(kwargs, :rs, zeros(n_windings)), "xsc" => get(kwargs, :xsc, zeros(n_windings^2-n_windings)), @@ -257,7 +257,7 @@ end function create_shunt_capacitor(; kwargs...) shunt_capacitor = Dict{String,Any}( "status" => get(kwargs, :status, 1), - "configuration" => get(kwargs, :configuration, "wye"), + "configuration" => get(kwargs, :configuration, WYE), "connections" => get(kwargs, :connections, collect(1:4)), "qd_ref" => get(kwargs, :qd_ref, fill(0.0, 3)), ) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 8c6fd5a30..e420812ef 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -395,7 +395,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic for w in 1:nrw # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction - tm_nom = eng_obj["configuration"][w]=="delta" ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] + tm_nom = eng_obj["configuration"][w]==DELTA ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] transformer_2wa_obj = Dict{String,Any}( "name" => "_virtual_transformer.$name.$w", "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", @@ -419,7 +419,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic if kron_reduced # TODO fix how padding works, this is a workaround to get bank working - if all(eng_obj["configuration"] .== "wye") + if all(eng_obj["configuration"] .== WYE) f_connections = transformer_2wa_obj["f_connections"] _pad_properties!(transformer_2wa_obj, ["tm_lb", "tm_ub", "tm_set"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) _pad_properties!(transformer_2wa_obj, ["tm_fix"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=false) @@ -448,7 +448,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["state"] = lowercase(get(eng_obj, "state", "closed")) == "open" ? 0 : 1 + math_obj["state"] = get(eng_obj, "state", CLOSED) # OPF bounds for (fr_key, to_key) in zip(["cm_ub"], ["c_rating"]) @@ -476,7 +476,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A bus_obj = Dict{String,Any}( "name" => "_virtual_bus.switch.$name", "bus_i" => length(data_math["bus"])+1, - "bus_type" => lowercase(get(eng_obj, "state", "closed")) == "open" ? 4 : 1, + "bus_type" => get(eng_obj, "state", CLOSED) == OPEN ? 4 : 1, "vmin" => f_bus["vmin"], "vmax" => f_bus["vmax"], "base_kv" => f_bus["base_kv"], @@ -511,7 +511,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A "shift" => zeros(nphases), "tap" => ones(nphases), "switch" => false, - "br_status" => lowercase(get(eng_obj, "state", "closed")) == "open" ? 0 : 1, + "br_status" => get(eng_obj, "state", CLOSED) == OPEN ? 0 : 1, ) merge!(branch_obj, _branch_obj) @@ -723,7 +723,7 @@ function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::D math_obj["gs"] = fill(0.0, size(eng_obj["bs"])...) if kron_reduced - if eng_obj["configuration"] == "wye" + if eng_obj["configuration"] == WYE _pad_properties!(math_obj, ["gs", "bs"], connections[1:end-1], kr_phases) else _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) @@ -763,7 +763,7 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["qd"] = eng_obj["qd_nom"] if kron_reduced - if math_obj["configuration"]=="wye" + if math_obj["configuration"]==WYE @assert(connections[end]==kr_neutral) _pad_properties!(math_obj, ["pd", "qd"], connections[connections.!=kr_neutral], kr_phases) else @@ -819,7 +819,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["configuration"] = eng_obj["configuration"] if kron_reduced - if math_obj["configuration"]=="wye" + if math_obj["configuration"]==WYE @assert(connections[end]==kr_neutral) _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) else @@ -878,7 +878,7 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An _add_gen_cost_model!(math_obj, eng_obj) if kron_reduced - if math_obj["configuration"]=="wye" + if math_obj["configuration"]==WYE @assert(connections[end]==kr_neutral) _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) else @@ -972,7 +972,7 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: math_obj["gen_status"] = eng_obj["status"] math_obj["pg"] = fill(0.0, nconductors) math_obj["qg"] = fill(0.0, nconductors) - math_obj["configuration"] = "wye" + math_obj["configuration"] = WYE math_obj["source_id"] = "_virtual_gen.$(eng_obj["source_id"])" _add_gen_cost_model!(math_obj, eng_obj) diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 5986daa90..72cfabbd3 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -97,7 +97,7 @@ function _calc_vbase(data_model::Dict{String,<:Any}, vbase_sources::Dict{String, push!(edges,i) f_zone = bus_to_zone[string(transformer["f_bus"])] t_zone = bus_to_zone[string(transformer["t_bus"])] - tm_nom = transformer["configuration"]=="delta" ? transformer["tm_nom"]/sqrt(3) : transformer["tm_nom"] + tm_nom = transformer["configuration"]==DELTA ? transformer["tm_nom"]/sqrt(3) : transformer["tm_nom"] push!(zone_edges[f_zone], (i, t_zone, 1/tm_nom)) push!(zone_edges[t_zone], (i, f_zone, tm_nom)) end diff --git a/src/form/bf_mx.jl b/src/form/bf_mx.jl index 4d477cb9f..c15fd80d3 100644 --- a/src/form/bf_mx.jl +++ b/src/form/bf_mx.jl @@ -280,8 +280,8 @@ constant power or constant impedance. In all other cases (e.g. when a cone is used to constrain the power), variables need to be created. """ function variable_mc_load_setpoint(pm::AbstractUBFModels; nw=pm.cnw) - load_wye_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="wye"] - load_del_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="delta"] + load_wye_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]==WYE] + load_del_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]==DELTA] load_cone_ids = [id for (id, load) in ref(pm, nw, :load) if _check_load_needs_cone(load)] # create dictionaries var(pm, nw)[:pd] = Dict() @@ -311,8 +311,8 @@ all other load model variables are then linear transformations of these (linear Expressions). """ function variable_mc_load_setpoint(pm::SDPUBFKCLMXModel; nw=pm.cnw) - load_wye_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="wye"] - load_del_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]=="delta"] + load_wye_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]==WYE] + load_del_ids = [id for (id, load) in ref(pm, nw, :load) if load["configuration"]==DELTA] load_cone_ids = [id for (id, load) in ref(pm, nw, :load) if _check_load_needs_cone(load)] # create dictionaries var(pm, nw)[:Pd] = Dict{Int, Any}() @@ -385,7 +385,7 @@ function variable_mc_load_power_bus(pm::SDPUBFKCLMXModel, load_ids::Array{Int,1} bound = Dict{eltype(load_ids), Array{Real,2}}() for id in load_ids load = ref(pm, nw, :load, id) - @assert(load["configuration"]=="wye") + @assert(load["configuration"]==WYE) bus = ref(pm, nw, :bus, load["load_bus"]) cmax = _calc_load_current_max(load, bus) bound[id] = bus["vmax"]*cmax' @@ -579,11 +579,11 @@ function constraint_mc_load_setpoint(pm::AbstractUBFModels, load_id::Int; nw::In pmin, pmax, qmin, qmax = _calc_load_pq_bounds(load, bus) # take care of connections - if load["configuration"]=="wye" - if load["model"]=="constant_power" + if load["configuration"]==WYE + if load["model"]==POWER var(pm, nw, :pl)[load_id] = pd0 var(pm, nw, :ql)[load_id] = qd0 - elseif load["model"]=="constant_impedance" + elseif load["model"]==IMPEDANCE w = var(pm, nw, :w)[bus_id] var(pm, nw, :pl)[load_id] = a.*w var(pm, nw, :ql)[load_id] = b.*w @@ -599,7 +599,7 @@ function constraint_mc_load_setpoint(pm::AbstractUBFModels, load_id::Int; nw::In # :pd is identical to :pl now var(pm, nw, :pd)[load_id] = var(pm, nw, :pl)[load_id] var(pm, nw, :qd)[load_id] = var(pm, nw, :ql)[load_id] - elseif load["configuration"]=="delta" + elseif load["configuration"]==DELTA # link Wy, CCd and X Wr = var(pm, nw, :Wr, bus_id) Wi = var(pm, nw, :Wi, bus_id) @@ -622,12 +622,12 @@ function constraint_mc_load_setpoint(pm::AbstractUBFModels, load_id::Int; nw::In # |Vd|^2 is a linear transformation of Wr wd = LinearAlgebra.diag(Td*Wr*Td') - if load["model"]=="constant_power" + if load["model"]==POWER for c in 1:ncnds JuMP.@constraint(pm.model, pl[c]==pd0[c]) JuMP.@constraint(pm.model, ql[c]==qd0[c]) end - elseif load["model"]=="constant_impedance" + elseif load["model"]==IMPEDANCE for c in 1:ncnds JuMP.@constraint(pm.model, pl[c]==a[c]*wd[c]) JuMP.@constraint(pm.model, ql[c]==b[c]*wd[c]) @@ -668,11 +668,11 @@ function constraint_mc_load_setpoint(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int CCdr = var(pm, nw, :CCdr, load_id) CCdi = var(pm, nw, :CCdi, load_id) - if load["configuration"]=="wye" - if load["model"]=="constant_power" + if load["configuration"]==WYE + if load["model"]==POWER var(pm, nw, :pl)[load_id] = pd0 var(pm, nw, :ql)[load_id] = qd0 - elseif load["model"]=="constant_impedance" + elseif load["model"]==IMPEDANCE w = var(pm, nw, :w, bus_id) # for c in 1:ncnds var(pm, nw, :pl)[load_id] = a.*w @@ -695,7 +695,7 @@ function constraint_mc_load_setpoint(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int Qd[c,c] = var(pm, nw, :ql)[load_id][c] end - elseif load["configuration"]=="delta" + elseif load["configuration"]==DELTA # link Wy, CCd and X Xdr = var(pm, nw, :Xdr, load_id) Xdi = var(pm, nw, :Xdi, load_id) @@ -714,12 +714,12 @@ function constraint_mc_load_setpoint(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int # |Vd|^2 is a linear transformation of Wr wd = LinearAlgebra.diag(Td*Wr*Td') - if load["model"]=="constant_power" + if load["model"]==POWER for c in 1:ncnds JuMP.@constraint(pm.model, pl[c]==pd0[c]) JuMP.@constraint(pm.model, ql[c]==qd0[c]) end - elseif load["model"]=="constant_impedance" + elseif load["model"]==IMPEDANCE for c in 1:ncnds JuMP.@constraint(pm.model, pl[c]==a[c]*wd[c]) JuMP.@constraint(pm.model, ql[c]==b[c]*wd[c]) diff --git a/src/io/common.jl b/src/io/common.jl index 9d9289c75..1534754a6 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -3,7 +3,7 @@ Parses the IOStream of a file into a PowerModelsDistribution data structure. """ -function parse_file(io::IO, filetype::AbstractString="json"; data_model::DataModelType=ENGINEERING, import_all::Bool=false, bank_transformers::Bool=true, transformations::Vector{<:Function}=Vector{Function}([]))::Dict{String,Any} +function parse_file(io::IO, filetype::AbstractString="json"; data_model::DataModel=ENGINEERING, import_all::Bool=false, bank_transformers::Bool=true, transformations::Vector{<:Function}=Vector{Function}([]))::Dict{String,Any} if filetype == "dss" data_eng = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index 3f6330750..db9132858 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -284,7 +284,7 @@ function _create_load(name::String=""; kwargs...)::Dict{String,Any} "daily" => get(kwargs, :daily, Vector{Float64}([1.0, 1.0])), "duty" => get(kwargs, :duty, ""), "growth" => get(kwargs, :growth, ""), - "conn" => get(kwargs, :conn, "wye"), + "conn" => get(kwargs, :conn, WYE), "kvar" => kvar, "rneut" => get(kwargs, :rneut, -1.0), "xneut" => get(kwargs, :xneut, 0.0), @@ -329,7 +329,7 @@ different properties. function _create_generator(name::String=""; kwargs...)::Dict{String,Any} bus1 = get(kwargs, :bus1, "") - conn = get(kwargs, :conn, "wye") + conn = get(kwargs, :conn, WYE) kw = get(kwargs, :kw, 100.0) kva = get(kwargs, :kva, kw * 1.2) @@ -407,7 +407,7 @@ function _create_capacitor(name::String=""; kwargs...)::Dict{String,Any} "phases" => phases, "kvar" => get(kwargs, :kvar, 1200.0), "kv" => get(kwargs, :kv, 12.47), - "conn" => get(kwargs, :conn, "wye"), + "conn" => get(kwargs, :conn, WYE), "cmatrix" => get(kwargs, :cmatrix, zeros(phases, phases)), "cuf" => get(kwargs, :cuf, zeros(phases)), "r" => get(kwargs, :r, zeros(phases)), @@ -440,7 +440,7 @@ function _create_reactor(name::String=""; kwargs...)::Dict{String,Any} phases = get(kwargs, :phases, 3) kvar = get(kwargs, :kvar, 1200.0) kv = get(kwargs, :kv, 12.47) - conn = get(kwargs, :conn, "wye") + conn = get(kwargs, :conn, WYE) parallel = get(kwargs, :parallel, false) normamps = get(kwargs, :normamps, 400.0) @@ -463,7 +463,7 @@ function _create_reactor(name::String=""; kwargs...)::Dict{String,Any} if (haskey(kwargs, :kv) && haskey(kwargs, :kvar)) || haskey(kwargs, :x) || haskey(kwargs, :lmh) || haskey(kwargs, :z) if haskey(kwargs, :kvar) && haskey(kwargs, :kv) kvarperphase = kvar / phases - if conn == "delta" + if conn == DELTA phasekv = kv else if phases == 2 || phases == 3 @@ -808,7 +808,7 @@ function _create_transformer(name::String=""; kwargs...) temp = Dict{String,Any}("buss" => get(kwargs, :buses, fill("", windings)), "taps" => get(kwargs, :taps, fill(1.0, windings)), - "conns" => get(kwargs, :conns, fill("wye", windings)), + "conns" => get(kwargs, :conns, fill(WYE, windings)), "kvs" => get(kwargs, :kvs, fill(12.47, windings)), "kvas" => get(kwargs, :kvas, fill(10.0, windings)), "%rs" => prcnt_rs, @@ -928,7 +928,7 @@ function _create_xfmrcode(name::String=""; kwargs...) temp = Dict{String,Any}( "taps" => get(kwargs, :taps, fill(1.0, windings)), - "conns" => get(kwargs, :conns, fill("wye", windings)), + "conns" => get(kwargs, :conns, fill(WYE, windings)), "kvs" => get(kwargs, :kvs, fill(12.47, windings)), "kvas" => get(kwargs, :kvas, fill(10.0, windings)), "%rs" => prcnt_rs, @@ -1081,7 +1081,7 @@ function _create_pvsystem(name::String=""; kwargs...) "irradiance" => get(kwargs, :irradiance, 0), "pmpp" => get(kwargs, :pmpp, 0), "temperature" => get(kwargs, :temperature, 0), - "conn" => get(kwargs, :conn, "wye"), + "conn" => get(kwargs, :conn, WYE), "kvar" => kvar, "kva" => kva, "%cutin" => get(kwargs, :cutin, 0), #TODO not sure what to do with this @@ -1129,7 +1129,7 @@ function _create_storage(name::String=""; kwargs...) "bus1" => get(kwargs, :bus1, ""), "chargetrigger" => get(kwargs, :chargetrigger, 0.0), "class" => get(kwargs, :class, 0), - "conn" => get(kwargs, :conn, "wye"), + "conn" => get(kwargs, :conn, WYE), "daily" => get(kwargs, :daily, [1.0, 1.0]), "debugtrace" => get(kwargs, :debugtrace, false), "dischargetrigger" => get(kwargs, :dischargetrigger, 0.0), diff --git a/src/io/json.jl b/src/io/json.jl index 5eacb6372..2ee0b6604 100644 --- a/src/io/json.jl +++ b/src/io/json.jl @@ -54,6 +54,40 @@ function _parse_mats!(data::Dict{String,<:Any}) end +"parses enums from json" +function _parse_enums!(data::Dict{String,<:Any}) + data["data_model"] = DataModel(get(data, "data_model", 1)) + + for (root_type, root_value) in data + if isa(root_value, Dict) + for (component_id, component) in root_value + if isa(component, Dict) + if haskey(component, "configuration") + if isa(component["configuration"], Vector) + component["configuration"] = Vector{ConnectionConfiguration}([ConnectionConfiguration(el) for el in component["configuration"]]) + else + component["configuration"] = ConnectionConfiguration(component["configuration"]) + end + end + + if root_type == "switch" && haskey(component, "state") + component["state"] = SwitchState(component["state"]) + end + + if root_type == "generator" && haskey(component, "control_mode") + component["generator"] = GeneratorControlMode(component["control_model"]) + end + + if root_type == "load" && haskey(component, "model") + component["model"] = LoadModel(component["model"]) + end + end + end + end + end +end + + "Parses a JSON file into a PMD data structure" function parse_json(file::String; validate::Bool=false) data = open(file) do io @@ -71,8 +105,7 @@ function parse_json(io::IO; validate::Bool=false)::Dict{String,Any} _parse_mats!(data) - # converts data model in into enum, default is MATHEMATICAL == 1 - data["data_model"] = DataModelType(get(data, "data_model", 1)) + _parse_enums!(data) if validate correct_network_data!(data) @@ -110,12 +143,14 @@ function show_json(io::StructuralContext, ::PMDSerialization, f::Matrix{<:Any}) end -function show_json(io::StructuralContext, ::CommonSerialization, f::DataModelType) +"custom handling for enums output to json" +function show_json(io::StructuralContext, ::CommonSerialization, f::Union{DataModel,LoadModel,SwitchState,GeneratorControlMode,ConnectionConfiguration}) return show_json(io, StandardSerialization(), Int(f)) end -"custom handling for data model type enums" -JSON.lower(p::DataModelType) = Int(p) + +"custom handling for enums output to json" +JSON.lower(p::Union{DataModel,LoadModel,SwitchState,GeneratorControlMode,ConnectionConfiguration}) = Int(p) "parses in a serialized matrix" diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 09164be75..0addbff3c 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -102,40 +102,17 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An _apply_like!(dss_obj, data_dss, "load") defaults = _apply_ordered_properties(_create_load(name; _to_kwargs(dss_obj)...), dss_obj) - # parse the model - model = defaults["model"] - - if model == 3 - Memento.warn(_LOGGER, "$name: load model 3 not supported. Treating as model 1.") - model = 1 - elseif model == 4 - Memento.warn(_LOGGER, "$name: load model 4 not supported. Treating as model 1.") - model = 1 - elseif model == 6 - Memento.warn(_LOGGER, "$name: load model 6 identical to model 1 in current feature set. Treating as model 1.") - model = 1 - elseif model == 7 - Memento.warn(_LOGGER, "$name: load model 7 not supported. Treating as model 1.") - model = 1 - elseif model == 8 - Memento.warn(_LOGGER, "$name: load model 8 not supported. Treating as model 1.") - model = 1 - end - - model_int2str = Dict(1=>"constant_power", 2=>"constant_impedance", 5=>"constant_current") - model = model_int2str[model] - nphases = defaults["phases"] conf = defaults["conn"] - if conf=="delta" + if conf==DELTA @assert(nphases in [1, 3], "$name: only 1 and 3-phase delta loads are supported!") end # connections bus = _parse_busname(defaults["bus1"])[1] - connections_default = conf=="wye" ? [collect(1:nphases)..., 0] : nphases==1 ? [1,2] : [1,2,3] - connections = _get_conductors_ordered(defaults["bus1"], default=connections_default, pad_ground=(conf=="wye")) + connections_default = conf==WYE ? [collect(1:nphases)..., 0] : nphases==1 ? [1,2] : [1,2,3] + connections = _get_conductors_ordered(defaults["bus1"], default=connections_default, pad_ground=(conf==WYE)) @assert(length(unique(connections))==length(connections), "$name: connections cannot be made to a terminal more than once.") @@ -144,20 +121,22 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj = Dict{String,Any}( "name" => name, "bus" => bus, - "model" => model, + "model" => defaults["model"], "configuration" => conf, "connections" => connections, "source_id" => "load.$name", "status" => convert(Int, defaults["enabled"]) ) + _parse_dss_load_model!(eng_obj, name) + # if the ground is used directly, register load if 0 in connections _register_awaiting_ground!(data_eng["bus"][bus], connections) end kv = defaults["kv"] - if conf=="wye" && nphases in [2, 3] + if conf==WYE && nphases in [2, 3] kv = kv/sqrt(3) end @@ -189,7 +168,7 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String conn = defaults["conn"] f_terminals = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) - if conn=="wye" + if conn==WYE t_terminals = _get_conductors_ordered(defaults["bus2"], default=fill(0,nphases)) else # if delta connected, ignore bus2 and generate t_terminals such that @@ -201,7 +180,6 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String "configuration" => conn, "status" => convert(Int, defaults["enabled"]), "source_id" => "capacitor.$name", - "phases" => nphases, ) if import_all @@ -269,7 +247,7 @@ function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< "source_id" => "reactor.$name", ) - connections_default = eng_obj["configuration"] == "wye" ? [collect(1:nphases)..., 0] : collect(1:nphases) + connections_default = eng_obj["configuration"] == WYE ? [collect(1:nphases)..., 0] : collect(1:nphases) eng_obj["connections"] = _get_conductors_ordered(defaults["bus1"], default=connections_default, check_length=false) # if the ground is used directly, register @@ -350,11 +328,13 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String "qg_lb" => fill(defaults["minkvar"] / nphases, nphases), "qg_ub" => fill(defaults["maxkvar"] / nphases, nphases), "control_mode" => defaults["model"], - "configuration" => "wye", + "configuration" => WYE, "status" => convert(Int, defaults["enabled"]), "source_id" => "generator.$(name)" ) + _parse_dss_generator_model!(eng_obj, name) + # if the ground is used directly, register load if 0 in eng_obj["connections"] _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) @@ -523,7 +503,7 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, "tm_step" => Vector{Vector{Float64}}(fill(fill(1/32, nphases), nrw)), "vnom" => Vector{Float64}(defaults["kvs"]), "snom" => Vector{Float64}(defaults["kvas"]), - "configuration" => Vector{String}(defaults["conns"]), + "configuration" => Vector{ConnectionConfiguration}(defaults["conns"]), "rs" => Vector{Float64}(defaults["%rs"] ./ 100), "noloadloss" => defaults["%noloadloss"] / 100, "imag" => defaults["%imag"] / 100, @@ -577,7 +557,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri nrw = _shared["windings"] # two-phase delta transformers have single coil - if all(confs.=="delta") && nphases==2 + if all(confs.==DELTA) && nphases==2 ncoils = 1 else ncoils = nphases @@ -674,7 +654,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end # test if this transformer conforms with limitations - if nphases<3 && "delta" in confs + if nphases<3 && DELTA in confs # Memento.error(_LOGGER, "Transformers with delta windings should have at least 3 phases to be well-defined: $name.") end if nrw>3 @@ -687,20 +667,20 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri eng_obj["bus"][w] = _parse_busname(defaults["buses"][w])[1] conf = confs[w] - terminals_default = conf=="wye" ? [1:nphases..., 0] : collect(1:nphases) + terminals_default = conf==WYE ? [1:nphases..., 0] : collect(1:nphases) # append ground if connections one too short - eng_obj["connections"][w] = _get_conductors_ordered(defaults["buses"][w], default=terminals_default, pad_ground=(conf=="wye")) + eng_obj["connections"][w] = _get_conductors_ordered(defaults["buses"][w], default=terminals_default, pad_ground=(conf==WYE)) if w>1 prim_conf = confs[1] if leadlag in ["ansi", "lag"] - if prim_conf=="delta" && conf=="wye" + if prim_conf==DELTA && conf==WYE eng_obj["polarity"][w] = -1 eng_obj["connections"][w] = [_barrel_roll(eng_obj["connections"][w][1:end-1], 1)..., eng_obj["connections"][w][end]] end else # hence defaults["leadlag"] in ["euro", "lead"] - if prim_conf=="wye" && conf=="delta" + if prim_conf==WYE && conf==DELTA eng_obj["polarity"][w] = -1 eng_obj["connections"][w] = _barrel_roll(eng_obj["connections"][w], -1) end @@ -784,7 +764,7 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< eng_obj = Dict{String,Any}( "bus" => _parse_busname(defaults["bus1"])[1], "connections" => _get_conductors_ordered(defaults["bus1"], check_length=false), - "configuration" => "wye", + "configuration" => WYE, "energy" => defaults["kwhstored"], "energy_ub" => defaults["kwrated"], "charge_ub" => defaults["%charge"] / 100.0 * defaults["kwrated"], diff --git a/src/io/utils.jl b/src/io/utils.jl index 7e7c6662f..cc22087c8 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -76,6 +76,25 @@ const _dtype_regex = Dict{Regex, Type}( ) +"dss to pmd load model" +const _dss2pmd_load_model = Dict{Int,LoadModel}( + 1 => POWER, + 2 => IMPEDANCE, + 5 => CURRENT, + 4 => EXPONENTIAL, # TODO add official support for exponential load model + 8 => ZIP, # TODO add official support for ZIP load model +) + + +"dss to pmd generator model" +const _dss2pmd_gen_model = Dict{Int,GeneratorControlMode}( + 1 => PQ, + 2 => Z, + 3 => PV, + 7 => INVERTER, +) + + "detects if `expr` is Reverse Polish Notation expression" function _isa_rpn(expr::AbstractString)::Bool expr = split(strip(expr, _array_delimiters)) @@ -145,14 +164,14 @@ end "parses connection \"conn\" specification reducing to wye or delta" -function _parse_conn(conn::String)::String +function _parse_conn(conn::AbstractString)::ConnectionConfiguration if conn in ["wye", "y", "ln"] - return "wye" + return WYE elseif conn in ["delta", "ll"] - return "delta" + return DELTA else - Memento.warn(_LOGGER, "Unsupported connection $conn, defaulting to \"wye\"") - return "wye" + Memento.warn(_LOGGER, "Unsupported connection $conn, defaulting to WYE") + return WYE end end @@ -266,7 +285,13 @@ function _parse_array(dtype::Type, data::AbstractString)::Vector{dtype} elements = [strip(el) for el in elements if strip(el) != ""] end - if dtype == String || dtype == AbstractString || dtype == Char + if all(_isa_conn(el) for el in elements) + array = Vector{ConnectionConfiguration}([]) + for el in elements + a = _parse_conn(el) + push!(array, a) + end + elseif dtype == String || dtype == AbstractString || dtype == Char array = Vector{String}([]) for el in elements push!(array, el) @@ -315,7 +340,7 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) nrw = length(btrans["bus"]) # only attempt to bank wye-connected transformers - if !all(all(tr["configuration"].=="wye") for tr in trs) + if !all(all(tr["configuration"].==WYE) for tr in trs) Memento.warn(_LOGGER, "Not all configurations 'wye' on transformers identified by bank='$bank', aborting attempt to bank") continue end @@ -437,12 +462,12 @@ function _find_neutrals(data_eng::Dict{String,<:Any}) trans_neutrals = [] for (_, tr) in data_eng["transformer"] for w in 1:length(tr["connections"]) - if tr["configuration"][w] == "wye" + if tr["configuration"][w] == WYE push!(trans_neutrals, (tr["bus"][w], tr["connections"][w][end])) end end end - load_neutrals = [(eng_obj["bus"],eng_obj["connections"][end]) for (_,eng_obj) in get(data_eng, "load", Dict{String,Any}()) if eng_obj["configuration"]=="wye"] + load_neutrals = [(eng_obj["bus"],eng_obj["connections"][end]) for (_,eng_obj) in get(data_eng, "load", Dict{String,Any}()) if eng_obj["configuration"]==WYE] neutrals = Set(vcat(bus_neutrals, trans_neutrals, load_neutrals)) neutrals = Set([(bus,t) for (bus,t) in neutrals if t!=0]) stack = deepcopy(neutrals) @@ -824,3 +849,32 @@ function _guess_dtype(value::AbstractString)::Type return String end end + + +"converts dss load model to supported PMD LoadModel enum" +function _parse_dss_load_model!(eng_obj::Dict{String,<:Any}, id::Any) + model = eng_obj["model"] + + if model in [3, 4, 7, 8] + Memento.warn(_LOGGER, "$id: dss load model $model not supported. Treating as constant POWER model") + model = 1 + elseif model == 6 + Memento.warn(_LOGGER, "$id: dss load model $model identical to model 1 in current feature set. Treating as constant POWER model") + model = 1 + end + + eng_obj["model"] = _dss2pmd_load_model[model] +end + + +"converts dss generator model into PMD GeneratorControlMode enum" +function _parse_dss_generator_model!(eng_obj::Dict{String,<:Any}, id::Any) + model = eng_obj["control_mode"] + + if model in [2, 4, 5, 6, 7] + Memento.warn(_LOGGER, "$id: dss generator model $model not supported. Treating as constant PQ model") + model = 1 + end + + eng_obj["control_mode"] = _dss2pmd_gen_model[model] +end diff --git a/test/delta_gens.jl b/test/delta_gens.jl index dc93ff85e..d22a84552 100644 --- a/test/delta_gens.jl +++ b/test/delta_gens.jl @@ -8,7 +8,7 @@ # convert to constant power loads for (_, load) in pmd_1["load"] - load["model"] = "constant_power" + load["model"] = POWER end # create data model with equivalent generators diff --git a/test/opendss.jl b/test/opendss.jl index 6b46f565f..cfc6b94aa 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -69,9 +69,9 @@ @testset "opendss parse load model warnings" begin for model in [3, 4, 7, 8] dss = parse_dss("../test/data/opendss/loadparser_warn_model.dss") - dss["load"] = Dict{String,Any}((n,l) for (n,l) in dss["load"] if l["name"]=="d1phm$model") + dss["load"] = Dict{String,Any}((n,l) for (n,l) in dss["load"] if n=="d1phm$model") Memento.setlevel!(TESTLOG, "info") - @test_warn(TESTLOG, ": load model $model not supported. Treating as model 1.", parse_opendss(dss)) + @test_warn(TESTLOG, ": dss load model $model not supported. Treating as constant POWER model", parse_opendss(dss)) Memento.setlevel!(TESTLOG, "error") end end @@ -206,7 +206,7 @@ @test transformer["%loadloss"] == 0.01 @test transformer["xhl"] == 0.02 @test transformer["kv_2"] == 12.47 - @test transformer["conn_2"] == "wye" + @test transformer["conn_2"] == WYE @test transformer["tap_3"] == 0.9 @test transformer["wdg_3"] == 3 From f707f3d89d19eb0830f7c847b843a1d9eb3a9ddf Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 29 Apr 2020 16:18:02 -0600 Subject: [PATCH 174/224] WIP: final data model changes --- Project.toml | 2 - src/core/data.jl | 4 +- src/core/types.jl | 18 +- src/data_model/components.jl | 19 +- src/data_model/eng2math.jl | 380 ++----------------------------- src/data_model/utils.jl | 9 +- src/io/dss_structs.jl | 5 +- src/io/json.jl | 22 +- src/io/opendss.jl | 419 +++++++++++++++++++++-------------- src/io/utils.jl | 60 +++-- test/opendss.jl | 12 - test/opf.jl | 2 +- 12 files changed, 353 insertions(+), 599 deletions(-) diff --git a/Project.toml b/Project.toml index ea5242eb2..8b7ec0e83 100644 --- a/Project.toml +++ b/Project.toml @@ -5,7 +5,6 @@ repo = "https://github.com/lanl-ansi/PowerModelsDistribution.jl.git" version = "0.9.0" [deps] -Dierckx = "39dd38d3-220a-591b-8e3c-4c3a8c710a94" InfrastructureModels = "2030c09a-7f63-5d83-885d-db604e0e9cc0" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" @@ -17,7 +16,6 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] Cbc = ">= 0.4" -Dierckx = "~0.4" InfrastructureModels = "~0.5" Ipopt = ">= 0.4" JSON = "~0.18, ~0.19, ~0.20, ~0.21" diff --git a/src/core/data.jl b/src/core/data.jl index db0166d3a..f6fa28168 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -58,7 +58,7 @@ function count_nodes(data::Dict{String,<:Any})::Int if isa(object, Dict) if haskey(object, "buses") for busname in values(object["buses"]) - name, nodes = _parse_busname(busname) + name, nodes = _parse_bus_id(busname) if !haskey(all_nodes, name) all_nodes[name] = Set([]) @@ -73,7 +73,7 @@ function count_nodes(data::Dict{String,<:Any})::Int else for (prop, val) in object if startswith(prop, "bus") && prop != "buses" - name, nodes = _parse_busname(val) + name, nodes = _parse_bus_id(val) if !haskey(all_nodes, name) all_nodes[name] = Set([]) diff --git a/src/core/types.jl b/src/core/types.jl index 1d34010b0..8bcf2bd82 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -4,15 +4,25 @@ "Load Models" @enum LoadModel POWER CURRENT IMPEDANCE EXPONENTIAL ZIP +"Shunt Models" +@enum ShuntModel GENERIC CAPACITOR REACTOR + "Switch States" -@enum SwitchState OPEN CLOSED FIXED_OPEN FIXED_CLOSED +@enum SwitchState OPEN CLOSED -"Generator Control Modes" -@enum GeneratorControlMode PQ PV Z INVERTER # TODO needs better names +"Generator, Solar, Storage, Wind Control Modes" +@enum ControlMode DROOP ISOCHRONOUS "Configurations" -@enum ConnectionConfiguration WYE DELTA +@enum ConnConfig WYE DELTA + +"Dispatchable" +@enum Dispatchable NO YES + +"Status" +@enum Status DISABLED ENABLED +PowerModelsDistributionEnums = Union{DataModel,LoadModel,ShuntModel,SwitchState,ControlMode,ConnConfig,Dispatchable,Status} "Base Abstract NLP Unbalanced Branch Flow Model" abstract type AbstractNLPUBFModel <: _PM.AbstractBFQPModel end diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 67053035e..83fb5f385 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -141,7 +141,7 @@ function create_line(; kwargs...) line = Dict{String,Any}( "f_bus" => kwargs[:f_bus], "t_bus" => kwargs[:t_bus], - "status" => get(kwargs, :status, 1), + "status" => get(kwargs, :status, ENABLED), "f_connections" => get(kwargs, :f_connections, collect(1:4)), "t_connections" => get(kwargs, :t_connections, collect(1:4)), "angmin" => get(kwargs, :angmin, fill(-60/180*pi, N)), @@ -162,14 +162,13 @@ end "creates a bus object with some defaults" -function create_bus(; kwargs...) +function create_bus(; status::Status=ENABLED, terminals::Union{Vector{Int},Vector{String}}=collect(1:4), grounded::Union{Vector{Int},Vector{String}}=Vector{Int}([]), rg::Vector{<:Real}=Vector{Float64}([]), xg::Vector{<:Real}=Vector{Float64}([]), kwargs...) kwargs = Dict{Symbol,Any}(kwargs) bus = Dict{String,Any}( - "status" => get(kwargs, :status, 1), + "status" => get(kwargs, :status, ENABLED), "terminals" => get(kwargs, :terminals, collect(1:4)), "grounded" => get(kwargs, :grounded, []), - "bus_type" => get(kwargs, :bus_type, 1), "rg" => get(kwargs, :rg, Array{Float64, 1}()), "xg" => get(kwargs, :xg, Array{Float64, 1}()), ) @@ -185,7 +184,7 @@ function create_load(; kwargs...) kwargs = Dict{Symbol,Any}(kwargs) load = Dict{String,Any}( - "status" => get(kwargs, :status, 1), + "status" => get(kwargs, :status, ENABLED), "configuration" => get(kwargs, :configuration, WYE), "model" => get(kwargs, :model, POWER), "connections" => get(kwargs, :connections, get(kwargs, :configuration, WYE)==WYE ? [1, 2, 3, 4] : [1, 2, 3]), @@ -211,7 +210,7 @@ function create_generator(; kwargs...) kwargs = Dict{Symbol,Any}(kwargs) generator = Dict{String,Any}( - "status" => get(kwargs, :status, 1), + "status" => get(kwargs, :status, ENABLED), "configuration" => get(kwargs, :configuration, WYE), "cost" => get(kwargs, :cost, [1.0, 0.0]*1E-3), ) @@ -232,7 +231,7 @@ function create_transformer(; kwargs...) n_windings = length(kwargs[:bus]) transformer = Dict{String,Any}( - "status" => get(kwargs, :status, 1), + "status" => get(kwargs, :status, ENABLED), "configuration" => get(kwargs, :configuration, fill(WYE, n_windings)), "polarity" => get(kwargs, :polarity, fill(true, n_windings)), "rs" => get(kwargs, :rs, zeros(n_windings)), @@ -256,7 +255,7 @@ end "creates a shunt capacitor object with some defaults" function create_shunt_capacitor(; kwargs...) shunt_capacitor = Dict{String,Any}( - "status" => get(kwargs, :status, 1), + "status" => get(kwargs, :status, ENABLED), "configuration" => get(kwargs, :configuration, WYE), "connections" => get(kwargs, :connections, collect(1:4)), "qd_ref" => get(kwargs, :qd_ref, fill(0.0, 3)), @@ -275,7 +274,7 @@ function create_shunt(; kwargs...) N = length(get(kwargs, :connections, collect(1:4))) shunt = Dict{String,Any}( - "status" => get(kwargs, :status, 1), + "status" => get(kwargs, :status, ENABLED), "g_sh" => get(kwargs, :g_sh, fill(0.0, N, N)), "b_sh" => get(kwargs, :b_sh, fill(0.0, N, N)), ) @@ -291,7 +290,7 @@ function create_voltage_source(; kwargs...) kwargs = Dict{Symbol,Any}(kwargs) voltage_source = Dict{String,Any}( - "status" => get(kwargs, :status, 1), + "status" => get(kwargs, :status, ENABLED), "connections" => get(kwargs, :connections, collect(1:3)), ) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index e420812ef..4ef125998 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -7,20 +7,17 @@ const _1to1_maps = Dict{String,Vector{String}}( "transformer" => ["f_connections", "t_connections", "source_id", "dss"], "switch" => ["status", "f_connections", "t_connections", "source_id", "dss"], "line_reactor" => ["f_connections", "t_connections", "source_id", "dss"], - "series_capacitor" => ["f_connections", "t_connections", "source_id", "dss"], - "shunt" => ["status", "gs", "bs", "connections", "source_id", "dss"], - "shunt_capacitor" => ["status", "bs", "connections", "source_id", "dss"], - "shunt_reactor" => ["status", "connections", "source_id", "dss"], - "load" => ["model", "configuration", "connections", "status", "source_id", "dss"], + "shunt" => ["status", "dispatchable", "gs", "bs", "connections", "source_id", "dss"], + "load" => ["model", "configuration", "connections", "dispatchable", "status", "source_id", "dss"], "generator" => ["pg", "qg", "configuration", "connections", "source_id", "dss"], - "solar" => ["configuration", "connections", "source_id", "dss"], + "solar" => ["pg", "qg", "configuration", "connections", "source_id", "dss"], "storage" => ["status", "energy", "ps", "qs", "connections", "source_id", "dss"], "voltage_source" => ["source_id", "dss"], ) "list of nodal type elements in the engineering model" const _node_elements = Vector{String}([ - "load", "capacitor", "shunt_reactor", "generator", "solar", "storage", "vsource" + "load", "shunt", "generator", "solar", "storage", "voltage_source" ]) "list of edge type elements in the engineering model" @@ -28,119 +25,6 @@ const _edge_elements = Vector{String}([ "line", "switch", "transformer", "line_reactor", "series_capacitor" ]) -"list of time-series supported parameters that map one-to-one" -const _time_series_parameters = Dict{String,Dict{String,Tuple{Function, String}}}( - "switch" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "state" => (_no_conversion, "state"), - "c_rating" => (_no_conversion, "cm_ub"), - "s_rating" => (_no_conversion, "sm_ub"), - "br_r" => (_impedance_conversion, "rs"), - "br_x" => (_impedance_conversion, "xs"), - "g_fr" => (_admittance_conversion, "g_fr"), - "g_to" => (_admittance_conversion, "g_to"), - "b_fr" => (_admittance_conversion, "b_fr"), - "b_to" => (_admittance_conversion, "b_to") - ), - "fuse" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "state" => (_no_conversion, "state"), - "c_rating" => (_no_conversion, "cm_ub"), - "s_rating" => (_no_conversion, "sm_ub"), - "br_r" => (_impedance_conversion, "rs"), - "br_x" => (_impedance_conversion, "xs"), - "g_fr" => (_admittance_conversion, "g_fr"), - "g_to" => (_admittance_conversion, "g_to"), - "b_fr" => (_admittance_conversion, "b_fr"), - "b_to" => (_admittance_conversion, "b_to") - ), - "line" => Dict{String,Tuple{Function, String}}( - "br_status" => (_no_conversion, "status"), - "c_rating" => (_no_conversion, "cm_ub"), - "s_rating" => (_no_conversion, "sm_ub"), - "br_r" => (_impedance_conversion, "rs"), - "br_x" => (_impedance_conversion, "xs"), - "g_fr" => (_admittance_conversion, "g_fr"), - "g_to" => (_admittance_conversion, "g_to"), - "b_fr" => (_admittance_conversion, "b_fr"), - "b_to" => (_admittance_conversion, "b_to") - ), - "transformer" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - # TODO need to figure out how to convert values for time series for decomposed transformers - ), - "bus" => Dict{String,Tuple{Function, String}}( - "bus_type" => (_bus_type_conversion, "status"), - "vmin" => (_no_conversion, "vm_lb"), - "vmax" => (_no_conversion, "vm_ub"), - "vm" => (_no_conversion, "vm"), - "va" => (_no_conversion, "va") - ), - "shunt" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "gs" => (_no_conversion, "gs"), - "bs" => (_no_conversion, "bs"), - ), - "shunt_capacitor" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "gs" => (_no_conversion, "gs"), - "bs" => (_no_conversion, "bs"), - ), - "shunt_reactor" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "gs" => (_no_conversion, "gs"), - "bs" => (_no_conversion, "bs"), - ), - "load" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "pd_ref" => (_no_conversion, "pd_nom"), - "qd_ref" => (_no_conversion, "qd_nom"), - ), - "generator" => Dict{String,Tuple{Function, String}}( - "gen_status" => (_no_conversion, "status"), - "pg" => (_no_conversion, "pg"), - "qg" => (_no_conversion, "qg"), - "vg" => (_vnom_conversion, "vg"), - "pmin" => (_no_conversion, "pg_lb"), - "pmax" => (_no_conversion, "pg_ub"), - "qmin" => (_no_conversion, "qg_lb"), - "qmax" => (_no_conversion, "qg_ub"), - ), - "solar" => Dict{String,Tuple{Function, String}}( - "gen_status" => (_no_conversion, "status"), - "pg" => (_no_conversion, "pg"), - "qg" => (_no_conversion, "qg"), - "vg" => (_vnom_conversion, "vg"), - "pmin" => (_no_conversion, "pg_lb"), - "pmax" => (_no_conversion, "pg_ub"), - "qmin" => (_no_conversion, "qg_lb"), - "qmax" => (_no_conversion, "qg_ub"), - ), - "storage" => Dict{String,Tuple{Function, String}}( - "status" => (_no_conversion, "status"), - "energy" => (_no_conversion, "energy"), - "energy_rating" => (_no_conversion, "energy_ub"), - "charge_rating" => (_no_conversion, "charge_ub"), - "discharge_rating" => (_no_conversion, "discharge_ub"), - "charge_efficiency" => (_no_conversion, "charge_efficiency"), - "discharge_efficiency" => (_no_conversion, "discharge_efficiency"), - "thermal_rating" => (_no_conversion, "cm_ub"), - "qmin" => (_no_conversion, "qs_lb"), - "qmax" => (_no_conversion, "qs_ub"), - "r" => (_no_conversion, "rs"), - "x" => (_no_conversion, "xs"), - "p_loss" => (_no_conversion, "pex"), - "q_loss" => (_no_conversion, "qex"), - "ps" => (_no_conversion, "ps"), - "qs" => (_no_conversion, "qs"), - ), - "voltage_source" => Dict{String,Tuple{Function, String}}( - "gen_status" => (_no_conversion, "status"), - "vm" => (_no_conversion, "vm"), - "va" => (_angle_shift_conversion, "va"), - ), - -) "base function for converting engineering model to mathematical model" function _map_eng2math(data_eng; kron_reduced::Bool=true) @@ -172,15 +56,11 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) _map_eng2math_line!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_switch!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_transformer!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_line_reactor!(data_math, data_eng; kron_reduced=kron_reduced) - # _map_eng2math_series_capacitor(data_math, data_eng; kron_reduced=kron_reduced) # TODO build conversion for series capacitors # convert nodes _map_eng2math_load!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_shunt!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_shunt_capacitor!(data_math, data_eng; kron_reduced=kron_reduced) - _map_eng2math_shunt_reactor!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_generator!(data_math, data_eng; kron_reduced=kron_reduced) _map_eng2math_solar!(data_math, data_eng; kron_reduced=kron_reduced) @@ -266,11 +146,9 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, )) # time series - for (fr, (f, to)) in _time_series_parameters["bus"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj, "bus", "$(math_obj["index"])", fr, to, f) - end + # TODO + for (k, v) in get(eng_obj, "time_series", Dict{String,Any}()) + time_series = data_eng["time_series"][v] end end end @@ -324,7 +202,7 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["switch"] = false - math_obj["br_status"] = eng_obj["status"] + math_obj["br_status"] = Int(eng_obj["status"]) data_math["branch"]["$(math_obj["index"])"] = math_obj @@ -333,15 +211,6 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any "to" => "branch.$(math_obj["index"])", "unmap_function" => "_map_math2eng_line!", )) - - # time series - # TODO - for (fr, to) in zip(["status"], ["status"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) - end - end end end @@ -459,15 +328,6 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A map_to = "switch.$(math_obj["index"])" - # time series - # TODO switch time series - for (fr, to) in zip(["status", "state"], ["status", "state"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "switch", "$(math_obj["index"])", to) - end - end - if any(haskey(eng_obj, k) for k in ["rs", "xs", "linecode"]) # build virtual bus @@ -532,18 +392,6 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A data_math["branch"]["$(branch_obj["index"])"] = branch_obj - # build switch - switch_obj = Dict{String,Any}( - "name" => name, - "source_id" => eng_obj["source_id"], - "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], - "t_bus" => bus_obj["bus_i"], - "status" => eng_obj["status"], - "index" => length(data_math["switch"])+1 - ) - - # data_math["switch"]["$(switch_obj["index"])"] = switch_obj - map_to = ["branch.$(branch_obj["index"])"] # map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] # TODO enable real switches end @@ -559,76 +407,6 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A end -"converts engineering line reactors into mathematical branches" -function _map_eng2math_line_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - # TODO support line reactors natively, currently treated like branches - for (name, eng_obj) in get(data_eng, "line_reactor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) - math_obj["name"] = "_virtual_branch.$(eng_obj["source_id"])" - - nphases = length(eng_obj["f_connections"]) - nconductors = data_math["conductors"] - - math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] - - math_obj["br_r"] = _impedance_conversion(data_eng, eng_obj, "rs") - math_obj["br_x"] = _impedance_conversion(data_eng, eng_obj, "xs") - - math_obj["g_fr"] = _admittance_conversion(data_eng, eng_obj, "g_fr") - math_obj["g_to"] = _admittance_conversion(data_eng, eng_obj, "g_to") - - math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") - math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") - - math_obj["angmin"] = fill(-60.0, nphases) - math_obj["angmax"] = fill( 60.0, nphases) - - math_obj["transformer"] = false - math_obj["shift"] = zeros(nphases) - math_obj["tap"] = ones(nphases) - - f_bus = data_eng["bus"][eng_obj["f_bus"]] - t_bus = data_eng["bus"][eng_obj["t_bus"]] - - if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") - filter = _kron_reduce_branch!(math_obj, - ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], - eng_obj["f_connections"], kr_neutral - ) - _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) - connections = eng_obj["f_connections"][filter] - _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) - else - math_obj["f_connections"] = eng_obj["f_connections"] - math_obj["t_connections"] = eng_obj["t_connections"] - end - - math_obj["switch"] = false - - math_obj["br_status"] = eng_obj["status"] - - data_math["branch"]["$(math_obj["index"])"] = math_obj - - push!(data_math["map"], Dict{String,Any}( - "from" => name, - "to" => "branch.$(math_obj["index"])", - "unmap_function" => "_map_math2eng_line_reactor!", - )) - - # time series - # TODO - for (fr, to) in zip(["status"], ["status"]) - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "branch", "$(math_obj["index"])", to) - end - end - end -end - - "converts engineering generic shunt components into mathematical shunt components" function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) for (name, eng_obj) in get(data_eng, "shunt", Dict{Any,Dict{String,Any}}()) @@ -637,47 +415,7 @@ function _map_eng2math_shunt!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An # TODO change to new capacitor shunt calc logic math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gs"] = eng_obj["gs"] - math_obj["bs"] = eng_obj["bs"] - - if kron_reduced - filter = _kron_reduce_branch!(math_obj, - Vector{String}([]), ["gs", "bs"], - eng_obj["connections"], kr_neutral - ) - connections = eng_obj["connections"][filter] - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) - else - math_obj["connections"] = eng_obj["connections"] - end - - data_math["shunt"]["$(math_obj["index"])"] = math_obj - - push!(data_math["map"], Dict{String,Any}( - "from" => name, - "to" => "shunt.$(math_obj["index"])", - "unmap_function" => "_map_math2eng_capacitor!", - )) - - # time series - for (fr, (f, to)) in _time_series_parameters["shunt"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) - end - end - end -end - - -"converts engineering shunt capacitors into mathematical shunts" -function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt_capacitor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt_capacitor", name, eng_obj, length(data_math["shunt"])+1) - - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - - math_obj["gs"] = zeros(size(eng_obj["bs"])) + math_obj["gs"] = get(eng_obj, "gs", zeros(size(eng_obj["bs"]))) if kron_reduced filter = _kron_reduce_branch!(math_obj, @@ -695,57 +433,8 @@ function _map_eng2math_shunt_capacitor!(data_math::Dict{String,<:Any}, data_eng: push!(data_math["map"], Dict{String,Any}( "from" => name, "to" => "shunt.$(math_obj["index"])", - "unmap_function" => "_map_math2eng_shunt_capacitor!", - )) - - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["shunt_capacitor"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) - end - end - end -end - - -"converts engineering shunt reactors into mathematical shunts" -function _map_eng2math_shunt_reactor!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any,<:Any}; kron_reduced::Bool=true, kr_phases::Vector{Int}=[1, 2, 3], kr_neutral::Int=4) - for (name, eng_obj) in get(data_eng, "shunt_reactor", Dict{Any,Dict{String,Any}}()) - math_obj = _init_math_obj("shunt_reactor", name, eng_obj, length(data_math["shunt"])+1) - - connections = eng_obj["connections"] - nconductors = data_math["conductors"] - - math_obj["shunt_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - - math_obj["gs"] = fill(0.0, size(eng_obj["bs"])...) - - if kron_reduced - if eng_obj["configuration"] == WYE - _pad_properties!(math_obj, ["gs", "bs"], connections[1:end-1], kr_phases) - else - _pad_properties!(math_obj, ["gs", "bs"], connections, kr_phases) - end - end - - data_math["shunt"]["$(math_obj["index"])"] = math_obj - - push!(data_math["map"], Dict{String,Any}( - "from" => name, - "to" => "shunt.$(math_obj["index"])", - "unmap_function" => "_map_math2eng_shunt_reactor!", + "unmap_function" => "_map_math2eng_shunt!", )) - - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["shunt_reactor"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "shunt", "$(math_obj["index"])", to) - end - end end end @@ -782,14 +471,6 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any "to" => "load.$(math_obj["index"])", "unmap_function" => "_map_math2eng_load!", )) - - # time series - for (fr, (f, to)) in _time_series_parameters["load"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "load", "$(math_obj["index"])", to) - end - end end end @@ -841,15 +522,6 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ "to" => "gen.$(math_obj["index"])", "unmap_function" => "_map_math2eng_generator!", )) - - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["generator"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "gen", "$(math_obj["index"])", to) - end - end end end @@ -865,15 +537,11 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["gen_status"] = eng_obj["status"] - math_obj["pg"] = eng_obj["kva"] - math_obj["qg"] = eng_obj["kvar"] - math_obj["vg"] = eng_obj["kv"] - - math_obj["pmin"] = get(eng_obj, "minkva", zeros(size(eng_obj["kva"]))) - math_obj["pmax"] = get(eng_obj, "maxkva", eng_obj["kva"]) - - math_obj["qmin"] = get(eng_obj, "minkvar", -eng_obj["kvar"]) - math_obj["qmax"] = get(eng_obj, "maxkvar", eng_obj["kvar"]) + for (fr_k, to_k) in [("vg", "vg"), ("pg_lb", "pmin"), ("pg_ub", "pmax"), ("qg_lb", "qmin"), ("qg_ub", "qmax")] + if haskey(eng_obj, fr_k) + math_obj[to_k] = eng_obj[fr_k] + end + end _add_gen_cost_model!(math_obj, eng_obj) @@ -895,15 +563,6 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An "to" => "gen.$(math_obj["index"])", "unmap_function" => "_map_math2eng_solar!", )) - - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["solar"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj, "gen", "$(math_obj["index"])", fr, to, f) - end - end end end @@ -947,15 +606,6 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: "to" => "storage.$(math_obj["index"])", "unmap_function" => "_map_math2eng_storage!", )) - - # time series - # TODO - for (fr, (f, to)) in _time_series_parameters["storage"] - if haskey(eng_obj, "$(fr)_time_series") - time_series = data_eng["time_series"][eng_obj["$(fr)_time_series"]] - _parse_time_series_parameter!(data_math, time_series, eng_obj[fr], "storage", "$(math_obj["index"])", to) - end - end end end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index a5a3ee94d..d3fc27e4f 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -1,12 +1,17 @@ "initializes the base math object of any type, and copies any one-to-one mappings" function _init_math_obj(obj_type::String, eng_id::Any, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} math_obj = Dict{String,Any}( - "name" => "$eng_id" + "name" => "$eng_id", + "status" => Int(eng_obj["status"]) ) for key in _1to1_maps[obj_type] if haskey(eng_obj, key) - math_obj[key] = eng_obj[key] + if key in ["status", "dispatchable"] + math_obj[key] = Int(eng_obj[key]) + else + math_obj[key] = eng_obj[key] + end end end diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index db9132858..c562a68fa 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -269,7 +269,6 @@ function _create_load(name::String=""; kwargs...)::Dict{String,Any} kva = abs(kw) + kvar^2 end - # TODO: yearly, daily, duty, growth, model # TODO: ZIPV (7 coefficient array, depends on model keyword) Dict{String,Any}( @@ -280,8 +279,8 @@ function _create_load(name::String=""; kwargs...)::Dict{String,Any} "kw" => kw, "pf" => pf, "model" => get(kwargs, :model, 1), - "yearly" => get(kwargs, :yearly, get(kwargs, :daily, Vector{Float64}([1.0, 1.0]))), - "daily" => get(kwargs, :daily, Vector{Float64}([1.0, 1.0])), + "yearly" => get(kwargs, :yearly, ""), + "daily" => get(kwargs, :daily, ""), "duty" => get(kwargs, :duty, ""), "growth" => get(kwargs, :growth, ""), "conn" => get(kwargs, :conn, WYE), diff --git a/src/io/json.jl b/src/io/json.jl index 2ee0b6604..73f1b12c0 100644 --- a/src/io/json.jl +++ b/src/io/json.jl @@ -62,11 +62,19 @@ function _parse_enums!(data::Dict{String,<:Any}) if isa(root_value, Dict) for (component_id, component) in root_value if isa(component, Dict) + if haskey(component, "status") + component["status"] = Status(component["status"]) + end + + if haskey(component, "dispatchable") + component["dispatchable"] = Dispatchable(component["dispatchable"]) + end + if haskey(component, "configuration") if isa(component["configuration"], Vector) - component["configuration"] = Vector{ConnectionConfiguration}([ConnectionConfiguration(el) for el in component["configuration"]]) + component["configuration"] = Vector{ConnConfig}([ConnConfig(el) for el in component["configuration"]]) else - component["configuration"] = ConnectionConfiguration(component["configuration"]) + component["configuration"] = ConnConfig(component["configuration"]) end end @@ -75,12 +83,16 @@ function _parse_enums!(data::Dict{String,<:Any}) end if root_type == "generator" && haskey(component, "control_mode") - component["generator"] = GeneratorControlMode(component["control_model"]) + component["generator"] = ControlMode(component["control_model"]) end if root_type == "load" && haskey(component, "model") component["model"] = LoadModel(component["model"]) end + + if root_type == "shunt" && haskey(component, "model") + component["model"] = ShuntModel(component["model"]) + end end end end @@ -144,13 +156,13 @@ end "custom handling for enums output to json" -function show_json(io::StructuralContext, ::CommonSerialization, f::Union{DataModel,LoadModel,SwitchState,GeneratorControlMode,ConnectionConfiguration}) +function show_json(io::StructuralContext, ::CommonSerialization, f::PowerModelsDistributionEnums) return show_json(io, StandardSerialization(), Int(f)) end "custom handling for enums output to json" -JSON.lower(p::Union{DataModel,LoadModel,SwitchState,GeneratorControlMode,ConnectionConfiguration}) = Int(p) +JSON.lower(p::PowerModelsDistributionEnums) = Int(p) "parses in a serialized matrix" diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 0addbff3c..b774bcf82 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -4,9 +4,9 @@ import LinearAlgebra: diagm "Parses buscoords [lon,lat] (if present) into their respective buses" function _dss2eng_buscoords!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}) - for (name, coords) in get(data_dss, "buscoords", Dict{String,Any}()) - if haskey(data_eng["bus"], name) - bus = data_eng["bus"][name] + for (id, coords) in get(data_dss, "buscoords", Dict{String,Any}()) + if haskey(data_eng["bus"], id) + bus = data_eng["bus"][id] bus["lon"] = coords["x"] bus["lat"] = coords["y"] end @@ -21,7 +21,7 @@ function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any for bus in buses @assert !startswith(bus, "_virtual") "Bus $bus: identifiers should not start with _virtual" - eng_obj = create_bus(status=1, bus_type=1) + eng_obj = create_bus(status=ENABLED) if !haskey(data_eng, "bus") data_eng["bus"] = Dict{String,Any}() @@ -34,50 +34,32 @@ end "Adds loadshapes to `data_eng` from `data_dss`" function _dss2eng_loadshape!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) - for (name, dss_obj) in get(data_dss, "loadshape", Dict{String,Any}()) + for (id, dss_obj) in get(data_dss, "loadshape", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "loadshape") - defaults = _apply_ordered_properties(_create_loadshape(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_loadshape(id; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}( - "hour" => defaults["hour"], - "pmult" => defaults["pmult"], - "qmult" => defaults["qmult"], - "use_actual" => defaults["useactual"], - "source_id" => "loadshape.$name", + "time" => defaults["hour"], + "offset" => 0.0, + "replace" => defaults["useactual"], + "values" => defaults["pmult"], + "source_id" => "loadshape.$id", ) if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "loadshape", name, eng_obj) - end -end - - -"Adds xycurve objects to `data_eng` from `data_dss`" -function _dss2eng_xycurve!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) - for (name, dss_obj) in get(data_dss, "xycurve", Dict{String,Any}()) - _apply_like!(dss_obj, data_dss, "xycurve") - defaults = _apply_ordered_properties(_create_xycurve(name; _to_kwargs(dss_obj)...), dss_obj) - - xarray = defaults["xarray"] .* defaults["xscale"] .+ defaults["xshift"] - yarray = defaults["yarray"] .* defaults["yscale"] .+ defaults["yshift"] + if _is_loadshape_split(dss_obj) + Memento.warn(_LOGGER, "Loadshape '$id' contains mismatched pmult and qmult, splitting into `time_series` ids '$(id)_p' and '$(id)_q'") + _add_eng_obj!(data_eng, "time_series", "$(id)_p", eng_obj) - @assert length(xarray) >= 2 && length(yarray) >= 2 "XYCurve data must have two or more points" - - k = length(xarray) - 1 - - eng_obj = Dict{String,Any}( - "interpolated_curve" => Spline1D(xarray, yarray; k=min(k, 5)), - "source_id" => "xycurve.$name", - ) + eng_obj["values"] = defaults["qmult"] - if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _add_eng_obj!(data_eng, "time_series", "$(id)_q", eng_obj) + else + _add_eng_obj!(data_eng, "time_series", id, eng_obj) end - - _add_eng_obj!(data_eng, "xycurve", name, eng_obj) end end @@ -97,38 +79,38 @@ Note that in the current feature set, fixed therefore equals constant # 7: Constant P and quadratic Q (i.e., fixed reactance) # 8: ZIP """ -function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, ground_terminal::Int=4) - for (name, dss_obj) in get(data_dss, "load", Dict{String,Any}()) +function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, ground_terminal::Int=4, time_series::String="daily") + for (id, dss_obj) in get(data_dss, "load", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "load") - defaults = _apply_ordered_properties(_create_load(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_load(id; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] conf = defaults["conn"] if conf==DELTA - @assert(nphases in [1, 3], "$name: only 1 and 3-phase delta loads are supported!") + @assert(nphases in [1, 3], "$id: only 1 and 3-phase delta loads are supported!") end # connections - bus = _parse_busname(defaults["bus1"])[1] + bus = _parse_bus_id(defaults["bus1"])[1] connections_default = conf==WYE ? [collect(1:nphases)..., 0] : nphases==1 ? [1,2] : [1,2,3] connections = _get_conductors_ordered(defaults["bus1"], default=connections_default, pad_ground=(conf==WYE)) - @assert(length(unique(connections))==length(connections), "$name: connections cannot be made to a terminal more than once.") + @assert(length(unique(connections))==length(connections), "$id: connections cannot be made to a terminal more than once.") # now we can create the load; if you do not have the correct model, # pd/qd fields will be populated by default (should not happen for constant current/impedance) eng_obj = Dict{String,Any}( - "name" => name, "bus" => bus, "model" => defaults["model"], "configuration" => conf, "connections" => connections, - "source_id" => "load.$name", - "status" => convert(Int, defaults["enabled"]) + "dispatchable" => NO, + "source_id" => "load.$id", + "status" => defaults["enabled"] ? ENABLED : DISABLED ) - _parse_dss_load_model!(eng_obj, name) + _parse_dss_load_model!(eng_obj, id) # if the ground is used directly, register load if 0 in connections @@ -145,24 +127,42 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["pd_nom"] = fill(defaults["kw"]/nphases, nphases) eng_obj["qd_nom"] = fill(defaults["kvar"]/nphases, nphases) - if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) + eng_obj["time_series"] = Dict{String,Any}() + if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) + eng_obj["time_series"]["pd_nom"] = "$(defaults[time_series])_p" + eng_obj["time_series"]["qd_nom"] = "$(defaults[time_series])_q" + else + eng_obj["time_series"]["pd_nom"] = defaults[time_series] + eng_obj["time_series"]["qd_nom"] = defaults[time_series] + end end - if !haskey(data_eng, "load") - data_eng["load"] = Dict{String,Any}() + if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) + eng_obj["time_series"] = Dict{String,Any}() + if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) + eng_obj["time_series"]["pg"] = "$(defaults[time_series])_p" + eng_obj["time_series"]["qg"] = "$(defaults[time_series])_q" + else + eng_obj["time_series"]["pg"] = defaults[time_series] + eng_obj["time_series"]["qg"] = defaults[time_series] + end end - data_eng["load"][name] = eng_obj + if import_all + _import_all!(eng_obj, dss_obj) + end + + _add_eng_obj!(data_eng, "load", id, eng_obj) end end "Adds capacitors to `data_eng` from `data_dss`" function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "capacitor", Dict{String,Any}()) + for (id, dss_obj) in get(data_dss, "capacitor", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "capacitor") - defaults = _apply_ordered_properties(_create_capacitor(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_capacitor(id; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] conn = defaults["conn"] @@ -178,16 +178,18 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String eng_obj = Dict{String,Any}( "configuration" => conn, - "status" => convert(Int, defaults["enabled"]), - "source_id" => "capacitor.$name", + "model" => CAPACITOR, + "dispatchable" => NO, + "status" => defaults["enabled"] ? ENABLED : DISABLED, + "source_id" => "capacitor.$id", ) if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - bus_name = _parse_busname(defaults["bus1"])[1] - bus2_name = _parse_busname(defaults["bus2"])[1] + bus_name = _parse_bus_id(defaults["bus1"])[1] + bus2_name = _parse_bus_id(defaults["bus2"])[1] if bus_name == bus2_name eng_obj["bus"] = bus_name @@ -213,38 +215,65 @@ function _dss2eng_capacitor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String # if one terminal is ground (0), reduce shunt addmittance matrix terminals, B = _calc_ground_shunt_admittance_matrix(terminals, B, 0) + eng_obj["gs"] = zeros(size(B)) eng_obj["bs"] = B eng_obj["connections"] = terminals - _add_eng_obj!(data_eng, "shunt_capacitor", name, eng_obj) + _add_eng_obj!(data_eng, "shunt", id, eng_obj) else - eng_obj["f_bus"] = bus_name - eng_obj["t_bus"] = bus2_name + Memento.warn(_LOGGER, "capacitors as constant impedance elements is not yet supported, treating reactor.$id like line") + _eng_obj = Dict{String,Any}( + "f_bus" => _parse_bus_id(defaults["bus1"])[1], + "t_bus" => _parse_bus_id(defaults["bus2"])[1], + "f_connections" => _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)), + "t_connections" => _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)), + "length" => 1.0, + "rs" => diagm(0 => fill(0.2, nphases)), + "xs" => zeros(nphases, nphases), + "g_fr" => zeros(nphases, nphases), + "b_fr" => zeros(nphases, nphases), + "g_to" => zeros(nphases, nphases), + "b_to" => zeros(nphases, nphases), + "status" => defaults["enabled"] ? ENABLED : DISABLED, + "source_id" => "capacitor.$id", + ) + + merge!(eng_obj, _eng_obj) + + # if the ground is used directly, register + if 0 in eng_obj["f_connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["f_bus"]], eng_obj["f_connections"]) + end + if 0 in eng_obj["t_connections"] + _register_awaiting_ground!(data_eng["bus"][eng_obj["t_bus"]], eng_obj["t_connections"]) + end - eng_obj["kv"] = fill(defaults["kv"] / nphases, nphases) - eng_obj["kvar"] = fill(defaults["kvar"] / nphases, nphases) + if import_all + _import_all!(eng_obj, dss_obj) + end - _add_eng_obj!(data_eng, "series_capacitor", name, eng_obj) + _add_eng_obj!(data_eng, "line", id, eng_obj) end end end - "Adds shunt reactors to `data_eng` from `data_dss`" function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) + for (id, dss_obj) in get(data_dss, "reactor", Dict{String,Any}()) if !haskey(dss_obj, "bus2") _apply_like!(dss_obj, data_dss, "reactor") - defaults = _apply_ordered_properties(_create_reactor(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_reactor(id; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] eng_obj = Dict{String,Any}( + "bus" => _parse_bus_id(defaults["bus1"])[1], "configuration" => defaults["conn"], - "bus" => _parse_busname(defaults["bus1"])[1], - "status" => convert(Int, defaults["enabled"]), - "source_id" => "reactor.$name", + "model" => REACTOR, + "dispatchable" => NO, + "status" => defaults["enabled"] ? ENABLED : DISABLED, + "source_id" => "reactor.$id", ) connections_default = eng_obj["configuration"] == WYE ? [collect(1:nphases)..., 0] : collect(1:nphases) @@ -259,34 +288,35 @@ function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< Gcap = sum(defaults["kvar"]) / (nphases * 1e3 * (data_eng["settings"]["vbase"])^2) eng_obj["bs"] = diagm(0=>fill(Gcap, nphases)) + eng_obj["gs"] = zeros(size(eng_obj["bs"])) if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "shunt_reactor", name, eng_obj) + _add_eng_obj!(data_eng, "shunt", id, eng_obj) else - Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating reactor.$name like line") + Memento.warn(_LOGGER, "reactors as constant impedance elements is not yet supported, treating reactor.$id like line") _apply_like!(dss_obj, data_dss, "reactor") - defaults = _apply_ordered_properties(_create_reactor(name; _to_kwargs(dss_obj)...), dss_obj) - - eng_obj = Dict{String,Any}() + defaults = _apply_ordered_properties(_create_reactor(id; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] - eng_obj["f_bus"] = _parse_busname(defaults["bus1"])[1] - eng_obj["t_bus"] = _parse_busname(defaults["bus2"])[1] - - eng_obj["length"] = 1.0 - - eng_obj["rs"] = diagm(0 => fill(0.2, nphases)) - - for key in ["xs", "g_fr", "g_to", "b_fr", "b_to"] - eng_obj[key] = zeros(nphases, nphases) - end - - eng_obj["f_connections"] = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) - eng_obj["t_connections"] = _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)) + eng_obj = Dict{String,Any}( + "f_bus" => _parse_bus_id(defaults["bus1"])[1], + "t_bus" => _parse_bus_id(defaults["bus2"])[1], + "f_connections" => _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)), + "t_connections" => _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)), + "length" => 1.0, + "rs" => diagm(0 => fill(0.2, nphases)), + "xs" => zeros(nphases, nphases), + "g_fr" => zeros(nphases, nphases), + "b_fr" => zeros(nphases, nphases), + "g_to" => zeros(nphases, nphases), + "b_to" => zeros(nphases, nphases), + "status" => defaults["enabled"] ? ENABLED : DISABLED, + "source_id" => "reactor.$id", + ) # if the ground is used directly, register if 0 in eng_obj["f_connections"] @@ -296,64 +326,69 @@ function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< _register_awaiting_ground!(data_eng["bus"][eng_obj["t_bus"]], eng_obj["t_connections"]) end - eng_obj["status"] = convert(Int, defaults["enabled"]) - - eng_obj["source_id"] = "reactor.$(name)" - if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "line_reactor", name, eng_obj) + _add_eng_obj!(data_eng, "line", id, eng_obj) end end end "Adds generators to `data_eng` from `data_dss`" -function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "generator", Dict{String,Any}()) +function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, time_series::String="daily") + for (id, dss_obj) in get(data_dss, "generator", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "generator") - defaults = _apply_ordered_properties(_create_generator(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_generator(id; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] eng_obj = Dict{String,Any}( "phases" => nphases, "connections" => _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)), - "bus" => _parse_busname(defaults["bus1"])[1], + "bus" => _parse_bus_id(defaults["bus1"])[1], "pg" => fill(defaults["kw"] / nphases, nphases), "qg" => fill(defaults["kvar"] / nphases, nphases), "vg" => fill(defaults["kv"], nphases), "qg_lb" => fill(defaults["minkvar"] / nphases, nphases), "qg_ub" => fill(defaults["maxkvar"] / nphases, nphases), - "control_mode" => defaults["model"], + "control_mode" => DROOP, "configuration" => WYE, - "status" => convert(Int, defaults["enabled"]), - "source_id" => "generator.$(name)" + "status" => defaults["enabled"] ? ENABLED : DISABLED, + "source_id" => "generator.$id" ) - _parse_dss_generator_model!(eng_obj, name) - # if the ground is used directly, register load if 0 in eng_obj["connections"] _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end + if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) + eng_obj["time_series"] = Dict{String,Any}() + if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) + eng_obj["time_series"]["pg"] = "$(defaults[time_series])_p" + eng_obj["time_series"]["qg"] = "$(defaults[time_series])_q" + else + eng_obj["time_series"]["pg"] = defaults[time_series] + eng_obj["time_series"]["qg"] = defaults[time_series] + end + end + if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "generator", name, eng_obj) + _add_eng_obj!(data_eng, "generator", id, eng_obj) end end "Adds vsources to `data_eng` from `data_dss`" -function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "vsource", Dict{<:Any,<:Any}()) +function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, time_series::String="daily") + for (id, dss_obj) in get(data_dss, "vsource", Dict{<:Any,<:Any}()) _apply_like!(dss_obj, data_dss, "vsource") - defaults = _create_vsource(name; _to_kwargs(dss_obj)...) + defaults = _create_vsource(id; _to_kwargs(dss_obj)...) ph1_ang = defaults["angle"] @@ -366,14 +401,14 @@ function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) eng_obj = Dict{String,Any}( - "bus" => _parse_busname(defaults["bus1"])[1], + "bus" => _parse_bus_id(defaults["bus1"])[1], "connections" => collect(1:phases), "vm" => vm, "va" => va, "rs" => defaults["rmatrix"], "xs" => defaults["xmatrix"], - "source_id" => "vsource.$name", - "status" => convert(Int, defaults["enabled"]) + "source_id" => "vsource.$id", + "status" => defaults["enabled"] ? ENABLED : DISABLED ) # if the ground is used directly, register load @@ -381,23 +416,34 @@ function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end + if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) + eng_obj["time_series"] = Dict{String,Any}() + if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) + eng_obj["time_series"]["pg"] = "$(defaults[time_series])_p" + eng_obj["time_series"]["qg"] = "$(defaults[time_series])_q" + else + eng_obj["time_series"]["pg"] = defaults[time_series] + eng_obj["time_series"]["qg"] = defaults[time_series] + end + end + if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "voltage_source", name, eng_obj) + _add_eng_obj!(data_eng, "voltage_source", id, eng_obj) end end "Adds lines to `data_eng` from `data_dss`" function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "linecode", Dict{String,Any}()) + for (id, dss_obj) in get(data_dss, "linecode", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "linecode") dss_obj["circuit_basefreq"] = data_eng["settings"]["base_frequency"] - defaults = _apply_ordered_properties(_create_linecode(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_linecode(id; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String,Any}() @@ -412,24 +458,24 @@ function _dss2eng_linecode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, eng_obj["g_to"] = fill(0.0, nphases, nphases) if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "linecode", name, eng_obj) + _add_eng_obj!(data_eng, "linecode", id, eng_obj) end end "Adds lines to `data_eng` from `data_dss`" function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "line", Dict()) + for (id, dss_obj) in get(data_dss, "line", Dict()) _apply_like!(dss_obj, data_dss, "line") if haskey(dss_obj, "basefreq") && dss_obj["basefreq"] != data_eng["settings"]["base_frequency"] - Memento.warn(_LOGGER, "basefreq=$(dss_obj["basefreq"]) on line $(dss_obj["name"]) does not match circuit basefreq=$(data_eng["settings"]["base_frequency"])") + Memento.warn(_LOGGER, "basefreq=$(dss_obj["basefreq"]) on line.$id does not match circuit basefreq=$(data_eng["settings"]["base_frequency"])") end - defaults = _apply_ordered_properties(_create_line(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_line(id; _to_kwargs(dss_obj)...), dss_obj) _like_is_switch = haskey(dss_obj, "like") && get(get(data_dss["line"], dss_obj["like"], Dict{String,Any}()), "switch", false) nphases = defaults["phases"] @@ -438,13 +484,13 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An t_connections = _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)) eng_obj = Dict{String,Any}( - "f_bus" => _parse_busname(defaults["bus1"])[1], - "t_bus" => _parse_busname(defaults["bus2"])[1], + "f_bus" => _parse_bus_id(defaults["bus1"])[1], + "t_bus" => _parse_bus_id(defaults["bus2"])[1], "length" => defaults["switch"] || _like_is_switch ? 0.001 : defaults["length"], "f_connections" => f_connections, "t_connections" => t_connections, - "status" => convert(Int, defaults["enabled"]), - "source_id" => "line.$name" + "status" => defaults["enabled"] ? ENABLED : DISABLED, + "source_id" => "line.$id" ) if haskey(dss_obj, "linecode") @@ -474,13 +520,20 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An end if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end if defaults["switch"] - _add_eng_obj!(data_eng, "switch", name, eng_obj) + eng_obj["state"] = CLOSED + eng_obj["dispatchable"] = NO + + for key in ["g_fr", "b_fr", "g_to", "b_to"] + delete!(eng_obj, key) + end + + _add_eng_obj!(data_eng, "switch", id, eng_obj) else - _add_eng_obj!(data_eng, "line", name, eng_obj) + _add_eng_obj!(data_eng, "line", id, eng_obj) end end end @@ -488,9 +541,9 @@ end "Adds transformers to `data_eng` from `data_dss`" function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "xfmrcode", Dict{String,Any}()) + for (id, dss_obj) in get(data_dss, "xfmrcode", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "xfmrcode") - defaults = _apply_ordered_properties(_create_xfmrcode(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_xfmrcode(id; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] nrw = defaults["windings"] @@ -503,31 +556,31 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, "tm_step" => Vector{Vector{Float64}}(fill(fill(1/32, nphases), nrw)), "vnom" => Vector{Float64}(defaults["kvs"]), "snom" => Vector{Float64}(defaults["kvas"]), - "configuration" => Vector{ConnectionConfiguration}(defaults["conns"]), + "configuration" => Vector{ConnConfig}(defaults["conns"]), "rs" => Vector{Float64}(defaults["%rs"] ./ 100), "noloadloss" => defaults["%noloadloss"] / 100, "imag" => defaults["%imag"] / 100, "xsc" => nrw == 2 ? [defaults["xhl"] / 100] : [defaults["xhl"], defaults["xht"], defaults["xlt"]] ./ 100, - "source_id" => "xfmrcode.$name", + "source_id" => "xfmrcode.$id", ) if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "xfmrcode", name, eng_obj) + _add_eng_obj!(data_eng, "xfmrcode", id, eng_obj) end end "Adds transformers to `data_eng` from `data_dss`" function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) + for (id, dss_obj) in get(data_dss, "transformer", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "transformer") - defaults = _apply_ordered_properties(_create_transformer(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_transformer(id; _to_kwargs(dss_obj)...), dss_obj) eng_obj = Dict{String, Any}( - "source_id" => "transformer.$name" + "source_id" => "transformer.$id" ) # no way around checking xfmrcode for these properties @@ -655,7 +708,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri # test if this transformer conforms with limitations if nphases<3 && DELTA in confs - # Memento.error(_LOGGER, "Transformers with delta windings should have at least 3 phases to be well-defined: $name.") + # Memento.error(_LOGGER, "Transformers with delta windings should have at least 3 phases to be well-defined: $id.") end if nrw>3 # All of the code is compatible with any number of windings, @@ -664,7 +717,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end for w in 1:nrw - eng_obj["bus"][w] = _parse_busname(defaults["buses"][w])[1] + eng_obj["bus"][w] = _parse_bus_id(defaults["buses"][w])[1] conf = confs[w] terminals_default = conf==WYE ? [1:nphases..., 0] : collect(1:nphases) @@ -703,40 +756,44 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "transformer", name, eng_obj) + _add_eng_obj!(data_eng, "transformer", id, eng_obj) end end "Adds pvsystems to `data_eng` from `data_dss`" -function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "pvsystem", Dict{String,Any}()) +function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, time_series::String="daily") + for (id, dss_obj) in get(data_dss, "pvsystem", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "pvsystem") - defaults = _apply_ordered_properties(_create_pvsystem(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_pvsystem(id; _to_kwargs(dss_obj)...), dss_obj) # TODO pick parameters for solar objects nphases = defaults["phases"] eng_obj = Dict{String,Any}( - "bus" => _parse_busname(defaults["bus1"])[1], + "bus" => _parse_bus_id(defaults["bus1"])[1], "configuration" => defaults["conn"], "connections" => _get_conductors_ordered(defaults["bus1"], pad_ground=true, default=collect(1:defaults["phases"]+1)), - "kva" => fill(defaults["kva"] / nphases, nphases), - "kvar" => fill(defaults["kvar"] / nphases, nphases), - "kv" => fill(defaults["kv"] / nphases, nphases), - "max_rated_power" => fill(defaults["pmpp"] / nphases, nphases), - "irradiance" => defaults["irradiance"], - "temperature" => defaults["temperature"], - "p-t_curve" => defaults["p-tcurve"], - "efficiency_curve" => defaults["effcurve"], - "rs" => diagm(0 => fill(defaults["%r"] / 100., nphases)), - "xs" => diagm(0 => fill(defaults["%x"] / 100., nphases)), - "status" => convert(Int, defaults["enabled"]), - "source_id" => "pvsystem.$name", + "pg" => fill(defaults["kva"] / nphases, nphases), + "qg" => fill(defaults["kvar"] / nphases, nphases), + "vg" => fill(defaults["kv"] / nphases, nphases), + "pg_lb" => fill(0.0, nphases), + "pg_ub" => fill(defaults["kva"], nphases), + "qg_lb" => fill(-defaults["kvar"], nphases), + "qg_ub" => fill( defaults["kvar"], nphases), + # "sm_ub" => fill(defaults["pmpp"] / nphases, nphases), # TODO add irradiance model + # "irradiance" => defaults["irradiance"], + # "temperature" => defaults["temperature"], + # "p-t_curve" => defaults["p-tcurve"], + # "efficiency_curve" => defaults["effcurve"], + # "rs" => diagm(0 => fill(defaults["%r"] / 100., nphases)), + # "xs" => diagm(0 => fill(defaults["%x"] / 100., nphases)), + "status" => defaults["enabled"] ? ENABLED : DISABLED, + "source_id" => "pvsystem.$id", ) # if the ground is used directly, register load @@ -744,25 +801,36 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end + if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) + eng_obj["time_series"] = Dict{String,Any}() + if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) + eng_obj["time_series"]["pg"] = "$(defaults[time_series])_p" + eng_obj["time_series"]["qg"] = "$(defaults[time_series])_q" + else + eng_obj["time_series"]["pg"] = defaults[time_series] + eng_obj["time_series"]["qg"] = defaults[time_series] + end + end + if import_all - _import_all!(eng_obj, dss_obj, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "solar", name, eng_obj) + _add_eng_obj!(data_eng, "solar", id, eng_obj) end end "Adds storage to `data_eng` from `data_dss`" -function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool) - for (name, dss_obj) in get(data_dss, "storage", Dict{String,Any}()) +function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, time_series::String="daily") + for (id, dss_obj) in get(data_dss, "storage", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "storage") - defaults = _apply_ordered_properties(_create_storage(name; _to_kwargs(dss_obj)...), dss_obj) + defaults = _apply_ordered_properties(_create_storage(id; _to_kwargs(dss_obj)...), dss_obj) nphases = defaults["phases"] eng_obj = Dict{String,Any}( - "bus" => _parse_busname(defaults["bus1"])[1], + "bus" => _parse_bus_id(defaults["bus1"])[1], "connections" => _get_conductors_ordered(defaults["bus1"], check_length=false), "configuration" => WYE, "energy" => defaults["kwhstored"], @@ -778,8 +846,8 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< "xs" => fill(defaults["%x"] / nphases / 100.0, nphases), "pex" => defaults["%idlingkw"] .* defaults["kwrated"], "qex" => defaults["%idlingkvar"] .* defaults["kvar"], - "status" => convert(Int, defaults["enabled"]), - "source_id" => "storage.$(name)", + "status" => defaults["enabled"] ? ENABLED : DISABLED, + "source_id" => "storage.$id", ) # if the ground is used directly, register load @@ -787,11 +855,22 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end + if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) + eng_obj["time_series"] = Dict{String,Any}() + if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) + eng_obj["time_series"]["ps"] = "$(defaults[time_series])_p" + eng_obj["time_series"]["qs"] = "$(defaults[time_series])_q" + else + eng_obj["time_series"]["ps"] = defaults[time_series] + eng_obj["time_series"]["qs"] = defaults[time_series] + end + end + if import_all - _import_all(eng_obj, defaults, dss_obj["prop_order"]) + _import_all!(eng_obj, dss_obj) end - _add_eng_obj!(data_eng, "storage", name, eng_obj) + _add_eng_obj!(data_eng, "storage", id, eng_obj) end end @@ -817,7 +896,7 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban if haskey(data_dss, "vsource") && haskey(data_dss["vsource"], "source") && haskey(data_dss, "circuit") defaults = _create_vsource("source"; _to_kwargs(data_dss["vsource"]["source"])...) - source_bus = _parse_busname(defaults["bus1"])[1] + source_bus = _parse_bus_id(defaults["bus1"])[1] data_eng["name"] = data_dss["circuit"] data_eng["settings"]["v_var_scalar"] = 1e3 @@ -847,8 +926,6 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban _dss2eng_loadshape!(data_eng, data_dss, import_all) _dss2eng_load!(data_eng, data_dss, import_all) - _dss2eng_xycurve!(data_eng, data_dss, import_all) - _dss2eng_vsource!(data_eng, data_dss, import_all) _dss2eng_generator!(data_eng, data_dss, import_all) _dss2eng_pvsystem!(data_eng, data_dss, import_all) diff --git a/src/io/utils.jl b/src/io/utils.jl index cc22087c8..fcc946c0a 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -86,15 +86,6 @@ const _dss2pmd_load_model = Dict{Int,LoadModel}( ) -"dss to pmd generator model" -const _dss2pmd_gen_model = Dict{Int,GeneratorControlMode}( - 1 => PQ, - 2 => Z, - 3 => PV, - 7 => INVERTER, -) - - "detects if `expr` is Reverse Polish Notation expression" function _isa_rpn(expr::AbstractString)::Bool expr = split(strip(expr, _array_delimiters)) @@ -164,7 +155,7 @@ end "parses connection \"conn\" specification reducing to wye or delta" -function _parse_conn(conn::AbstractString)::ConnectionConfiguration +function _parse_conn(conn::AbstractString)::ConnConfig if conn in ["wye", "y", "ln"] return WYE elseif conn in ["delta", "ll"] @@ -286,7 +277,7 @@ function _parse_array(dtype::Type, data::AbstractString)::Vector{dtype} end if all(_isa_conn(el) for el in elements) - array = Vector{ConnectionConfiguration}([]) + array = Vector{ConnConfig}([]) for el in elements a = _parse_conn(el) push!(array, a) @@ -513,8 +504,8 @@ end "creates a `dss` dict inside `object` that imports all items in `prop_order` from `dss_obj`" -function _import_all!(object::Dict{String,<:Any}, dss_obj::Dict{String,<:Any}, prop_order::Vector{String}) - object["dss"] = Dict{String,Any}((key, dss_obj[key]) for key in prop_order) +function _import_all!(object::Dict{String,<:Any}, dss_obj::Dict{String,<:Any}) + object["dss"] = Dict{String,Any}((key, dss_obj[key]) for key in dss_obj["prop_order"]) end @@ -592,7 +583,7 @@ end "Parses busnames as defined in OpenDSS, e.g. \"primary.1.2.3.0\"" -function _parse_busname(busname::AbstractString)::Tuple{String,Vector{Bool}} +function _parse_bus_id(busname::AbstractString)::Tuple{String,Vector{Bool}} parts = split(busname, '.'; limit=2) name = parts[1] elements = "1.2.3" @@ -817,6 +808,11 @@ function _add_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, eng_o data_eng[eng_obj_type] = Dict{Any,Any}() end + if haskey(data_eng[eng_obj_type], eng_obj_id) + Memento.warn(_LOGGER, "id '$eng_obj_id' already exists in $eng_obj_type, renaming to '$(eng_obj["source_id"])'") + eng_obj_id = eng_obj["source_id"] + end + data_eng[eng_obj_type][eng_obj_id] = eng_obj end @@ -867,14 +863,34 @@ function _parse_dss_load_model!(eng_obj::Dict{String,<:Any}, id::Any) end -"converts dss generator model into PMD GeneratorControlMode enum" -function _parse_dss_generator_model!(eng_obj::Dict{String,<:Any}, id::Any) - model = eng_obj["control_mode"] +"checks if loadshape has both pmult and qmult" +function _is_loadshape_split(dss_obj::Dict{String,<:Any}) + haskey(dss_obj, "pmult") && haskey(dss_obj, "qmult") && all(dss_obj["pmult"] .!= dss_obj["qmult"]) +end - if model in [2, 4, 5, 6, 7] - Memento.warn(_LOGGER, "$id: dss generator model $model not supported. Treating as constant PQ model") - model = 1 - end - eng_obj["control_mode"] = _dss2pmd_gen_model[model] +"" +function _parse_dss_xycurve(dss_obj::Dict{String,<:Any})::Array{Vector{Real},2} + _apply_like!(dss_obj, data_dss, "xycurve") + defaults = _apply_ordered_properties(_create_xycurve(id; _to_kwargs(dss_obj)...), dss_obj) + + xarray = defaults["xarray"] .* defaults["xscale"] .+ defaults["xshift"] + yarray = defaults["yarray"] .* defaults["yscale"] .+ defaults["yshift"] + + @assert length(xarray) >= 2 && length(yarray) >= 2 "XYCurve data must have two or more points" + + return Array{Vector{Real},2}([xarray, yarray]) +end + + +"" +function _apply_time_series!(eng_obj::Dict{String,<:Any}, defaults::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, time_series::String="daily") + eng_obj["time_series"] = Dict{String,Any}() + if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) + eng_obj["time_series"]["pd_nom"] = "$(defaults[time_series])_p" + eng_obj["time_series"]["qd_nom"] = "$(defaults[time_series])_q" + else + eng_obj["time_series"]["pd_nom"] = defaults[time_series] + eng_obj["time_series"]["qd_nom"] = defaults[time_series] + end end diff --git a/test/opendss.jl b/test/opendss.jl index cfc6b94aa..15cd0faa3 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -234,18 +234,6 @@ end @testset "opendss parse verify source_id" begin - @test math["shunt"]["2"]["source_id"] == "capacitor.c1" - @test math["shunt"]["4"]["source_id"] == "reactor.reactor3" - - @test math["branch"]["8"]["source_id"] == "line.l1" - @test math["transformer"]["9"]["source_id"] == "_virtual_transformer.transformer.t4.1" # winding indicated by .1 - @test math["branch"]["25"]["source_id"] == "reactor.reactor1" - - @test math["gen"]["4"]["source_id"] == "_virtual_gen.vsource.source" - @test math["gen"]["1"]["source_id"] == "generator.g2" - - @test math["load"]["1"]["source_id"] == "load.ld1" - @test all(haskey(component, "source_id") for component_type in PMD._dss_supported_components for component in values(get(math, component_type, Dict())) if component_type != "bus") end diff --git a/test/opf.jl b/test/opf.jl index 591bfe42f..6195e3e9d 100644 --- a/test/opf.jl +++ b/test/opf.jl @@ -188,7 +188,7 @@ @test sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]) < 0.0 @test sum(sol["solution"]["voltage_source"]["source"]["qg"] * sol["solution"]["settings"]["sbase"]) < 0.005 @test isapprox(sum(sol["solution"]["solar"]["pv1"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.0183685; atol=1e-4) - @test isapprox(sum(sol["solution"]["solar"]["pv1"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.0048248; atol=1e-4) + @test isapprox(sum(sol["solution"]["solar"]["pv1"]["qg"] * sol["solution"]["settings"]["sbase"]), 0.0091449; atol=1e-4) end @testset "3-bus unbalanced single-phase pv acp opf" begin From fae605e73506727e2d89ee3e41834ffd94a0eb85 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Thu, 30 Apr 2020 20:15:13 +1000 Subject: [PATCH 175/224] enum vector equalities vector equalities break for enums, so I replaced them with a list comprehension --- src/data_model/eng2math.jl | 2 +- src/io/opendss.jl | 2 +- src/io/utils.jl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 4ef125998..d119a913d 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -288,7 +288,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic if kron_reduced # TODO fix how padding works, this is a workaround to get bank working - if all(eng_obj["configuration"] .== WYE) + if all(conf==WYE for conf in eng_obj["configuration"]) f_connections = transformer_2wa_obj["f_connections"] _pad_properties!(transformer_2wa_obj, ["tm_lb", "tm_ub", "tm_set"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) _pad_properties!(transformer_2wa_obj, ["tm_fix"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=false) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index b774bcf82..7decb7b17 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -610,7 +610,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri nrw = _shared["windings"] # two-phase delta transformers have single coil - if all(confs.==DELTA) && nphases==2 + if all(conf==DELTA for conf in confs) && nphases==2 ncoils = 1 else ncoils = nphases diff --git a/src/io/utils.jl b/src/io/utils.jl index fcc946c0a..e9119dbf4 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -331,7 +331,7 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) nrw = length(btrans["bus"]) # only attempt to bank wye-connected transformers - if !all(all(tr["configuration"].==WYE) for tr in trs) + if !all(all(conf==WYE for conf in tr["configuration"]) for tr in trs) Memento.warn(_LOGGER, "Not all configurations 'wye' on transformers identified by bank='$bank', aborting attempt to bank") continue end From 7fa4dd39a23165cf7322a6fe9b8de27d86e2b580 Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Thu, 30 Apr 2020 22:42:30 +1000 Subject: [PATCH 176/224] vbase/sbase/scale_factor --- src/data_model/components.jl | 5 ++- src/data_model/eng2math.jl | 36 ++++++++---------- src/data_model/units.jl | 74 +++++++++++++++++++----------------- src/io/common.jl | 6 ++- src/io/opendss.jl | 11 +++--- 5 files changed, 69 insertions(+), 63 deletions(-) diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 83fb5f385..5ff4ce4f5 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -62,7 +62,8 @@ function Model(model_type::String=ENGINEERING; kwargs...)::Dict{String,Any} "data_model" => model_type, "per_unit" => false, "settings" => Dict{String,Any}( - "v_var_scalar" => get(kwargs, :v_var_scalar, 1e3), + "voltage_scale_factor" => get(kwargs, :voltage_scale_factor, 1e3), + "power_scale_factor" => get(kwargs, :power_scale_factor, 1e3), "vbase" => get(kwargs, :basekv, 1.0), "sbase" => get(kwargs, :baseMVA, 1.0), "base_frequency" => get(kwargs, :basefreq, 60.0), @@ -336,4 +337,4 @@ add_voltage_source!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) add_generator!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "generator", id, create_generator(; bus=bus, kwargs...)) # add_storage!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "storage", id, create_storage(; bus=bus, kwargs...)) # add_solar!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "solar", id, create_solar(; bus=bus, kwargs...)) -# add_wind!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "wind", id, create_wind(; bus=bus, kwargs...)) \ No newline at end of file +# add_wind!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "wind", id, create_wind(; bus=bus, kwargs...)) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index d119a913d..8a63241a2 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -40,8 +40,6 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) #TODO the PM tests break for branches which are not of the size indicated by conductors; # for now, set to 1 to prevent this from breaking when not kron-reduced data_math["conductors"] = kron_reduced ? 3 : 1 - data_math["basekv"] = data_eng["settings"]["vbase"] - data_math["baseMVA"] = data_eng["settings"]["sbase"]*data_eng["settings"]["v_var_scalar"]/1E6 data_math["map"] = Vector{Dict{String,Any}}([ Dict{String,Any}("unmap_function" => "_map_math2eng_root!") @@ -120,8 +118,6 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, math_obj["vmin"] = get(eng_obj, "vm_lb", fill(0.0, length(terminals))) math_obj["vmax"] = get(eng_obj, "vm_ub", fill(Inf, length(terminals))) - math_obj["base_kv"] = data_eng["settings"]["vbase"] - if kron_reduced filter = terminals.!=kr_neutral terminals_kr = terminals[filter] @@ -229,8 +225,8 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic _apply_xfmrcode!(eng_obj, data_eng) - vnom = eng_obj["vnom"] * data_eng["settings"]["v_var_scalar"] - snom = eng_obj["snom"] * data_eng["settings"]["v_var_scalar"] + vnom = eng_obj["vnom"] * data_eng["settings"]["voltage_scale_factor"] + snom = eng_obj["snom"] * data_eng["settings"]["power_scale_factor"] nrw = length(eng_obj["bus"]) @@ -247,7 +243,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 # data is measured externally, but we now refer it to the internal side - ratios = vnom/data_eng["settings"]["v_var_scalar"] + ratios = vnom/data_eng["settings"]["voltage_scale_factor"] x_sc = x_sc./ratios[1]^2 r_s = r_s./ratios.^2 g_sh = g_sh*ratios[1]^2 @@ -339,7 +335,6 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A "bus_type" => get(eng_obj, "state", CLOSED) == OPEN ? 4 : 1, "vmin" => f_bus["vmin"], "vmax" => f_bus["vmax"], - "base_kv" => f_bus["base_kv"], "index" => length(data_math["bus"])+1, ) @@ -487,7 +482,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["gen_status"] = eng_obj["status"] - math_obj["vg"] = eng_obj["vg"] ./ data_math["basekv"] + math_obj["vg"] = eng_obj["vg"] math_obj["qmin"] = eng_obj["qg_lb"] math_obj["qmax"] = eng_obj["qg_ub"] @@ -578,19 +573,21 @@ function _map_eng2math_storage!(data_math::Dict{String,<:Any}, data_eng::Dict{<: math_obj["storage_bus"] = data_math["bus_lookup"][eng_obj["bus"]] # needs to be in units MW - math_obj["energy"] = eng_obj["energy"] * data_eng["settings"]["v_var_scalar"] / 1e6 - math_obj["energy_rating"] = eng_obj["energy_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 - math_obj["charge_rating"] = eng_obj["charge_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 - math_obj["discharge_rating"] = eng_obj["discharge_ub"] * data_eng["settings"]["v_var_scalar"] / 1e6 + math_obj["energy"] = eng_obj["energy"] * data_eng["settings"]["power_scale_factor"] / 1e6 + #TODO is scale factor correct? + math_obj["energy_rating"] = eng_obj["energy_ub"] * data_eng["settings"]["power_scale_factor"] / 1e6 + math_obj["charge_rating"] = eng_obj["charge_ub"] * data_eng["settings"]["power_scale_factor"] / 1e6 + math_obj["discharge_rating"] = eng_obj["discharge_ub"] * data_eng["settings"]["power_scale_factor"] / 1e6 math_obj["charge_efficiency"] = eng_obj["charge_efficiency"] / 100.0 math_obj["discharge_efficiency"] = eng_obj["discharge_efficiency"] / 100.0 - math_obj["thermal_rating"] = eng_obj["cm_ub"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 - math_obj["qmin"] = eng_obj["qs_lb"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 - math_obj["qmax"] = eng_obj["qs_ub"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + #TODO is scale factor correct? what should be the unit? + math_obj["thermal_rating"] = eng_obj["cm_ub"] .* data_eng["settings"]["power_scale_factor"] ./ 1e6 + math_obj["qmin"] = eng_obj["qs_lb"] .* data_eng["settings"]["power_scale_factor"] ./ 1e6 + math_obj["qmax"] = eng_obj["qs_ub"] .* data_eng["settings"]["power_scale_factor"] ./ 1e6 math_obj["r"] = eng_obj["rs"] math_obj["x"] = eng_obj["xs"] - math_obj["p_loss"] = eng_obj["pex"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 - math_obj["q_loss"] = eng_obj["qex"] .* data_eng["settings"]["v_var_scalar"] ./ 1e6 + math_obj["p_loss"] = eng_obj["pex"] .* data_eng["settings"]["power_scale_factor"] ./ 1e6 + math_obj["q_loss"] = eng_obj["qex"] .* data_eng["settings"]["power_scale_factor"] ./ 1e6 math_obj["ps"] = get(eng_obj, "ps", zeros(size(eng_obj["cm_ub"]))) math_obj["qs"] = get(eng_obj, "qs", zeros(size(eng_obj["cm_ub"]))) @@ -640,8 +637,7 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: "vm" => [eng_obj["vm"]..., 0.0], "va" => [eng_obj["va"]..., 0.0], "vmin" => [eng_obj["vm"]..., 0.0], - "vmax" => [eng_obj["vm"]..., 0.0], - "basekv" => data_math["basekv"] + "vmax" => [eng_obj["vm"]..., 0.0] ) if kron_reduced diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 72cfabbd3..e5f24d777 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -24,14 +24,17 @@ const _dimensionalize_math = Dict{String,Dict{String,Vector{String}}}( "converts data model between per-unit and SI units" -function make_per_unit!(data::Dict{String,<:Any}; data_model_type=get(data, "data_model", MATHEMATICAL)) +function make_per_unit!(data::Dict{String,<:Any}; vbases=nothing, sbase=nothing, data_model_type=get(data, "data_model", MATHEMATICAL)) if data_model_type == MATHEMATICAL if !get(data, "per_unit", false) - bus_indexed_id = string(data["bus_lookup"][data["settings"]["base_bus"]]) - vbases = Dict(bus_indexed_id=>data["settings"]["vbase"]) - sbase = data["settings"]["sbase"] + if vbases==nothing + vbases = Dict(string(data["bus_lookup"][id])=>vbase for (id, vbase) in data["settings"]["vbases_default"]) + end + if sbase==nothing + sbase = data["settings"]["sbase_default"] + end - _make_math_per_unit!(data, vbases=vbases, sbase=sbase, v_var_scalar=data["settings"]["v_var_scalar"]) + _make_math_per_unit!(data, vbases=vbases, sbase=sbase) else # TODO make math model si units end @@ -125,25 +128,25 @@ end "converts to per unit from SI" -function _make_math_per_unit!(data_model::Dict{String,<:Any}; settings::Union{Missing,Dict{String,<:Any}}=missing, sbase::Union{Real,Missing}=1.0, vbases::Union{Dict{String,<:Real},Missing}=missing, v_var_scalar::Union{Missing,Real}=missing) +function _make_math_per_unit!(data_model::Dict{String,<:Any}; sbase::Union{Real,Missing}=missing, vbases::Union{Dict{String,<:Real},Missing}=missing, voltage_scale_factor::Union{Missing,Real}=missing) if ismissing(sbase) - if !ismissing(settings) && haskey(settings, "set_sbase") - sbase = settings["set_sbase"] + if haskey(data_model["settings"], "sbase_default") + sbase = settings["sbase_default"] else sbase = 1.0 end end - if haskey(data_model, "sbase") - sbase_old = data_model["sbase"] + if haskey(data_model["settings"], "sbase") + sbase_old = data_model["settings"]["sbase"] else sbase_old = 1.0 end # automatically find a good vbase if ismissing(vbases) - if !ismissing(settings) && haskey(settings, "set_vbases") - vbases = settings["vbases"] + if haskey(data_model["settings"], "vbases_default") + vbases = settings["vbases_default"] else buses_type_3 = [(id, sum(bus["vm"])/length(bus["vm"])) for (id,bus) in data_model["bus"] if haskey(bus, "bus_type") && bus["bus_type"]==3] if !isempty(buses_type_3) @@ -155,26 +158,27 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}; settings::Union{Mi end bus_vbase, line_vbase = _calc_vbase(data_model, vbases) + voltage_scale_factor = data_model["settings"]["voltage_scale_factor"] for (id, bus) in data_model["bus"] - _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, v_var_scalar) + _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, voltage_scale_factor) end for (id, line) in data_model["branch"] vbase = line_vbase[id] - _rebase_pu_branch!(line, line_vbase[id], sbase, sbase_old, v_var_scalar) + _rebase_pu_branch!(line, line_vbase[id], sbase, sbase_old, voltage_scale_factor) end for (id, shunt) in data_model["shunt"] - _rebase_pu_shunt!(shunt, bus_vbase[string(shunt["shunt_bus"])], sbase, sbase_old, v_var_scalar) + _rebase_pu_shunt!(shunt, bus_vbase[string(shunt["shunt_bus"])], sbase, sbase_old, voltage_scale_factor) end for (id, load) in data_model["load"] - _rebase_pu_load!(load, bus_vbase[string(load["load_bus"])], sbase, sbase_old, v_var_scalar) + _rebase_pu_load!(load, bus_vbase[string(load["load_bus"])], sbase, sbase_old, voltage_scale_factor) end for (id, gen) in data_model["gen"] - _rebase_pu_generator!(gen, bus_vbase[string(gen["gen_bus"])], sbase, sbase_old, v_var_scalar, data_model) + _rebase_pu_generator!(gen, bus_vbase[string(gen["gen_bus"])], sbase, sbase_old, voltage_scale_factor, data_model) end for (id, storage) in data_model["storage"] @@ -190,11 +194,11 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}; settings::Union{Mi # voltage base across transformer does not have to be consistent with the ratio! f_vbase = bus_vbase[string(trans["f_bus"])] t_vbase = bus_vbase[string(trans["t_bus"])] - _rebase_pu_transformer_2w_ideal!(trans, f_vbase, t_vbase, sbase_old, sbase, v_var_scalar) + _rebase_pu_transformer_2w_ideal!(trans, f_vbase, t_vbase, sbase_old, sbase, voltage_scale_factor) end end - data_model["sbase"] = sbase + data_model["settings"]["sbase"] = sbase data_model["per_unit"] = true return data_model @@ -202,7 +206,7 @@ end "per-unit conversion for buses" -function _rebase_pu_bus!(bus::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real) +function _rebase_pu_bus!(bus::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, voltage_scale_factor::Real) # if not in p.u., these are normalized with respect to vnom prop_vnom = ["vm", "vmax", "vmin", "vm_set", "vm_ln_min", "vm_ln_max", "vm_lg_min", "vm_lg_max", "vm_ng_min", "vm_ng_max", "vm_ll_min", "vm_ll_max"] @@ -219,11 +223,11 @@ function _rebase_pu_bus!(bus::Dict{String,<:Any}, vbase::Real, sbase::Real, sbas vbase_old = bus["vbase"] _scale_props!(bus, [prop_vnom..., "vnom"], vbase_old/vbase) - z_old = vbase_old^2*sbase_old*v_var_scalar + z_old = vbase_old^2*sbase_old*voltage_scale_factor end # rebase grounding resistance - z_new = vbase^2/sbase*v_var_scalar + z_new = vbase^2/sbase*voltage_scale_factor z_scale = z_old/z_new _scale_props!(bus, ["rg", "xg"], z_scale) @@ -237,14 +241,14 @@ end "per-unit conversion for branches" -function _rebase_pu_branch!(branch::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real) +function _rebase_pu_branch!(branch::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, voltage_scale_factor::Real) if !haskey(branch, "vbase") z_old = 1 else - z_old = vbase_old^2/sbase_old*v_var_scalar + z_old = vbase_old^2/sbase_old*voltage_scale_factor end - z_new = vbase^2/sbase*v_var_scalar + z_new = vbase^2/sbase*voltage_scale_factor z_scale = z_old/z_new y_scale = 1/z_scale @@ -260,15 +264,15 @@ end "per-unit conversion for shunts" -function _rebase_pu_shunt!(shunt::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real) +function _rebase_pu_shunt!(shunt::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, voltage_scale_factor::Real) if !haskey(shunt, "vbase") z_old = 1 else - z_old = vbase_old^2/sbase_old*v_var_scalar + z_old = vbase_old^2/sbase_old*voltage_scale_factor end # rebase grounding resistance - z_new = vbase^2/sbase*v_var_scalar + z_new = vbase^2/sbase*voltage_scale_factor z_scale = z_old/z_new y_scale = 1/z_scale @@ -281,7 +285,7 @@ end "per-unit conversion for loads" -function _rebase_pu_load!(load::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real) +function _rebase_pu_load!(load::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, voltage_scale_factor::Real) if !haskey(load, "vbase") vbase_old = 1 sbase_old = 1 @@ -303,8 +307,8 @@ end "per-unit conversion for generators" -function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, v_var_scalar::Real, data_model::Dict{String,<:Any}) - vbase_old = get(gen, "vbase", 1.0/v_var_scalar) +function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, voltage_scale_factor::Real, data_model::Dict{String,<:Any}) + vbase_old = get(gen, "vbase", 1.0/voltage_scale_factor) vbase_scale = vbase_old/vbase sbase_scale = sbase_old/sbase @@ -313,8 +317,8 @@ function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real end # if not in per unit yet, the cost has is in $/MWh - if !haskey(data_model, "sbase") - sbase_old_cost = 1E6/v_var_scalar + if !haskey(data_model["settings"], "sbase") + sbase_old_cost = 1E6/voltage_scale_factor sbase_scale_cost = sbase_old_cost/sbase else sbase_scale_cost = sbase_scale @@ -328,7 +332,7 @@ end "per-unit conversion for ideal 2-winding transformers" -function _rebase_pu_transformer_2w_ideal!(transformer::Dict{String,<:Any}, f_vbase_new::Real, t_vbase_new::Real, sbase_old::Real, sbase_new::Real, v_var_scalar::Real) +function _rebase_pu_transformer_2w_ideal!(transformer::Dict{String,<:Any}, f_vbase_new::Real, t_vbase_new::Real, sbase_old::Real, sbase_new::Real, voltage_scale_factor::Real) f_vbase_old = get(transformer, "f_vbase", 1.0) t_vbase_old = get(transformer, "t_vbase", 1.0) f_vbase_scale = f_vbase_old/f_vbase_new @@ -359,7 +363,7 @@ _apply_func_vals(x, f) = isa(x, Dict) ? Dict(k=>f(v) for (k,v) in x) : f.(x) function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true, mult_ibase=true, convert_rad2deg=true) solution_si = deepcopy(solution) - sbase = math_model["sbase"] + sbase = math_model["settings"]["sbase"] for (comp_type, comp_dict) in [(x,y) for (x,y) in solution_si if isa(y, Dict)] dimensionalize_math_comp = get(_dimensionalize_math, comp_type, Dict()) diff --git a/src/io/common.jl b/src/io/common.jl index 1534754a6..4baa282fd 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -64,9 +64,13 @@ end function correct_network_data!(data::Dict{String,Any}; make_pu::Bool=true) if get(data, "data_model", MATHEMATICAL) == ENGINEERING check_eng_data_model(data) - else + else #TODO if DSS is also a data_model enum, then use elseif instead if make_pu make_per_unit!(data) + #TODO system-wide vbase does not make sense anymore... + #take highest vbase just so it does not break anything for now + data["basekv"] = maximum(bus["vbase"] for (_, bus) in data["bus"]) + data["baseMVA"] = data["settings"]["sbase"]*data["settings"]["power_scale_factor"]/1E6 _PM.check_connectivity(data) _PM.correct_transformer_parameters!(data) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 7decb7b17..782638ccb 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -395,7 +395,7 @@ function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< vm_pu = defaults["pu"] phases = defaults["phases"] - vnom = data_eng["settings"]["vbase"] + vnom = first(data_eng["settings"]["vbases_default"])[2] vm = fill(vm_pu, phases)*vnom va = rad2deg.(_wrap_to_pi.([-2*pi/phases*(i-1)+deg2rad(ph1_ang) for i in 1:phases])) @@ -899,10 +899,11 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban source_bus = _parse_bus_id(defaults["bus1"])[1] data_eng["name"] = data_dss["circuit"] - data_eng["settings"]["v_var_scalar"] = 1e3 - data_eng["settings"]["vbase"] = defaults["basekv"] / sqrt(3) - data_eng["settings"]["base_bus"] = source_bus - data_eng["settings"]["sbase"] = defaults["basemva"] * 1e3 + + data_eng["settings"]["voltage_scale_factor"] = 1e3 + data_eng["settings"]["power_scale_factor"] = 1e3 + data_eng["settings"]["vbases_default"] = Dict(source_bus=>defaults["basekv"] / sqrt(3)) + data_eng["settings"]["sbase_default"] = defaults["basemva"] * 1e3 data_eng["settings"]["base_frequency"] = get(get(data_dss, "options", Dict{String,Any}()), "defaultbasefreq", 60.0) # collect turns the Set into Array, making it serializable From 47b5982761036cfe60edfcd39bb65578d4c1be49 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 30 Apr 2020 13:00:18 -0600 Subject: [PATCH 177/224] ADD: multinetwork support FIX: unit tests RM: xycurve FIX: opendss syntax in test2_master --- src/PowerModelsDistribution.jl | 2 - src/data_model/eng2math.jl | 46 +++++++--- src/data_model/units.jl | 51 ++++++----- src/data_model/utils.jl | 138 ++++++++++++++++++----------- src/io/common.jl | 56 ++++++++---- src/io/dss_structs.jl | 9 +- src/io/opendss.jl | 15 +--- src/io/utils.jl | 15 +--- test/data/opendss/test2_master.dss | 7 +- test/opendss.jl | 57 +----------- 10 files changed, 204 insertions(+), 192 deletions(-) diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 19d0f51e4..4be288680 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -7,8 +7,6 @@ module PowerModelsDistribution import InfrastructureModels import Memento - import Dierckx: Spline1D - import LinearAlgebra const _PM = PowerModels diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 8a63241a2..37ce21f3c 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -25,6 +25,34 @@ const _edge_elements = Vector{String}([ "line", "switch", "transformer", "line_reactor", "series_capacitor" ]) +"list of multinetwork keys that belong at the root level" +const _pmd_math_global_keys = Set{String}([ + "conductors", "data_model", "per_unit", "name", "settings", "map", "bus_lookup" +]) + + +"converts a engineering multinetwork to a math multinetwork" +function _map_eng2math_multinetwork(data_eng_mn::Dict{String,Any}; kron_reduced::Bool=kron_reduced)::Dict{String,Any} + data_math_mn = Dict{String,Any}( + "nw" => Dict{String,Any}(), + "multinetwork" => true + ) + for (n, nw) in data_eng_mn["nw"] + for k in _pmd_eng_global_keys + nw[k] = data_eng_mn[k] + end + + data_math_mn["nw"][n] = _map_eng2math(nw; kron_reduced=kron_reduced) + + for k in _pmd_math_global_keys + data_math_mn[k] = data_math_mn["nw"][n][k] + delete!(data_math_mn["nw"][n], k) + end + end + + return data_math_mn +end + "base function for converting engineering model to mathematical model" function _map_eng2math(data_eng; kron_reduced::Bool=true) @@ -140,12 +168,6 @@ function _map_eng2math_bus!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any, "to" => "bus.$(math_obj["index"])", "unmap_function" => "_map_math2eng_bus!", )) - - # time series - # TODO - for (k, v) in get(eng_obj, "time_series", Dict{String,Any}()) - time_series = data_eng["time_series"][v] - end end end @@ -316,7 +338,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A math_obj["state"] = get(eng_obj, "state", CLOSED) # OPF bounds - for (fr_key, to_key) in zip(["cm_ub"], ["c_rating"]) + for (fr_key, to_key) in [("cm_ub", "c_rating")] if haskey(eng_obj, fr_key) math_obj[to_key] = eng_obj[fr_key] end @@ -356,10 +378,10 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], "br_r" => _impedance_conversion(data_eng, eng_obj, "rs"), "br_x" => _impedance_conversion(data_eng, eng_obj, "xs"), - "g_fr" => _admittance_conversion(data_eng, eng_obj, "g_fr"), - "g_to" => _admittance_conversion(data_eng, eng_obj, "g_to"), - "b_fr" => _admittance_conversion(data_eng, eng_obj, "b_fr"), - "b_to" => _admittance_conversion(data_eng, eng_obj, "b_to"), + "g_fr" => zeros(nphases, nphases), + "g_to" => zeros(nphases, nphases), + "b_fr" => zeros(nphases, nphases), + "b_to" => zeros(nphases, nphases), "angmin" => fill(-60.0, nphases), "angmax" => fill( 60.0, nphases), "transformer" => false, @@ -466,7 +488,7 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any "to" => "load.$(math_obj["index"])", "unmap_function" => "_map_math2eng_load!", )) - end + end end diff --git a/src/data_model/units.jl b/src/data_model/units.jl index e5f24d777..252442228 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -25,16 +25,23 @@ const _dimensionalize_math = Dict{String,Dict{String,Vector{String}}}( "converts data model between per-unit and SI units" function make_per_unit!(data::Dict{String,<:Any}; vbases=nothing, sbase=nothing, data_model_type=get(data, "data_model", MATHEMATICAL)) - if data_model_type == MATHEMATICAL + if data_model_type == MATHEMATICAL if !get(data, "per_unit", false) - if vbases==nothing + if vbases === nothing vbases = Dict(string(data["bus_lookup"][id])=>vbase for (id, vbase) in data["settings"]["vbases_default"]) end - if sbase==nothing + + if sbase === nothing sbase = data["settings"]["sbase_default"] end - _make_math_per_unit!(data, vbases=vbases, sbase=sbase) + if ismultinetwork(data) + for (n, nw) in data["nw"] + _make_math_per_unit!(nw, data; sbase=sbase, vbases=vbases) + end + else + _make_math_per_unit!(data, data; sbase=sbase, vbases=vbases) + end else # TODO make math model si units end @@ -128,37 +135,37 @@ end "converts to per unit from SI" -function _make_math_per_unit!(data_model::Dict{String,<:Any}; sbase::Union{Real,Missing}=missing, vbases::Union{Dict{String,<:Real},Missing}=missing, voltage_scale_factor::Union{Missing,Real}=missing) +function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math; sbase::Union{Real,Missing}=missing, vbases::Union{Dict{String,<:Real},Missing}=missing) if ismissing(sbase) - if haskey(data_model["settings"], "sbase_default") - sbase = settings["sbase_default"] + if haskey(data_math["settings"], "sbase_default") + sbase = data_math["settings"]["sbase_default"] else sbase = 1.0 end end - if haskey(data_model["settings"], "sbase") - sbase_old = data_model["settings"]["sbase"] + if haskey(data_math["settings"], "sbase") + sbase_old = data_math["settings"]["sbase"] else sbase_old = 1.0 end - # automatically find a good vbase + # automatically find a good vbase if not provided if ismissing(vbases) - if haskey(data_model["settings"], "vbases_default") - vbases = settings["vbases_default"] + if haskey(data_math["settings"], "vbases_default") + vbases = data_math["settings"]["vbases_default"] else buses_type_3 = [(id, sum(bus["vm"])/length(bus["vm"])) for (id,bus) in data_model["bus"] if haskey(bus, "bus_type") && bus["bus_type"]==3] - if !isempty(buses_type_3) - vbases = Dict([buses_type_3[1]]) - else - Memento.error("Please specify vbases manually; cannot make an educated guess for this data model.") + if !isempty(buses_type_3) + vbases = Dict([buses_type_3[1]]) + else + Memento.error("Please specify vbases manually; cannot make an educated guess for this data model.") + end end end - end bus_vbase, line_vbase = _calc_vbase(data_model, vbases) - voltage_scale_factor = data_model["settings"]["voltage_scale_factor"] + voltage_scale_factor = data_math["settings"]["voltage_scale_factor"] for (id, bus) in data_model["bus"] _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, voltage_scale_factor) @@ -178,7 +185,7 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}; sbase::Union{Real, end for (id, gen) in data_model["gen"] - _rebase_pu_generator!(gen, bus_vbase[string(gen["gen_bus"])], sbase, sbase_old, voltage_scale_factor, data_model) + _rebase_pu_generator!(gen, bus_vbase[string(gen["gen_bus"])], sbase, sbase_old, voltage_scale_factor, data_math) end for (id, storage) in data_model["storage"] @@ -198,10 +205,8 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}; sbase::Union{Real, end end - data_model["settings"]["sbase"] = sbase - data_model["per_unit"] = true - - return data_model + data_math["settings"]["sbase"] = sbase + data_math["per_unit"] = true end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index d3fc27e4f..abdfc21cb 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -1,3 +1,8 @@ +const _pmd_eng_global_keys = Set{String}([ + "settings", "files", "name", "data_model" +]) + + "initializes the base math object of any type, and copies any one-to-one mappings" function _init_math_obj(obj_type::String, eng_id::Any, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} math_obj = Dict{String,Any}( @@ -501,44 +506,6 @@ function _apply_linecode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:A end -"parses {}_times_series parameters into the expected InfrastructureModels format" -function _parse_time_series_parameter!(data_math::Dict{String,<:Any}, time_series::Dict{String,<:Any}, to_component_type::String, to_component_id::String, fr_parameter::Any, to_parameter::String, conversion_function::Function) - if !haskey(data_math, "time_series") - data_math["time_series"] => Dict{String,Any}() - end - - if !haskey(data_math["time_series"], "time") - data_math["time_series"]["time_point"] = time_series["time"] - else - if time_series["time"] == data_math["time_series"]["time_point"] - Memento.warn(_LOGGER, "Time series data doesn't match between different objects, aborting") - end - end - - data_math["time_series"]["num_steps"] = length(time_series["time"]) - - if !haskey(data_math["time_series"], to_component_type) - data_math["time_series"][to_component_type] = Dict{String,Any}() - end - - if !haskey(data_math["time_series"][to_component_type], to_component_id) - data_math["time_series"][to_component_type][to_component_id] = Dict{String,Any}() - end - - if time_series["replace"] - data_math["time_series"][to_component_type][to_component_id][to_parameter] = time_series["values"] - else - data_math["time_series"][to_component_type][to_component_id][to_parameter] = fr_parameter .* time_series["values"] - end -end - - -"" -function _no_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) - eng_obj[key] -end - - "" function _impedance_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) eng_obj[key] .* get(eng_obj, "length", 1.0) @@ -557,18 +524,6 @@ function _bus_type_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String end -"" -function _vnom_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) - eng_obj[key] ./ data_eng["settings"]["vbase"] -end - - -"" -function _angle_shift_conversion(data_eng::Dict{String,<:Any}, eng_obj::Dict{String,<:Any}, key::String) - _wrap_to_180([120.0 * i for i in 1:3] .+ eng_obj[key]) -end - - "lossy grounding to perfect grounding and shunts" function _convert_grounding(terminals, grounded, rg, xg) grouped = Dict(t=>[] for t in unique(grounded)) @@ -632,3 +587,86 @@ function _get_ground_math!(bus; exclude_terminals=[]) return n end end + + +"initializes a time_series data structure in the InfrastructureModels style" +function _init_time_series!(data::Dict{String,<:Any}, component_type::String, component_id::Any) + if !haskey(data, "time_series") + data["time_series"] = Dict{String,Any}() + end + + if !haskey(data["time_series"], component_type) + data["time_series"][component_type] = Dict{String,Any}() + end + + if !haskey(data["time_series"][component_type], "$component_id") + data["time_series"][component_type]["$component_id"] = Dict{String,Any}() + end +end + + +"Builds a Multinetwork" +function _build_eng_multinetwork(data_eng::Dict{String,<:Any})::Dict{String,Any} + _data_eng = Dict{String,Any}( + "time_series" => Dict{String,Any}( + "step_mismatch" => false + ) + ) + + for (eng_obj_type, eng_objs) in data_eng + if !(eng_obj_type in _pmd_eng_global_keys) && isa(eng_objs, Dict{<:Any,<:Any}) + for (id, eng_obj) in eng_objs + if haskey(eng_obj, "time_series") + _init_time_series!(_data_eng, eng_obj_type, id) + + for (k, v) in eng_obj["time_series"] + time_series = data_eng["time_series"][v] + + if !haskey(_data_eng["time_series"], "num_steps") + _data_eng["time_series"]["time"] = time_series["time"] + _data_eng["time_series"]["num_steps"] = length(time_series["time"]) + end + + if length(time_series["time"]) != _data_eng["time_series"]["num_steps"] + _data_eng["time_series"]["step_mismatch"] = true + end + + if time_series["replace"] + _data_eng["time_series"][eng_obj_type][id][k] = [zeros(size(eng_obj[k])) .+ val for val in time_series["values"]] + else + _data_eng["time_series"][eng_obj_type][id][k] = [eng_obj[k] .* val for val in time_series["values"]] + end + end + end + end + end + end + + data_eng_new = Dict{String,Any}() + ### HACK to get make_multinetwork working + for (eng_obj_type, eng_objs) in data_eng + if isa(eng_objs, Dict{Any,Any}) + eng_objs_new = Dict{String,Any}() + for (k, v) in eng_objs + key_new = "$k" + if key_new != k + Memento.warn(_LOGGER, "$eng_obj_type id $k converted to String") + end + eng_objs_new[key_new] = v + end + data_eng_new[eng_obj_type] = eng_objs_new + else + data_eng_new[eng_obj_type] = eng_objs + end + end + + _pre_mn_data = merge(data_eng_new, _data_eng) + + if _pre_mn_data["time_series"]["step_mismatch"] + Memento.warn(_LOGGER, "There is a mismatch between the num_steps of different time_series, cannot automatically build multinetwork structure") + return _pre_mn_data + else + delete!(_data_eng["time_series"], "step_mismatch") + return _IM.make_multinetwork(_pre_mn_data, _pmd_eng_global_keys) + end +end diff --git a/src/io/common.jl b/src/io/common.jl index 4baa282fd..032299d73 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -3,7 +3,16 @@ Parses the IOStream of a file into a PowerModelsDistribution data structure. """ -function parse_file(io::IO, filetype::AbstractString="json"; data_model::DataModel=ENGINEERING, import_all::Bool=false, bank_transformers::Bool=true, transformations::Vector{<:Function}=Vector{Function}([]))::Dict{String,Any} +function parse_file( + io::IO, filetype::AbstractString="json"; + data_model::DataModel=ENGINEERING, + import_all::Bool=false, + bank_transformers::Bool=true, + transformations::Vector{<:Function}=Vector{Function}([]), + build_multinetwork::Bool=false, + kron_reduced::Bool=true + )::Dict{String,Any} + if filetype == "dss" data_eng = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) @@ -12,7 +21,7 @@ function parse_file(io::IO, filetype::AbstractString="json"; data_model::DataMod end if data_model == MATHEMATICAL - return transform_data_model(data_eng; make_pu=true) + return transform_data_model(data_eng; make_pu=true, kron_reduced=kron_reduced, build_multinetwork=build_multinetwork) else return data_eng end @@ -20,7 +29,7 @@ function parse_file(io::IO, filetype::AbstractString="json"; data_model::DataMod pmd_data = parse_json(io; validate=false) if pmd_data["data_model"] != data_model && data_model == ENGINEERING - return transform_data_model(pmd_data) + return transform_data_model(pmd_data; kron_reduced=kron_reduced, build_multinetwork=build_multinetwork) else return pmd_data end @@ -41,11 +50,20 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=true)::Dict{String,Any} +function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=true, build_multinetwork::Bool=false)::Dict{String,Any} current_data_model = get(data, "data_model", MATHEMATICAL) if current_data_model == ENGINEERING - data_math = _map_eng2math(data; kron_reduced=kron_reduced) + if build_multinetwork && haskey(data, "time_series") + mn_data = _build_eng_multinetwork(data) + if ismultinetwork(mn_data) + data_math = _map_eng2math_multinetwork(mn_data; kron_reduced=kron_reduced) + else + data_math = _map_eng2math(mn_data; kron_reduced=kron_reduced) + end + else + data_math = _map_eng2math(data; kron_reduced=kron_reduced) + end correct_network_data!(data_math; make_pu=make_pu) @@ -64,24 +82,28 @@ end function correct_network_data!(data::Dict{String,Any}; make_pu::Bool=true) if get(data, "data_model", MATHEMATICAL) == ENGINEERING check_eng_data_model(data) - else #TODO if DSS is also a data_model enum, then use elseif instead + elseif get(data, "data_model", MATHEMATICAL) == MATHEMATICAL if make_pu make_per_unit!(data) #TODO system-wide vbase does not make sense anymore... #take highest vbase just so it does not break anything for now - data["basekv"] = maximum(bus["vbase"] for (_, bus) in data["bus"]) data["baseMVA"] = data["settings"]["sbase"]*data["settings"]["power_scale_factor"]/1E6 - _PM.check_connectivity(data) - _PM.correct_transformer_parameters!(data) - _PM.correct_voltage_angle_differences!(data) - _PM.correct_thermal_limits!(data) - _PM.correct_branch_directions!(data) - _PM.check_branch_loops(data) - _PM.correct_bus_types!(data) - _PM.correct_dcline_limits!(data) - _PM.correct_cost_functions!(data) - _PM.standardize_cost_terms!(data) + if ismultinetwork(data) + data["basekv"] = maximum(maximum(bus["vbase"] for (_, bus) in nw["bus"]) for nw in values(data["nw"])) + else ismultinetwork(data) + data["basekv"] = maximum(bus["vbase"] for (_, bus) in data["bus"]) + _PM.check_connectivity(data) + _PM.correct_transformer_parameters!(data) + _PM.correct_voltage_angle_differences!(data) + _PM.correct_thermal_limits!(data) + _PM.correct_branch_directions!(data) + _PM.check_branch_loops(data) + _PM.correct_bus_types!(data) + _PM.correct_dcline_limits!(data) + _PM.correct_cost_functions!(data) + _PM.standardize_cost_terms!(data) + end end end end diff --git a/src/io/dss_structs.jl b/src/io/dss_structs.jl index c562a68fa..379e9f4a9 100644 --- a/src/io/dss_structs.jl +++ b/src/io/dss_structs.jl @@ -1174,10 +1174,13 @@ function _create_loadshape(name::String=""; kwargs...) interval = get(kwargs, :interval, 1.0) end - npts = get(kwargs, :npts, 1) + pmult = get(kwargs, :pmult, Vector{Float64}([])) + qmult = get(kwargs, :qmult, pmult) - pmult = get(kwargs, :pmult, fill(1.0, npts))[1:npts] - qmult = get(kwargs, :qmult, fill(1.0, npts))[1:npts] + npts = get(kwargs, :npts, length(pmult) == 0 && length(qmult) == 0 ? 0 : minimum(Int[length(a) for a in [pmult, qmult] if length(a) > 0])) + + pmult = pmult[1:npts] + qmult = qmult[1:npts] hour = get(kwargs, :hour, collect(range(1.0, step=interval, length=npts)))[1:npts] diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 782638ccb..ee396dad0 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -138,17 +138,6 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An end end - if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) - eng_obj["time_series"] = Dict{String,Any}() - if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) - eng_obj["time_series"]["pg"] = "$(defaults[time_series])_p" - eng_obj["time_series"]["qg"] = "$(defaults[time_series])_q" - else - eng_obj["time_series"]["pg"] = defaults[time_series] - eng_obj["time_series"]["qg"] = defaults[time_series] - end - end - if import_all _import_all!(eng_obj, dss_obj) end @@ -285,7 +274,7 @@ function _dss2eng_reactor!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< end # TODO Check unit conversion on Gcap - Gcap = sum(defaults["kvar"]) / (nphases * 1e3 * (data_eng["settings"]["vbase"])^2) + Gcap = sum(defaults["kvar"]) / (nphases * 1e3 * (first(data_eng["settings"]["vbases_default"])[2])^2) eng_obj["bs"] = diagm(0=>fill(Gcap, nphases)) eng_obj["gs"] = zeros(size(eng_obj["bs"])) @@ -899,7 +888,7 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban source_bus = _parse_bus_id(defaults["bus1"])[1] data_eng["name"] = data_dss["circuit"] - + data_eng["settings"]["voltage_scale_factor"] = 1e3 data_eng["settings"]["power_scale_factor"] = 1e3 data_eng["settings"]["vbases_default"] = Dict(source_bus=>defaults["basekv"] / sqrt(3)) diff --git a/src/io/utils.jl b/src/io/utils.jl index e9119dbf4..495dec529 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -870,7 +870,7 @@ end "" -function _parse_dss_xycurve(dss_obj::Dict{String,<:Any})::Array{Vector{Real},2} +function _parse_dss_xycurve(dss_obj::Dict{String,<:Any}, data_dss::Dict{String,<:Any})::Array{Vector{Real},2} _apply_like!(dss_obj, data_dss, "xycurve") defaults = _apply_ordered_properties(_create_xycurve(id; _to_kwargs(dss_obj)...), dss_obj) @@ -881,16 +881,3 @@ function _parse_dss_xycurve(dss_obj::Dict{String,<:Any})::Array{Vector{Real},2} return Array{Vector{Real},2}([xarray, yarray]) end - - -"" -function _apply_time_series!(eng_obj::Dict{String,<:Any}, defaults::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, time_series::String="daily") - eng_obj["time_series"] = Dict{String,Any}() - if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) - eng_obj["time_series"]["pd_nom"] = "$(defaults[time_series])_p" - eng_obj["time_series"]["qd_nom"] = "$(defaults[time_series])_q" - else - eng_obj["time_series"]["pd_nom"] = defaults[time_series] - eng_obj["time_series"]["qd_nom"] = defaults[time_series] - end -end diff --git a/test/data/opendss/test2_master.dss b/test/data/opendss/test2_master.dss index 0c138a407..969893924 100644 --- a/test/data/opendss/test2_master.dss +++ b/test/data/opendss/test2_master.dss @@ -22,9 +22,9 @@ new line.l7 like=L2 bus1=_b2 bus2=b10 linecode=lc10 test_param=100.0 ! Loads New Load.ld1 phases=1 Bus1=b7.1.0 kv=0.208 status=variable model=1 conn=wye kW=3.89 pf=0.97 Vminpu=.88 New "Load.ld2" bus1=b9 phases=3 conn=Wye model=5 kV=24.9 kW=27 kvar=21 Vminpu=.85 -New "Load.ld3" bus1=b10 phases=3 conn=Wye model=2 kV=24.9 kW=405 kvar=315 Vminpu=.85 yearly=(sngfile=load_profile.sng) +New "Load.ld3" bus1=b10 phases=3 conn=Wye model=2 kV=24.9 kW=405 kvar=315 Vminpu=.85 yearly=ld3_yearly new load.ld4 like=ld2 bus1=b1 -New "Load.ld2" bus1=b9 phases=3 conn=Wye model=5 kV=24.9 kW=27 kvar=21 Vminpu=.85 daily=(file=load_profile.csv) +New "Load.ld2" bus1=b9 phases=3 conn=Wye model=5 kV=24.9 kW=27 kvar=21 Vminpu=.85 daily=ld2_daily ! Capacitors New "Capacitor.c1" bus1=b5 phases=3 kvar=[ 250] kv=20.0 @@ -77,3 +77,6 @@ new xycurve.test_curve3 csvfile=load_profile_pq.csv new xycurve.test_curve4 dblfile=load_profile_interval.dbl new xfmrcode.t1 phases=1 windings=2 conns=[wye,wye] kvs=[15, 15] kvas=[50000 50000] + +new loadshape.ld3_yearly pmult=(sngfile=load_profile.sng) +new loadshape.ld2_daily pmult=(file=load_profile.csv) \ No newline at end of file diff --git a/test/opendss.jl b/test/opendss.jl index 15cd0faa3..7f5c6bbea 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -22,16 +22,6 @@ @test all(haskey.([loadshapes["$i"] for i in [4, 6, 8]], "hour")) end - @testset "arrays from files" begin - dss = parse_dss("../test/data/opendss/test2_master.dss") - - @test isa(dss["load"]["ld3"]["yearly"], Vector) - @test isa(dss["load"]["ld2"]["daily"], Vector) - - @test length(dss["load"]["ld3"]["yearly"]) == 10 - @test length(dss["load"]["ld2"]["daily"]) == 10 - end - @testset "reverse polish notation" begin @test isapprox(PMD._parse_rpn("2 pi * 60 * .001 *"), 2 * pi * 60 * .001; atol=1e-12) @test isapprox(PMD._parse_rpn("(14.4 13.8 / sqr 300 *"), (14.4 / 13.8)^2 * 300; atol=1e-12) @@ -55,17 +45,6 @@ Memento.setlevel!(TESTLOG, "error") end - # TODO fix, do we support these previously erroring cases now? - # @testset "opendss parse load model errors" begin - # dss = parse_dss("../test/data/opendss/loadparser_error.dss") - # for (name, load) in dss["load"] - # _dss = deepcopy(dss) - # _dss["load"] = Dict{String,Any}(name => load) - - # @test_throws(TESTLOG, AssertionError, parse_opendss(_dss)) - # end - # end - @testset "opendss parse load model warnings" begin for model in [3, 4, 7, 8] dss = parse_dss("../test/data/opendss/loadparser_warn_model.dss") @@ -129,7 +108,7 @@ @test math["name"] == "test2" - @test length(math) == 19 + @test length(math) == 18 @test length(dss) == 16 for (key, len) in zip(["bus", "load", "shunt", "branch", "gen", "dcline", "transformer"], [33, 4, 5, 27, 4, 0, 10]) @@ -140,56 +119,22 @@ @test all(haskey(dss, key) for key in ["loadshape", "linecode", "buscoords", "options", "filename"]) end - # TODO fix, the way we calculate voltage bases changed @testset "opendss parse like" begin - # for i in [6, 3] - # basekv_bri = math["bus"][string(math["branch"]["$i"]["f_bus"])]["base_kv"] - # @test all(isapprox.(diag(math["branch"]["$i"]["b_fr"]), (3.4 * 2.0 + 1.6) / 3.0 * (basekv_bri^2 / math["baseMVA"] * 2.0 * pi * 60.0 / 1e9) / 2.0; atol=1e-6)) - # end - - # @test all(isapprox.(math["branch"]["9"]["br_r"].*(115/69)^2, diagm(0 => fill(6.3012e-8, 3)); atol=1e-12)) - # @test all(isapprox.(math["branch"]["9"]["br_x"].*(115/69)^2, diagm(0 => fill(6.3012e-7, 3)); atol=1e-12)) - for k in ["pd_nom", "qd_nom"] @test all(isapprox.(eng["load"]["ld2"][k], eng["load"]["ld4"][k]; atol=1e-12)) end - # TODO fix shunt_capacitor and shunt_reactor eng parameters - # @test all(isapprox.(eng["shunt_capacitor"]["c1"]["bs"], eng["shunt_capacitor"]["c3"]["bs"]; atol=1e-12)) - # @test all(isapprox.(eng["shunt_reactor"]["reactor3"]["bs"], eng["shunt_reactor"]["reactor4"]["bs"]; atol=1e-12)) - for (k,v) in eng["generator"]["g2"] if !(k in ["bus", "source_id", "dss"]) @test all(isapprox.(v, eng["generator"]["g3"][k]; atol=1e-12)) end end - - # for k in keys(math["branch"]["11"]) - # if !(k in ["f_bus", "t_bus", "index", "name", "linecode", "source_id", "t_connections", "f_connections"]) - # mult = 1.0 - # if k in ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to"] - # # compensation for the different voltage base - # basekv_br5 = math["bus"][string(math["branch"]["5"]["f_bus"])]["base_kv"] - # basekv_br2 = math["bus"][string(math["branch"]["2"]["f_bus"])]["base_kv"] - # zmult = (basekv_br5/basekv_br2)^2 - # mult = (k in ["br_r", "br_x"]) ? zmult : 1/zmult - # end - # @test all(isapprox.(math["branch"]["5"][k].*mult, math["branch"]["2"][k]; atol=1e-12)) - # end - # end end @testset "opendss parse length units" begin @test eng["line"]["l8"]["length"] == 1000.0 * 0.013516796 end - @testset "opendss parse xycurve" begin - @test eng["xycurve"]["test_curve1"]["interpolated_curve"](0.0226) == 4.52 - @test eng["xycurve"]["test_curve2"]["interpolated_curve"](2.5) == 2.5 - @test eng["xycurve"]["test_curve3"]["interpolated_curve"](0.55) == 5.5 - @test eng["xycurve"]["test_curve4"]["interpolated_curve"](0.55) == 5.5 - end - @testset "opendss parse switch length verify" begin @testset "branches with switches" begin @test eng["switch"]["_l4"]["length"] == 0.001 From 0eba10853960d6b8050f1b4ccb251372efa365d2 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 30 Apr 2020 16:14:43 -0600 Subject: [PATCH 178/224] WIP: updating for final data model --- src/data_model/components.jl | 496 ++++++++++++++++++++++++++--------- src/data_model/units.jl | 8 +- 2 files changed, 375 insertions(+), 129 deletions(-) diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 5ff4ce4f5..bee42bcbc 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -64,8 +64,8 @@ function Model(model_type::String=ENGINEERING; kwargs...)::Dict{String,Any} "settings" => Dict{String,Any}( "voltage_scale_factor" => get(kwargs, :voltage_scale_factor, 1e3), "power_scale_factor" => get(kwargs, :power_scale_factor, 1e3), - "vbase" => get(kwargs, :basekv, 1.0), - "sbase" => get(kwargs, :baseMVA, 1.0), + "vbases_default" => get(kwargs, :vbases_default, Dict{<:Any,<:Real}()), + "sbase_default" => get(kwargs, :sbase_default, 1.0), "base_frequency" => get(kwargs, :basefreq, 60.0), ) ) @@ -97,38 +97,79 @@ function Model(model_type::String=ENGINEERING; kwargs...)::Dict{String,Any} end -"" -function create_linecode(; kwargs...) - n_conductors = 0 - for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] - if haskey(kwargs, key) - n_conductors = size(kwargs[key])[1] +"creates a linecode with some defaults" +function create_linecode(; + rs::Union{Matrix{<:Real},Missing}=missing, + xs::Union{Matrix{<:Real},Missing}=missing, + g_fr::Union{Matrix{<:Real},Missing}=missing, + b_fr::Union{Matrix{<:Real},Missing}=missing, + g_to::Union{Matrix{<:Real},Missing}=missing, + b_to::Union{Matrix{<:Real},Missing}=missing, + cm_ub::Union{Vector{<:Real},Missing}=missing, + kwargs... + )::Dict{String,Any} + + shape = () + for v in [rs, xs, g_fr, g_to, b_fr, b_to] + if !ismissing(v) + shape = size(v) + break + end + end + + for v in [rs, xs, g_fr, g_to, b_fr, b_to] + if !ismissing(v) + @assert size(v) == shape "not all of the properties are the same size, aborting linecode creation" end end linecode = Dict{String,Any}( - "rs" => get(kwargs, :rs, fill(0.0, n_conductors, n_conductors)), - "xs" => get(kwargs, :xs, fill(0.0, n_conductors, n_conductors)), - "g_fr" => get(kwargs, :g_fr, fill(0.0, n_conductors, n_conductors)), - "b_fr" => get(kwargs, :b_fr, fill(0.0, n_conductors, n_conductors)), - "g_to" => get(kwargs, :g_to, fill(0.0, n_conductors, n_conductors)), - "b_to" => get(kwargs, :b_to, fill(0.0, n_conductors, n_conductors)), + "rs" => !ismissing(rs) ? rs : fill(0.01, shape...), + "xs" => !ismissing(xs) ? xs : fill(0.2, shape...), + "g_fr" => !ismissing(g_fr) ? g_fr : fill(0.0, shape...), + "b_fr" => !ismissing(b_fr) ? b_fr : fill(0.0, shape...), + "g_to" => !ismissing(g_to) ? g_to : fill(0.0, shape...), + "b_to" => !ismissing(b_to) ? b_to : fill(0.0, shape...), ) + if !ismissing(cm_ub) + linecode["cm_ub"] = cm_ub + end + _add_unused_kwargs!(linecode, kwargs) return linecode end -"" -function create_line(; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) - - @assert haskey(kwargs, :f_bus) && haskey(kwargs, :t_bus) "Line must at least have f_bus and t_bus specified" - - N = length(get(kwargs, :f_connections, collect(1:4))) - n_conductors = N +"Create a line with some default values" +function create_line(f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Vector{String}}, t_connections::Union{Vector{Int},Vector{String}}; + linecode::Any=missing, + rs::Union{Matrix{<:Real},Missing}=missing, + xs::Union{Matrix{<:Real},Missing}=missing, + g_fr::Union{Matrix{<:Real},Missing}=missing, + b_fr::Union{Matrix{<:Real},Missing}=missing, + g_to::Union{Matrix{<:Real},Missing}=missing, + b_to::Union{Matrix{<:Real},Missing}=missing, + length::Real=1.0, + cm_ub::Union{Vector{<:Real},Missing}=missing, + sm_ub::Union{Vector{<:Real},Missing}=missing, + vad_lb::Union{Vector{<:Real},Missing}=missing, + vad_ub::Union{Vector{<:Real},Missing}=missing, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + n_conductors = length(f_connections) + shape = (n_conductors, n_conductors) + + for v in [rs, xs, g_fr, b_fr, g_to, b_to, cm_ub, sm_ub, vad_lb, vad_ub] + if isa(v, Matrix) + @assert size(v) == shape + else + @assert length(v) == n_conductors + end + end # if no linecode, then populate loss parameters with zero if !haskey(kwargs, :linecode) @@ -140,38 +181,96 @@ function create_line(; kwargs...) end line = Dict{String,Any}( - "f_bus" => kwargs[:f_bus], - "t_bus" => kwargs[:t_bus], - "status" => get(kwargs, :status, ENABLED), - "f_connections" => get(kwargs, :f_connections, collect(1:4)), - "t_connections" => get(kwargs, :t_connections, collect(1:4)), - "angmin" => get(kwargs, :angmin, fill(-60/180*pi, N)), - "angmax" => get(kwargs, :angmax, fill( 60/180*pi, N)), - "length" => get(kwargs, :length, 1.0), - "rs" => get(kwargs, :rs, diagm(0 => fill(0.01, n_conductors))), - "xs" => get(kwargs, :xs, diagm(0 => fill(0.01, n_conductors))), - "g_fr" => get(kwargs, :g_fr, diagm(0 => fill(0.0, n_conductors))), - "b_fr" => get(kwargs, :b_fr, diagm(0 => fill(0.0, n_conductors))), - "g_to" => get(kwargs, :g_to, diagm(0 => fill(0.0, n_conductors))), - "b_to" => get(kwargs, :b_to, diagm(0 => fill(0.0, n_conductors))), + "f_bus" => f_bus, + "t_bus" => t_bus, + "status" => status, + "f_connections" => f_connections, + "t_connections" => t_connections, + "vad_lb" => !ismissing(vad_lb) ? vad_lb : fill(-60.0, n_conductors), + "vad_ub" => !ismissing(vad_lb) ? vad_lb : fill( 60.0, n_conductors), + "length" => length, ) + if !ismissing(linecode) + line["rs"] = !ismissing(rs) ? rs : fill(0.01, shape...) + line["rs"] => !ismissing(rs) ? rs : fill(0.01, shape...) + line["xs"] = !ismissing(xs) ? xs : fill(0.2, shape...) + line["g_fr"] = !ismissing(g_fr) ? g_fr : fill(0.0, shape...) + line["b_fr"] = !ismissing(b_fr) ? b_fr : fill(0.0, shape...) + line["g_to"] = !ismissing(g_to) ? g_to : fill(0.0, shape...) + line["b_to"] = !ismissing(b_to) ? b_to : fill(0.0, shape...) + else + line["linecode"] = linecode + for (k,v) in [("rs", rs), ("xs", xs), ("g_fr", g_fr), ("b_fr", b_fr), ("g_to", g_to), ("b_to", b_to)] + if !ismissing(v) + line[k] = v + end + end + end + + for (k,v) in [("cm_ub", "sm_ub")] + if !ismissing(v) + line[k] = v + end + end + _add_unused_kwargs!(line, kwargs) return line end +"creates a switch object with some defaults" +function create_switch(f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Vector{String}}, t_connections::Union{Vector{Int},Vector{String}}; + cm_ub::Union{Vector{<:Real},Missing}=missing, + sm_ub::Union{Vector{<:Real},Missing}=missing, + linecode::Any=missing, + rs::Union{Matrix{<:Real},Missing}=missing, + xs::Union{Matrix{<:Real},Missing}=missing, + dipatchable::Dispatchable=NO, + state::SwitchState=CLOSED, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + eng_obj = Dict{String,Any}( + "f_bus" => f_bus, + "t_bus" => t_bus, + "f_connections" => f_connections, + "t_connections" => t_connections, + "dispatchable" => dispatchable, + "state" => state, + "status" => status, + ) + + for (k,v) in [("cm_ub", cm_ub), ("sm_ub", sm_ub), ("linecode", linecode), ("rs", rs), ("xs", xs)] + if !ismissing(v) + eng_obj[k] = v + end + end + + _add_unused_kwargs!(eng_obj, kwargs) + + return eng_obj +end + + "creates a bus object with some defaults" -function create_bus(; status::Status=ENABLED, terminals::Union{Vector{Int},Vector{String}}=collect(1:4), grounded::Union{Vector{Int},Vector{String}}=Vector{Int}([]), rg::Vector{<:Real}=Vector{Float64}([]), xg::Vector{<:Real}=Vector{Float64}([]), kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) +function create_bus(; + status::Status=ENABLED, + terminals::Union{Vector{Int},Vector{String}}=Vector{Int}([]), + grounded::Union{Vector{Int},Vector{String}}=Vector{Int}([]), + rg::Vector{<:Real}=Vector{Float64}([]), + xg::Vector{<:Real}=Vector{Float64}([]), + kwargs... + )::Dict{String,Any} bus = Dict{String,Any}( - "status" => get(kwargs, :status, ENABLED), - "terminals" => get(kwargs, :terminals, collect(1:4)), - "grounded" => get(kwargs, :grounded, []), - "rg" => get(kwargs, :rg, Array{Float64, 1}()), - "xg" => get(kwargs, :xg, Array{Float64, 1}()), + "status" => status, + "terminals" => terminals, + "grounded" => grounded, + "rg" => rg, + "xg" => xg, ) _add_unused_kwargs!(bus, kwargs) @@ -181,25 +280,37 @@ end "creates a load object with some defaults" -function create_load(; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) +function create_load(bus::Any, connections::Union{Vector{Int},Vector{String}}; + configuration::ConnConfig=WYE, + model::LoadModel=POWER, + pd_nom::Union{Vector{<:Real},Missing}=missing, + qd_nom::Union{Vector{<:Real},Missing}=missing, + vm_nom::Real=1.0, + dispatchable::Dispatchable=NO, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + n_conductors = length(connections) + + for v in [pd_nom, qd_nom] + if !ismissing(v) + @assert length(v) == n_conductors + end + end load = Dict{String,Any}( - "status" => get(kwargs, :status, ENABLED), - "configuration" => get(kwargs, :configuration, WYE), - "model" => get(kwargs, :model, POWER), - "connections" => get(kwargs, :connections, get(kwargs, :configuration, WYE)==WYE ? [1, 2, 3, 4] : [1, 2, 3]), - "vnom" => get(kwargs, :vnom, 1.0) + "bus" => bus, + "connections" => connections, + "configuration" => configuration, + "model" => model, + "pd_nom" => !ismissing(pd_nom) ? pd_nom : fill(0.0, n_conductors), + "qd_nom" => !ismissing(qd_nom) ? qd_nom : fill(0.0, n_conductors), + "vm_nom" => vm_nom, + "dispatchable" => dispatchable, + "status" => status, ) - if load["model"]==POWER - load["pd"] = get(kwargs, :pd, fill(0.0, 3)) - load["qd"] = get(kwargs, :qd, fill(0.0, 3)) - else - load["pd_ref"] = get(kwargs, :pd_ref, fill(0.0, 3)) - load["qd_ref"] = get(kwargs, :qd_ref, fill(0.0, 3)) - end - _add_unused_kwargs!(load, kwargs) return load @@ -207,16 +318,36 @@ end "creates a generator object with some defaults" -function create_generator(; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) +function create_generator(bus::Any, connections::Union{Vector{Int},Vector{String}}; + configuration::ConnConfig=WYE, + pg::Union{Vector{<:Real},Missing}=missing, + qg::Union{Vector{<:Real},Missing}=missing, + vg::Union{Vector{<:Real},Missing}=missing, + pg_lb::Union{Vector{<:Real},Missing}=missing, + pg_ub::Union{Vector{<:Real},Missing}=missing, + qg_lb::Union{Vector{<:Real},Missing}=missing, + qg_ub::Union{Vector{<:Real},Missing}=missing, + control_mode::ControlMode=DROOP, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + n_conductors = length(connections) generator = Dict{String,Any}( - "status" => get(kwargs, :status, ENABLED), - "configuration" => get(kwargs, :configuration, WYE), - "cost" => get(kwargs, :cost, [1.0, 0.0]*1E-3), + "bus" => bus, + "connections" => connections, + "configuration" => configuration, + "control_mode" => control_mode, + "status" => status, ) - generator["connections"] = get(kwargs, :connections, generator["configuration"]==WYE ? [1, 2, 3, 4] : [1, 2, 3]) + for v in [("pg", pg), ("qg", qg), ("vg", vg), ("pg_lb", pg_lb), ("pg_ub", pg_ub), ("qg_lb", qg_lb), ("qg_ub", qg_ub)] + if !ismissing(v) + @assert length(v) == n_conductors + generator[k] = v + end + end _add_unused_kwargs!(generator, kwargs) @@ -224,82 +355,202 @@ function create_generator(; kwargs...) end -"creates a n-winding transformer object with some defaults" -function create_transformer(; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) +"creates transformer code with some defaults" +function create_xfmrcode(; + configurations::Union{Vector{ConnConfig},Missing}=missing, + xsc::Union{Vector{<:Real},Missing}=missing, + rw::Union{Vector{<:Real},Missing}=missing, + tm_nom::Union{Vector{<:Real},Missing}=missing, + tm_lb::Union{Vector{Vector{<:Real}},Missing}=missing, + tm_ub::Union{Vector{Vector{<:Real}},Missing}=missing, + tm_set::Union{Vector{Vector{<:Real}},Missing}=missing, + tm_fix::Union{Vector{Vector{<:Real}},Missing}=missing, + kwargs... +)::Dict{String,Any} + + eng_obj = Dict{String,Any}( + # TODO + ) + + return eng_obj +end - @assert haskey(kwargs, :bus) "bus must be defined at the very least" - n_windings = length(kwargs[:bus]) + +"creates a n-winding transformer object with some defaults" +function create_transformer(buses::Vector{Any}, connections::Vector{Union{Vector{Int},Vector{String}}}; + configurations::Union{Vector{ConnConfig},Missing}=missing, + xfmrcode::Any=missing, + xsc::Union{Vector{<:Real},Missing}=missing, + rw::Union{Vector{<:Real},Missing}=missing, + imag::Real=0.0, + noloadloss::Real=0.0, + tm_nom::Union{Vector{<:Real},Missing}=missing, + tm_lb::Union{Vector{Vector{<:Real}},Missing}=missing, + tm_ub::Union{Vector{Vector{<:Real}},Missing}=missing, + tm_set::Union{Vector{Vector{<:Real}},Missing}=missing, + tm_fix::Union{Vector{Vector{Bool}},Missing}=missing, + polarity::Union{Vector{Int},Missing}=missing, + vm_nom::Union{Vector{<:Real},Missing}=missing, + sm_nom::Union{Vector{<:Real},Missing}=missing, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + n_windings = length(buses) + n_conductors = length(connections[1]) transformer = Dict{String,Any}( - "status" => get(kwargs, :status, ENABLED), - "configuration" => get(kwargs, :configuration, fill(WYE, n_windings)), - "polarity" => get(kwargs, :polarity, fill(true, n_windings)), - "rs" => get(kwargs, :rs, zeros(n_windings)), - "xsc" => get(kwargs, :xsc, zeros(n_windings^2-n_windings)), - "noloadloss" => get(kwargs, :noloadloss, 0.0), - "imag" => get(kwargs, :imag, 0.0), - "tm_set" => get(kwargs, :tm, fill(fill(1.0, 3), n_windings)), - "tm_lb" => get(kwargs, :tm_min, fill(fill(0.9, 3), n_windings)), - "tm_ub" => get(kwargs, :tm_max, fill(fill(1.1, 3), n_windings)), - "tm_step" => get(kwargs, :tm_step, fill(fill(1/32, 3), n_windings)), - "tm_fix" => get(kwargs, :tm_fix, fill(ones(Bool, 3), n_windings)), - "connections" => get(kwargs, :connections, fill(collect(1:4), n_windings)), + "buses" => buses, + "connections" => connections, + "configurations" => !ismissing(configurations) ? configurations : fill(WYE, n_windings), + "xsc" => !ismissing(xsc) ? xsc : zeros(Int(n_windings * (n_windings-1)//2)), + "rw" => !ismissing(rw) ? rw : zeros(n_windings), + "imag" => imag, + "noloadloss" => noloadloss, + "tm_nom" => !ismissing(tm_nom) ? tm_nom : ones(n_windings), + "tm_set" => !ismissing(tm_set) ? tm_set : fill(fill(1.0, n_conductors), n_windings), + "tm_fix" => !ismissing(tm_fix) ? tm_fix : fill(fill(true, n_conductors), n_windings), + "polarity" => !ismissing(polarity) ? polarity : fill(1, n_windings), + "status" => status, ) + for (k,v) in [("tm_lb", tm_lb), ("tm_ub", tm_ub), ("vm_nom", vm_nom), ("sm_nom", sm_nom)] + if !ismissing(v) + transformer[k] = v + end + end + _add_unused_kwargs!(transformer, kwargs) return transformer end -"creates a shunt capacitor object with some defaults" -function create_shunt_capacitor(; kwargs...) - shunt_capacitor = Dict{String,Any}( - "status" => get(kwargs, :status, ENABLED), - "configuration" => get(kwargs, :configuration, WYE), - "connections" => get(kwargs, :connections, collect(1:4)), - "qd_ref" => get(kwargs, :qd_ref, fill(0.0, 3)), +"creates a generic shunt with some defaults" +function create_shunt(bus, connections; + gs::Union{Vector{<:Real},Missing}=missing, + bs::Union{Vector{<:Real},Missing}=missing, + model::ShuntModel=GENERIC, + dispatchable::Dispatchable=NO, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + n_conductors = length(connections) + + shunt = Dict{String,Any}( + "bus" => bus, + "connections" => connections, + "gs" => !ismissing(gs) ? gs : fill(0.0, n_conductors), + "bs" => !ismissing(bs) ? bs : fill(0.0, n_conductors), + "model" => model, + "dispatchable" => dispatchable, + "status" => status, ) - _add_unused_kwargs!(shunt_capacitor, kwargs) + _add_unused_kwargs!(shunt, kwargs) - return shunt_capacitor + return shunt end -"creates a generic shunt with some defaults" -function create_shunt(; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) +"creates a solar generator with some defaults" +function create_solar(bus::Any, connections::Union{Vector{Int},Vector{String}}; + configuration::ConnConfig=WYE, + pg_lb::Union{Vector{<:Real},Missing}=missing, + pg_ub::Union{Vector{<:Real},Missing}=missing, + qg_lb::Union{Vector{<:Real},Missing}=missing, + qg_ub::Union{Vector{<:Real},Missing}=missing, + pg::Union{Vector{<:Real},Missing}=missing, + qg::Union{Vector{<:Real},Missing}=missing, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + eng_obj = Dict{String,Any}( + "bus" => bus, + "connections" => connections, + "configuration" => configuration, + "status" => status + ) + # TODO + return eng_obj +end - N = length(get(kwargs, :connections, collect(1:4))) - shunt = Dict{String,Any}( - "status" => get(kwargs, :status, ENABLED), - "g_sh" => get(kwargs, :g_sh, fill(0.0, N, N)), - "b_sh" => get(kwargs, :b_sh, fill(0.0, N, N)), +"creates energy storage object with some defaults" +function create_solar(bus::Any, connections::Union{Vector{Int},Vector{String}}; + configuration::ConnConfig=WYE, + energy::Real=0.0, + energy_ub::Real=0.0, + charge_ub::Real=0.0, + discharge_ub::Real=0.0, + sm_ub::Union{Vector{<:Real},Missing}=missing, + cm_ub::Union{Vector{<:Real},Missing}=missing, + charge_efficiency::Real=0.9, + discharge_efficiency::Real=0.9, + qs_lb::Union{Vector{<:Real},Missing}=missing, + qs_ub::Union{Vector{<:Real},Missing}=missing, + rs::Union{Vector{<:Real},Missing}=missing, + xs::Union{Vector{<:Real},Missing}=missing, + pex::Real=0.0, + qex::Real=0.0, + ps::Union{Vector{<:Real},Missing}=missing, + qs::Union{Vector{<:Real},Missing}=missing, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + n_conductors = length(connections) + + eng_obj = Dict{String,Any}( + "bus" => bus, + "connections" => connections, + "configuration" => configuration, + "energy" => energy, + "energy_ub" => energy_ub, + "charge_ub" => charge_ub, + "discharge_ub" => discharge_ub, + "charge_efficiency" => charge_efficiency, + "discharge_efficiency" => discharge_efficiency, + "pex" => pex, + "qex" => qex, + "status" => status ) - _add_unused_kwargs!(shunt, kwargs) + # TODO - return shunt + return eng_obj end "creates a voltage source with some defaults" -function create_voltage_source(; kwargs...) - kwargs = Dict{Symbol,Any}(kwargs) +function create_voltage_source(bus, connections; + configuration::ConnConfig=WYE, + vm::Union{Vector{<:Real},Missing}=missing, + va::Union{Vector{<:Real},Missing}=missing, + rs::Union{Vector{<:Real},Missing}=missing, + xs::Union{Vector{<:Real},Missing}=missing, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + n_conductors = length(connections) voltage_source = Dict{String,Any}( + "bus" => bus, + "connections" => connections, + "configuration" => configuration, + "vm" => !ismissing(vm) ? vm : ones(n_conductors), + "va" => !ismissing(va) ? va : zeros(n_conductors), "status" => get(kwargs, :status, ENABLED), - "connections" => get(kwargs, :connections, collect(1:3)), ) - nphases = length(voltage_source["connections"]) - voltage_source["vm"] = get(kwargs, :vm, fill(1.0, nphases)) - voltage_source["va"] = deg2rad.(get(kwargs, :va, rad2deg.(_wrap_to_pi.([-2*pi/nphases*(i-1) for i in 1:nphases])))) - voltage_source["rs"] = fill(0.1, nphases, nphases) - voltage_source["xs"] = fill(0.0, nphases, nphases) + for (k,v) in [("rs", rs), ("xs", xs)] + if !ismissing(v) + voltage_source[k] = v + end + end _add_unused_kwargs!(voltage_source, kwargs) @@ -318,23 +569,18 @@ end # Data objects add_bus!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "bus", id, create_bus(; kwargs...)) add_linecode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "linecode", id, create_linecode(; kwargs...)) -# add_xfmrcode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "xfmrcode", id, create_xfmrcode(; kwargs...)) -# add_timeseries!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "timeseries", id, create_timeseries(; kwargs...)) +add_xfmrcode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "xfmrcode", id, create_xfmrcode(; kwargs...)) +# add_time_series!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "time_series", id, create_timeseries(; kwargs...)) # Edge objects -add_line!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "line", id, create_line(; f_bus=f_bus, t_bus=t_bus, kwargs...)) -add_transformer!(data_eng::Dict{String,<:Any}, id::Any, bus::Vector{<:Any}; kwargs...) = add_object!(data_eng, "transformer", id, create_transformer(; bus=bus, kwargs...)) -# add_switch!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "switch", id, create_switch(; f_bus=f_bus, t_bus=t_bus, kwargs...)) -# add_series_capacitor!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "series_capacitor", id, create_series_capacitor(; f_bus=f_bus, t_bus=t_bus, kwargs...)) -# add_line_reactor!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any; kwargs...) = add_object!(data_eng, "line_reactor", id, create_line_reactor(; f_bus=f_bus, t_bus=t_bus, kwargs...)) +add_line!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Vector{String}}, t_connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "line", id, create_line(f_bus, t_bus, f_connections, t_connections; kwargs...)) +add_transformer!(data_eng::Dict{String,<:Any}, id::Any, buses::Vector{<:Any}, connections::Vector{Union{Vector{Int},Vector{String}}}; kwargs...) = add_object!(data_eng, "transformer", id, create_transformer(buses, connections; kwargs...)) +add_switch!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Vector{String}}, t_connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "switch", id, create_switch(f_bus, t_bus, f_connections, t_connections; kwargs...)) # Node objects -add_load!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "load", id, create_load(; bus=bus, kwargs...)) -add_shunt_capacitor!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "shunt_capacitor", id, create_shunt_capacitor(; bus=bus, kwargs...)) -# add_shunt_reactor!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "shunt_reactor", id, create_shunt_reactor(; bus=bus, kwargs...)) -add_shunt!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "shunt", id, create_shunt(; bus=bus, kwargs...)) -add_voltage_source!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "voltage_source", id, create_voltage_source(; bus=bus, kwargs...)) -add_generator!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "generator", id, create_generator(; bus=bus, kwargs...)) -# add_storage!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "storage", id, create_storage(; bus=bus, kwargs...)) -# add_solar!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "solar", id, create_solar(; bus=bus, kwargs...)) -# add_wind!(data_eng::Dict{String,<:Any}, id::Any, bus::Any; kwargs...) = add_object!(data_eng, "wind", id, create_wind(; bus=bus, kwargs...)) +add_load!(data_eng::Dict{String,<:Any}, id::Any, bus::Any, connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "load", id, create_load(bus, connections; kwargs...)) +add_shunt!(data_eng::Dict{String,<:Any}, id::Any, bus::Any, connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "shunt", id, create_shunt(bus, connections; kwargs...)) +add_voltage_source!(data_eng::Dict{String,<:Any}, id::Any, bus::Any, connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "voltage_source", id, create_voltage_source(bus, connections; kwargs...)) +add_generator!(data_eng::Dict{String,<:Any}, id::Any, bus::Any, connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "generator", id, create_generator(bus, connections; kwargs...)) +add_storage!(data_eng::Dict{String,<:Any}, id::Any, bus::Any, connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "storage", id, create_storage(bus, connections; kwargs...)) +add_solar!(data_eng::Dict{String,<:Any}, id::Any, bus::Any, connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "solar", id, create_solar(bus, connections; kwargs...)) diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 252442228..444af683c 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -185,7 +185,7 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math; sbase:: end for (id, gen) in data_model["gen"] - _rebase_pu_generator!(gen, bus_vbase[string(gen["gen_bus"])], sbase, sbase_old, voltage_scale_factor, data_math) + _rebase_pu_generator!(gen, bus_vbase[string(gen["gen_bus"])], sbase, sbase_old, data_math) end for (id, storage) in data_model["storage"] @@ -312,8 +312,8 @@ end "per-unit conversion for generators" -function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, voltage_scale_factor::Real, data_model::Dict{String,<:Any}) - vbase_old = get(gen, "vbase", 1.0/voltage_scale_factor) +function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, data_model::Dict{String,<:Any}) + vbase_old = get(gen, "vbase", 1.0/data_model["settings"]["voltage_scale_factor"]) vbase_scale = vbase_old/vbase sbase_scale = sbase_old/sbase @@ -323,7 +323,7 @@ function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real # if not in per unit yet, the cost has is in $/MWh if !haskey(data_model["settings"], "sbase") - sbase_old_cost = 1E6/voltage_scale_factor + sbase_old_cost = 1E6/data_model["settings"]["power_scale_factor"] sbase_scale_cost = sbase_old_cost/sbase else sbase_scale_cost = sbase_scale From d5d69a3a87574510fa3b6083742815f84ef559ba Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 11:56:19 -0600 Subject: [PATCH 179/224] FIX: failing travis tests --- src/data_model/eng2math.jl | 2 +- test/opendss.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 37ce21f3c..01259bb9d 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -62,7 +62,7 @@ function _map_eng2math(data_eng; kron_reduced::Bool=true) "name" => get(data_eng, "name", ""), "per_unit" => get(data_eng, "per_unit", false), "data_model" => MATHEMATICAL, - "settings" => data_eng["settings"], + "settings" => deepcopy(data_eng["settings"]), ) #TODO the PM tests break for branches which are not of the size indicated by conductors; diff --git a/test/opendss.jl b/test/opendss.jl index 7f5c6bbea..3881f1153 100644 --- a/test/opendss.jl +++ b/test/opendss.jl @@ -125,7 +125,7 @@ end for (k,v) in eng["generator"]["g2"] - if !(k in ["bus", "source_id", "dss"]) + if isa(v, Real) @test all(isapprox.(v, eng["generator"]["g3"][k]; atol=1e-12)) end end From d3a1f85bf88cac26b5e2c25c18dc4ca0c9784c5a Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 11:57:36 -0600 Subject: [PATCH 180/224] FIX: create_storage accidentally named create_solar (duplicate) --- src/data_model/components.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data_model/components.jl b/src/data_model/components.jl index bee42bcbc..2e6b64714 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -479,7 +479,7 @@ end "creates energy storage object with some defaults" -function create_solar(bus::Any, connections::Union{Vector{Int},Vector{String}}; +function create_storage(bus::Any, connections::Union{Vector{Int},Vector{String}}; configuration::ConnConfig=WYE, energy::Real=0.0, energy_ub::Real=0.0, From 01b11198790b01fa5bf2e5d98393d0d1c75b329f Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 11:58:01 -0600 Subject: [PATCH 181/224] RM: ipynb checkpoints --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index dc17729f7..207dbe4cf 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ deps/deps.jl docs/build -Manifest.toml \ No newline at end of file +Manifest.toml + +.ipynb_checkpoints/ \ No newline at end of file From 22013d0cef227f823f194c5172bf5a23d6224995 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 11:58:26 -0600 Subject: [PATCH 182/224] RM: data_model_test.jl --- examples/data_model_test.jl | 138 ------------------------------------ 1 file changed, 138 deletions(-) delete mode 100644 examples/data_model_test.jl diff --git a/examples/data_model_test.jl b/examples/data_model_test.jl deleted file mode 100644 index 1e6c77a52..000000000 --- a/examples/data_model_test.jl +++ /dev/null @@ -1,138 +0,0 @@ -using PowerModelsDistribution -using Ipopt - -import LinearAlgebra: diagm - -function make_test_data_model() - data_model = Model() - - add_linecode!(data_model, "6_conds", rs=ones(6, 6), xs=ones(6, 6)) - add_linecode!(data_model, "4_conds", rs=ones(4, 4), xs=ones(4, 4)) - add_linecode!(data_model, "3_conds", rs=ones(3, 3), xs=ones(3, 3)) - add_linecode!(data_model, "2_conds", rs=ones(2, 2), xs=ones(2, 2)) - - # 3 phase + 3 neutral conductors - add_line!(data_model, "1", f_bus="1", t_bus="2", linecode="6_conds", length=1, f_connections=[1,2,3,4,4,4], t_connections=collect(1:6)) - add_line!(data_model, "2", f_bus="2", t_bus="3", linecode="6_conds", length=1, f_connections=[1,2,3,4,5,6], t_connections=[1,2,3,4,4,4]) - # 3 phase + 1 neutral conductors - add_line!(data_model, "3", f_bus="3", t_bus="4", linecode="4_conds", length=1.2) - # 3 phase conductors - add_line!(data_model, "4", f_bus="4", t_bus="5", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) - # 2 phase + 1 neutral conductors - add_line!(data_model, "5", f_bus="4", t_bus="6", linecode="3_conds", length=1.3, f_connections=[1,3,4], t_connections=[1,3,4]) - # 1 phase + 1 neutral conductors - add_line!(data_model, "6", f_bus="4", t_bus="7", linecode="2_conds", length=1.7, f_connections=[2,4], t_connections=[2,4]) - # 2 phase conductors - add_line!(data_model, "7", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) - for i in 8:1000 - add_line!(data_model, id="$i", f_bus="4", t_bus="8", linecode="2_conds", length=1.3, f_connections=[1,2], t_connections=[1,2]) - end - - add_bus!(data_model, "1", terminals=collect(1:4)) - add_bus!(data_model, "2", terminals=collect(1:6)) - add_bus!(data_model, "3", terminals=collect(1:4)) - add_bus!(data_model, "4") - add_bus!(data_model, "5", terminals=collect(1:4)) - add_bus!(data_model, "6", terminals=[1,3,4]) - add_bus!(data_model, "7", terminals=[2,4]) - add_bus!(data_model, "8", terminals=[1,2]) - add_bus!(data_model, "9", terminals=[1,2,3,4]) - add_bus!(data_model, "10", terminals=[1,2,3]) - - # - add_load!(data_model, "1", bus="7", connections=[2,4], pd=[1.0], qd=[1.0]) - add_load!(data_model, "2", bus="8", connections=[1,2], pd_ref=[1.0], qd_ref=[1.0], model=CURRENT, vnom=[230*sqrt(3)]) - add_load!(data_model, "3", bus="6", connections=[1,4], pd_ref=[1.0], qd_ref=[1.0], model=IMPEDANCE, vnom=[230]) - add_load!(data_model, "4", bus="6", connections=[3,4], pd_ref=[1.0], qd_ref=[1.0], model=EXPONENTIAL, vnom=[230], alpha=[1.2], beta=[1.5]) - add_load!(data_model, "5", bus="4", configuration=WYE, pd=fill(1.0, 3), qd=fill(1.0, 3)) - add_load!(data_model, "6", bus="4", configuration=WYE, pd=fill(1.0, 3), qd=fill(1.0, 3), model=CURRENT, vnom=fill(230, 3)) - add_load!(data_model, "7", bus="4", configuration=WYE, pd=fill(1.0, 3), qd=fill(1.0, 3), model=IMPEDANCE, vnom=fill(230, 3)) - add_load!(data_model, "8", bus="4", configuration=WYE, pd=fill(1.0, 3), qd=fill(1.0, 3), model=EXPONENTIAL, vnom=fill(230, 3), alpha=[2.1,2.4,2.5], beta=[2.1,2.4,2.5]) - add_load!(data_model, "9", bus="5", configuration=DELTA, pd=fill(1.0, 3), qd=fill(1.0, 3)) - - add_generator!(data_model, "1", bus="1", configuration=WYE) - - add_transformer_nw!(data_model, "1", bus=["5", "9", "10"], connections=[[1,2,3], [1,2,3,4], [1,2,3]], - vnom=[0.230, 0.230, 0.230], snom=[0.230, 0.230, 0.230], - configuration=[DELTA, WYE, DELTA], - xsc=[0.0, 0.0, 0.0], - rs=[0.0, 0.0, 1.0], - noloadloss=0.05, - imag=0.05, - ) - - add_capacitor!(data_model, "cap_3ph", bus="3", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3]) - add_capacitor!(data_model, "cap_3ph_delta", bus="4", vnom=0.230*sqrt(3), qd_ref=[1, 2, 3], configuration=DELTA, connections=[1,2,3]) - add_capacitor!(data_model, "cap_2ph_yg", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-grounded") - add_capacitor!(data_model, "cap_2ph_yfl", bus="6", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3], configuration="wye-floating") - add_capacitor!(data_model, "cap_2ph_y", bus="5", vnom=0.230*sqrt(3), qd_ref=[1, 2], connections=[1,3,4]) - - return data_model -end - - -function make_3wire_data_model() - - data_model = Model() - - add_linecode!(data_model, "3_conds", rs=diagm(0=>fill(1.0, 3)), xs=diagm(0=>fill(1.0, 3))) - - add_voltage_source!(data_model, "source", "sourcebus", connections=collect(1:3), - vm=[0.23, 0.23, 0.23], va=[0.0, -2*pi/3, 2*pi/3], - pg_max=fill(10E10,3), pg_min=fill(-10E10,3), qg_max=fill(10E10,3), qg_min=fill(-10E10,3), - rs=ones(3,3)/10 - ) - - # 3 phase conductors - add_line!(data_model, :test, "sourcebus", "tr_prim", linecode="3_conds", length=1.3, f_connections=collect(1:3), t_connections=collect(1:3)) - - add_bus!(data_model, "sourcebus", terminals=collect(1:3)) - add_bus!(data_model, "tr_prim", terminals=collect(1:4)) - add_bus!(data_model, "tr_sec", terminals=collect(1:4)) - #add!(data_model, "bus", create_bus("4", terminals=collect(1:4))) - - # add!(data_model, "transformer_nw", create_transformer("1", 3, ["2", "3", "4"], [[1,2,3], [1,2,3,4], [1,2,3]], - # [0.230, 0.230, 0.230], [0.230, 0.230, 0.230], - # configuration=[DELTA, WYE, DELTA], - # xsc=[0.0, 0.0, 0.0], - # rs=[0.0, 0.0, 0.0], - # loadloss=0.00, - # imag=0.00, - # )) - - add_transformer!(data_model, "1", ["tr_prim", "tr_sec"], connections=[[1,2,3,4], [1,2,3,4]], - vnom=[0.230, 0.230], snom=[0.230, 0.230], - configuration=[WYE, WYE], - xsc=[0.0], - rs=[0.0, 0.0], - noloadloss=0.00, - imag=0.00, - ) - - - - # - add_load!(data_model, "1", "tr_prim", connections=collect(1:4), pd=[1.0, 1.0, 1.0]) - - # add!(data_model, "generator", create_generator("1", "source", - # connections=[1, 2, 3, 4], - # pg_min=fill(-100, 3), - # pg_max=fill( 100, 3), - # qg_min=fill(-100, 3), - # qg_max=fill( 100, 3), - # )) - - #add!(data_model, "capacitor", create_capacitor(1, "tr_sec", 0.230*sqrt(3), qd_ref=[1.0, 1.0, 1.0]*1E-3, connections=[1,2,3], configuration=DELTA)) - - return data_model -end - - -data_model = make_3wire_data_model() -correct_network_data!(data_model) - -ipopt_solver = with_optimizer(Ipopt.Optimizer, tol=1e-6, print_level=3) - -result = run_mc_pf(data_model, ACPPowerModel, ipopt_solver) - - From 4a80b1c0dcb78bf34707c597aa73093179b0e78f Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 11:59:20 -0600 Subject: [PATCH 183/224] UPD: multinetwork creation pattern --- src/io/common.jl | 27 ++++++++++++--- src/io/opendss.jl | 87 +++++++++++++++-------------------------------- src/io/utils.jl | 17 ++++++++- 3 files changed, 66 insertions(+), 65 deletions(-) diff --git a/src/io/common.jl b/src/io/common.jl index 032299d73..b281253dd 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -10,18 +10,27 @@ function parse_file( bank_transformers::Bool=true, transformations::Vector{<:Function}=Vector{Function}([]), build_multinetwork::Bool=false, - kron_reduced::Bool=true + kron_reduced::Bool=true, + time_series::String="daily" )::Dict{String,Any} if filetype == "dss" - data_eng = PowerModelsDistribution.parse_opendss(io; import_all=import_all, bank_transformers=bank_transformers) + data_eng = PowerModelsDistribution.parse_opendss(io; + import_all=import_all, + bank_transformers=bank_transformers, + time_series=time_series + ) for transformation in transformations transformation(data_eng) end if data_model == MATHEMATICAL - return transform_data_model(data_eng; make_pu=true, kron_reduced=kron_reduced, build_multinetwork=build_multinetwork) + return transform_data_model(data_eng; + make_pu=true, + kron_reduced=kron_reduced, + build_multinetwork=build_multinetwork + ) else return data_eng end @@ -29,7 +38,10 @@ function parse_file( pmd_data = parse_json(io; validate=false) if pmd_data["data_model"] != data_model && data_model == ENGINEERING - return transform_data_model(pmd_data; kron_reduced=kron_reduced, build_multinetwork=build_multinetwork) + return transform_data_model(pmd_data; + kron_reduced=kron_reduced, + build_multinetwork=build_multinetwork + ) else return pmd_data end @@ -50,7 +62,12 @@ end "transforms model between engineering (high-level) and mathematical (low-level) models" -function transform_data_model(data::Dict{String,<:Any}; kron_reduced::Bool=true, make_pu::Bool=true, build_multinetwork::Bool=false)::Dict{String,Any} +function transform_data_model(data::Dict{String,<:Any}; + kron_reduced::Bool=true, + make_pu::Bool=true, + build_multinetwork::Bool=false + )::Dict{String,Any} + current_data_model = get(data, "data_model", MATHEMATICAL) if current_data_model == ENGINEERING diff --git a/src/io/opendss.jl b/src/io/opendss.jl index ee396dad0..fd6e3da34 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -79,7 +79,7 @@ Note that in the current feature set, fixed therefore equals constant # 7: Constant P and quadratic Q (i.e., fixed reactance) # 8: ZIP """ -function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, ground_terminal::Int=4, time_series::String="daily") +function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool, time_series::String="daily") for (id, dss_obj) in get(data_dss, "load", Dict{String,Any}()) _apply_like!(dss_obj, data_dss, "load") defaults = _apply_ordered_properties(_create_load(id; _to_kwargs(dss_obj)...), dss_obj) @@ -127,16 +127,7 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An eng_obj["pd_nom"] = fill(defaults["kw"]/nphases, nphases) eng_obj["qd_nom"] = fill(defaults["kvar"]/nphases, nphases) - if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) - eng_obj["time_series"] = Dict{String,Any}() - if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) - eng_obj["time_series"]["pd_nom"] = "$(defaults[time_series])_p" - eng_obj["time_series"]["qd_nom"] = "$(defaults[time_series])_q" - else - eng_obj["time_series"]["pd_nom"] = defaults[time_series] - eng_obj["time_series"]["qd_nom"] = defaults[time_series] - end - end + _build_time_series_reference!(eng_obj, dss_obj, data_dss, defaults, time_series, "pd_nom", "qd_nom") if import_all _import_all!(eng_obj, dss_obj) @@ -353,16 +344,7 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end - if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) - eng_obj["time_series"] = Dict{String,Any}() - if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) - eng_obj["time_series"]["pg"] = "$(defaults[time_series])_p" - eng_obj["time_series"]["qg"] = "$(defaults[time_series])_q" - else - eng_obj["time_series"]["pg"] = defaults[time_series] - eng_obj["time_series"]["qg"] = defaults[time_series] - end - end + _build_time_series_reference!(eng_obj, dss_obj, data_dss, defaults, time_series, "pg", "qg") if import_all _import_all!(eng_obj, dss_obj) @@ -405,16 +387,7 @@ function _dss2eng_vsource!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end - if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) - eng_obj["time_series"] = Dict{String,Any}() - if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) - eng_obj["time_series"]["pg"] = "$(defaults[time_series])_p" - eng_obj["time_series"]["qg"] = "$(defaults[time_series])_q" - else - eng_obj["time_series"]["pg"] = defaults[time_series] - eng_obj["time_series"]["qg"] = defaults[time_series] - end - end + _build_time_series_reference!(eng_obj, dss_obj, data_dss, defaults, time_series, "pg", "qg") if import_all _import_all!(eng_obj, dss_obj) @@ -790,16 +763,7 @@ function _dss2eng_pvsystem!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end - if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) - eng_obj["time_series"] = Dict{String,Any}() - if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) - eng_obj["time_series"]["pg"] = "$(defaults[time_series])_p" - eng_obj["time_series"]["qg"] = "$(defaults[time_series])_q" - else - eng_obj["time_series"]["pg"] = defaults[time_series] - eng_obj["time_series"]["qg"] = defaults[time_series] - end - end + _build_time_series_reference!(eng_obj, dss_obj, data_dss, defaults, time_series, "pg", "qg") if import_all _import_all!(eng_obj, dss_obj) @@ -844,16 +808,7 @@ function _dss2eng_storage!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,< _register_awaiting_ground!(data_eng["bus"][eng_obj["bus"]], eng_obj["connections"]) end - if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) - eng_obj["time_series"] = Dict{String,Any}() - if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) - eng_obj["time_series"]["ps"] = "$(defaults[time_series])_p" - eng_obj["time_series"]["qs"] = "$(defaults[time_series])_q" - else - eng_obj["time_series"]["ps"] = defaults[time_series] - eng_obj["time_series"]["qs"] = defaults[time_series] - end - end + _build_time_series_reference!(eng_obj, dss_obj, data_dss, defaults, time_series, "ps", "qs") if import_all _import_all!(eng_obj, dss_obj) @@ -865,15 +820,29 @@ end "Parses a DSS file into a PowerModels usable format" -function parse_opendss(io::IOStream; import_all::Bool=false, bank_transformers::Bool=true)::Dict +function parse_opendss(io::IOStream; + import_all::Bool=false, + bank_transformers::Bool=true, + time_series::String="daily" + )::Dict{String,Any} + data_dss = parse_dss(io) - return parse_opendss(data_dss; import_all=import_all, bank_transformers=bank_transformers) + return parse_opendss(data_dss; + import_all=import_all, + bank_transformers=bank_transformers, + time_series=time_series + ) end "Parses a Dict resulting from the parsing of a DSS file into a PowerModels usable format" -function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, bank_transformers::Bool=true)::Dict{String,Any} +function parse_opendss(data_dss::Dict{String,<:Any}; + import_all::Bool=false, + bank_transformers::Bool=true, + time_series::String="daily" + )::Dict{String,Any} + data_eng = Dict{String,Any}( "data_model" => ENGINEERING, "settings" => Dict{String,Any}(), @@ -914,12 +883,12 @@ function parse_opendss(data_dss::Dict{String,<:Any}; import_all::Bool=false, ban _dss2eng_reactor!(data_eng, data_dss, import_all) _dss2eng_loadshape!(data_eng, data_dss, import_all) - _dss2eng_load!(data_eng, data_dss, import_all) + _dss2eng_load!(data_eng, data_dss, import_all, time_series) - _dss2eng_vsource!(data_eng, data_dss, import_all) - _dss2eng_generator!(data_eng, data_dss, import_all) - _dss2eng_pvsystem!(data_eng, data_dss, import_all) - _dss2eng_storage!(data_eng, data_dss, import_all) + _dss2eng_vsource!(data_eng, data_dss, import_all, time_series) + _dss2eng_generator!(data_eng, data_dss, import_all, time_series) + _dss2eng_pvsystem!(data_eng, data_dss, import_all, time_series) + _dss2eng_storage!(data_eng, data_dss, import_all, time_series) _discover_terminals!(data_eng) diff --git a/src/io/utils.jl b/src/io/utils.jl index 495dec529..6e45ad3ba 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -870,7 +870,7 @@ end "" -function _parse_dss_xycurve(dss_obj::Dict{String,<:Any}, data_dss::Dict{String,<:Any})::Array{Vector{Real},2} +function _parse_dss_xycurve(dss_obj::Dict{String,<:Any}, id::Any, data_dss::Dict{String,<:Any})::Array{Vector{Real},2} _apply_like!(dss_obj, data_dss, "xycurve") defaults = _apply_ordered_properties(_create_xycurve(id; _to_kwargs(dss_obj)...), dss_obj) @@ -881,3 +881,18 @@ function _parse_dss_xycurve(dss_obj::Dict{String,<:Any}, data_dss::Dict{String,< return Array{Vector{Real},2}([xarray, yarray]) end + + +"helper function to properly reference time series variables from opendss" +function _build_time_series_reference!(eng_obj::Dict{String,<:Any}, dss_obj::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, defaults::Dict{String,<:Any}, time_series::String, active::String, reactive::String) + if haskey(dss_obj, time_series) && haskey(data_dss, "loadshape") && haskey(data_dss["loadshape"], defaults[time_series]) + eng_obj["time_series"] = Dict{String,Any}() + if _is_loadshape_split(data_dss["loadshape"][defaults[time_series]]) + eng_obj["time_series"][active] = "$(defaults[time_series])_p" + eng_obj["time_series"][reactive] = "$(defaults[time_series])_q" + else + eng_obj["time_series"][active] = defaults[time_series] + eng_obj["time_series"][reactive] = defaults[time_series] + end + end +end From 3f1e8b3033033e6f62d112d3f379e29941063ae0 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 12:00:17 -0600 Subject: [PATCH 184/224] REF: move multiple dispatch from run_{model_name} to run_mc_model --- src/prob/common.jl | 11 ++- src/prob/debug.jl | 16 +--- src/prob/mld.jl | 24 +---- src/prob/opf.jl | 18 ++-- src/prob/opf_bf.jl | 12 +-- src/prob/opf_bf_lm.jl | 14 +-- src/prob/opf_iv.jl | 12 +-- src/prob/opf_oltc.jl | 18 ++-- src/prob/pf.jl | 22 +---- src/prob/pf_bf.jl | 12 +-- src/prob/pf_iv.jl | 12 +-- src/prob/test.jl | 216 ------------------------------------------ 12 files changed, 47 insertions(+), 340 deletions(-) diff --git a/src/prob/common.jl b/src/prob/common.jl index ace3a0fba..580ffceb3 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -1,14 +1,21 @@ "alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" -function run_mc_model(data::Dict{String,<:Any}, model_type, solver, build_mc; ref_extensions=[], make_si=!get(data, "per_unit", false), kwargs...) +function run_mc_model(data::Dict{String,<:Any}, model_type::DataType, solver, build_mc::Function; ref_extensions::Vector{<:Function}=Vector{Function}([]), make_si=!get(data, "per_unit", false), kwargs...)::Dict{String,Any} + if get(data, "data_model", MATHEMATICAL) == ENGINEERING data_math = transform_data_model(data) result = _PM.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_transformer!, ref_extensions...], multiconductor=true, kwargs...) result["solution"] = transform_solution(result["solution"], data_math; make_si=make_si) - else + elseif get(data, "data_model", MATHEMATICAL) == MATHEMATICAL result = _PM.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_transformer!, ref_extensions...], multiconductor=true, kwargs...) end return result end + + +"alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" +function run_mc_model(file::String, model_type::DataType, solver, build_mc::Function; ref_extensions::Vector{<:Function}=Vector{Function}([]), kwargs...)::Dict{String,Any} + return run_mc_model(parse_file(file), model_type, solver, build_mc; ref_extensions=ref_extensions, kwargs...) +end diff --git a/src/prob/debug.jl b/src/prob/debug.jl index 1aea52bac..1b2806449 100644 --- a/src/prob/debug.jl +++ b/src/prob/debug.jl @@ -1,29 +1,17 @@ # These problem formulations are used to debug Distribution datasets # that do not converge using the standard formulations "OPF problem with slack power at every bus" -function run_mc_opf_pbs(data::Dict{String,Any}, model_type, solver; kwargs...) +function run_mc_opf_pbs(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_opf_pbs; kwargs...) end -"OPF problem with slack power at every bus" -function run_mc_opf_pbs(file::String, model_type, solver; kwargs...) - return run_mc_opf_pbs(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - "PF problem with slack power at every bus" -function run_mc_pf_pbs(data::Dict{String,Any}, model_type, solver; kwargs...) +function run_mc_pf_pbs(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_pf_pbs; kwargs...) end -"PF problem with slack power at every bus" -function run_mc_pf_pbs(file::String, model_type, solver; kwargs...) - return run_mc_pf_pbs(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - "OPF problem with slack power at every bus" function build_mc_opf_pbs(pm::_PM.AbstractPowerModel) variable_mc_bus_voltage(pm) diff --git a/src/prob/mld.jl b/src/prob/mld.jl index 1481f526f..c87cdcbd5 100644 --- a/src/prob/mld.jl +++ b/src/prob/mld.jl @@ -1,39 +1,21 @@ "Run load shedding problem with storage" -function run_mc_mld(data::Dict{String,Any}, model_type, solver; kwargs...) +function run_mc_mld(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_mld; kwargs...) end -"" -function run_mc_mld(file::String, model_type, solver; kwargs...) - return run_mc_mld(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - "Run Branch Flow Model Load Shedding Problem" -function run_mc_mld_bf(data::Dict{String,Any}, model_type, solver; kwargs...) +function run_mc_mld_bf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_mld_bf; kwargs...) end -"" -function run_mc_mld_bf(file::String, model_type, solver; kwargs...) - return run_mc_mld_bf(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - "Run unit commitment load shedding problem (!relaxed)" -function run_mc_mld_uc(data::Dict{String,Any}, model_type, solver; kwargs...) +function run_mc_mld_uc(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_mld_uc; kwargs...) end -"" -function run_mc_mld_uc(file::String, model_type, solver; kwargs...) - return run_mc_mld(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - "Load shedding problem including storage (snap-shot)" function build_mc_mld(pm::_PM.AbstractPowerModel) variable_mc_bus_voltage_indicator(pm; relax=true) diff --git a/src/prob/opf.jl b/src/prob/opf.jl index b309d3656..2208e55b3 100644 --- a/src/prob/opf.jl +++ b/src/prob/opf.jl @@ -1,22 +1,16 @@ -"" -function run_ac_mc_opf(file, solver; kwargs...) - return run_mc_opf(file, _PM.ACPPowerModel, solver; kwargs...) +"OPF with ACPPowerModel" +function run_ac_mc_opf(data::Union{Dict{String,<:Any},String}, solver; kwargs...) + return run_mc_opf(data, ACPPowerModel, solver; kwargs...) end -"" -function run_mc_opf(data::Dict{String,Any}, model_type, solver; kwargs...) +"Optimal Power Flow" +function run_mc_opf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_opf; kwargs...) end -"" -function run_mc_opf(file::String, model_type, solver; kwargs...) - return run_mc_opf(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - -"" +"Constructor for Optimal Power Flow" function build_mc_opf(pm::_PM.AbstractPowerModel) variable_mc_bus_voltage(pm) variable_mc_branch_power(pm) diff --git a/src/prob/opf_bf.jl b/src/prob/opf_bf.jl index 00a4de469..c5c19ccec 100644 --- a/src/prob/opf_bf.jl +++ b/src/prob/opf_bf.jl @@ -1,16 +1,10 @@ -"" -function run_mc_opf_bf(data::Dict{String,Any}, model_type, solver; kwargs...) +"branch flow opf" +function run_mc_opf_bf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_opf_bf; kwargs...) end -"" -function run_mc_opf_bf(file::String, model_type, solver; kwargs...) - return run_mc_opf_bf(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - -"" +"constructor for branch flow opf" function build_mc_opf_bf(pm::_PM.AbstractPowerModel) # Variables variable_mc_bus_voltage(pm) diff --git a/src/prob/opf_bf_lm.jl b/src/prob/opf_bf_lm.jl index f3524991f..53f6b06f1 100644 --- a/src/prob/opf_bf_lm.jl +++ b/src/prob/opf_bf_lm.jl @@ -2,19 +2,13 @@ # handled in variable_mc_load_setpoint and constraint_mc_load_setpoint. -"" -function run_mc_opf_bf_lm(data::Dict{String,Any}, model_constructor, solver; kwargs...) - return run_mc_model(data, model_constructor, solver, build_mc_opf_bf_lm; kwargs...) +"branch flow with loadmodels" +function run_mc_opf_bf_lm(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) + return run_mc_model(data, model_type, solver, build_mc_opf_bf_lm; kwargs...) end -"" -function run_mc_opf_bf_lm(file::String, model_constructor, solver; kwargs...) - return run_mc_opf_bf_lm(PowerModelsDistribution.parse_file(file), model_constructor, solver; kwargs...) -end - - -"" +"constructor for branch flow with loadmodels" function build_mc_opf_bf_lm(pm::_PM.AbstractPowerModel) # Variables variable_mc_bus_voltage(pm) diff --git a/src/prob/opf_iv.jl b/src/prob/opf_iv.jl index 91509852a..95d6313e8 100644 --- a/src/prob/opf_iv.jl +++ b/src/prob/opf_iv.jl @@ -1,16 +1,10 @@ -"" -function run_mc_opf_iv(data::Dict{String,Any}, model_type, solver; kwargs...) +"OPF in current-voltage variable space" +function run_mc_opf_iv(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_opf_iv; kwargs...) end -"" -function run_mc_opf_iv(file::String, model_type, solver; kwargs...) - return run_mc_opf_iv(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - -"" +"constructor for OPF in current-voltage variable space" function build_mc_opf_iv(pm::_PM.AbstractPowerModel) # Variables variable_mc_bus_voltage(pm) diff --git a/src/prob/opf_oltc.jl b/src/prob/opf_oltc.jl index 8fecf5bc8..82ec1c8b0 100644 --- a/src/prob/opf_oltc.jl +++ b/src/prob/opf_oltc.jl @@ -1,22 +1,16 @@ -"" -function run_ac_mc_opf_oltc(file, solver; kwargs...) - return run_mc_opf_oltc(file, _PM.ACPPowerModel, solver; kwargs...) +"Online tap changing OPF with ACPPowerModel" +function run_ac_mc_opf_oltc(data::Union{Dict{String,<:Any},String}, solver; kwargs...) + return run_mc_opf_oltc(data, ACPPowerModel, solver; kwargs...) end -"" -function run_mc_opf_oltc(data::Dict{String,Any}, model_type, solver; kwargs...) +"Online tap changing OPF" +function run_mc_opf_oltc(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_opf_oltc; kwargs...) end -"" -function run_mc_opf_oltc(file::String, model_type, solver; kwargs...) - return run_mc_opf_oltc(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - -"" +"constructor for Online tap changing OPF" function build_mc_opf_oltc(pm::_PM.AbstractPowerModel) variable_mc_bus_voltage(pm) variable_mc_branch_power(pm) diff --git a/src/prob/pf.jl b/src/prob/pf.jl index ba5cddf6d..44abf7985 100644 --- a/src/prob/pf.jl +++ b/src/prob/pf.jl @@ -1,28 +1,16 @@ -"" -function run_ac_mc_pf(data, solver; kwargs...) +"Power Flow problem with ACPPowerModel" +function run_ac_mc_pf(data::Union{Dict{String,<:Any},String}, solver; kwargs...) return run_mc_pf(data, _PM.ACPPowerModel, solver; kwargs...) end -"" -function run_dc_mc_pf(data, solver; kwargs...) - return run_mc_pf(data, _PM.DCPPowerModel, solver; kwargs...) -end - - -"" -function run_mc_pf(data::Dict{String,Any}, model_type, solver; kwargs...) +"Power Flow Problem" +function run_mc_pf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_pf; kwargs...) end -"" -function run_mc_pf(file::String, model_type, solver; kwargs...) - return run_mc_pf(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - -"" +"Constructor for Power Flow Problem" function build_mc_pf(pm::_PM.AbstractPowerModel) variable_mc_bus_voltage(pm; bounded=false) variable_mc_branch_power(pm; bounded=false) diff --git a/src/prob/pf_bf.jl b/src/prob/pf_bf.jl index 643218c90..3b793347b 100644 --- a/src/prob/pf_bf.jl +++ b/src/prob/pf_bf.jl @@ -1,16 +1,10 @@ -"" -function run_mc_pf_bf(data::Dict{String,Any}, model_type, solver; kwargs...) +"Branch Flow Power Flow Problem" +function run_mc_pf_bf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_pf_bf; kwargs...) end -"" -function run_mc_pf_bf(file::String, model_type, solver; kwargs...) - return run_mc_pf_bf(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - -"" +"Constructor for Branch Flow Power Flow" function build_mc_pf_bf(pm::_PM.AbstractPowerModel) # Variables variable_mc_bus_voltage(pm; bounded=false) diff --git a/src/prob/pf_iv.jl b/src/prob/pf_iv.jl index 2f9098e91..b0b55982f 100644 --- a/src/prob/pf_iv.jl +++ b/src/prob/pf_iv.jl @@ -1,16 +1,10 @@ -"" -function run_mc_pf_iv(data::Dict{String,Any}, model_type, solver; kwargs...) +"Power Flow in current-voltage variable space" +function run_mc_pf_iv(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_pf_iv; kwargs...) end -"" -function run_mc_pf_iv(file::String, model_type, solver; kwargs...) - return run_mc_pf_iv(PowerModelsDistribution.parse_file(file), model_type, solver; kwargs...) -end - - -"" +"Constructor for Power Flow in current-voltage variable space" function build_mc_pf_iv(pm::_PM.AbstractPowerModel) # Variables variable_mc_bus_voltage(pm, bounded = false) diff --git a/src/prob/test.jl b/src/prob/test.jl index 2937f8060..e69de29bb 100644 --- a/src/prob/test.jl +++ b/src/prob/test.jl @@ -1,216 +0,0 @@ -###### -# -# Formulations from PowerModels -# -###### - -"" -function _run_mc_ucopf(file, model_type::Type, solver; kwargs...) - return run_mc_model(file, model_type, solver, _build_mc_ucopf; kwargs...) -end - -"" -function _build_mc_ucopf(pm::_PM.AbstractPowerModel) - for (n, network) in nws(pm) - variable_mc_bus_voltage(pm, nw=n) - variable_mc_branch_power(pm, nw=n) - variable_mc_transformer_power(pm, nw=n) - - variable_mc_gen_indicator(pm, nw=n) - variable_mc_gen_power_setpoint_on_off(pm, nw=n) - - - constraint_mc_model_voltage(pm, nw=n) - - - - variable_mc_storage_power_on_off(pm, nw=n) - _PM.variable_storage_energy(pm, nw=n) - _PM.variable_storage_charge(pm, nw=n) - _PM.variable_storage_discharge(pm, nw=n) - _PM.variable_storage_indicator(pm, nw=n) - _PM.variable_storage_complementary_indicator(pm, nw=n) - - - for i in ids(pm, :ref_buses, nw=n) - constraint_mc_theta_ref(pm, i, nw=n) - end - - for i in ids(pm, :bus, nw=n) - constraint_mc_power_balance(pm, i, nw=n) - - end - - for i in ids(pm, :branch, nw=n) - constraint_mc_ohms_yt_from(pm, i, nw=n) - constraint_mc_ohms_yt_to(pm, i, nw=n) - - constraint_mc_voltage_angle_difference(pm, i, nw=n) - - constraint_mc_thermal_limit_from(pm, i, nw=n) - constraint_mc_thermal_limit_to(pm, i, nw=n) - end - - - for i in ids(pm, :transformer, nw=n) - constraint_mc_transformer_power(pm, i, nw=n) - end - - # for i in ids(pm, :dcline, nw=n) - # constraint_mc_dcline(pm, i, nw=n) - # end - - for i in ids(pm, :storage; nw=n) - # _PM.constraint_storage_state(pm, i; nw=n) - _PM.constraint_storage_complementarity_mi(pm, i; nw=n) - constraint_mc_storage_losses(pm, i; nw=n) - constraint_mc_storage_thermal_limit(pm, i; nw=n) - - constraint_mc_storage_on_off(pm, i; nw=n) - - end - end - - network_ids = sort(collect(nw_ids(pm))) - - n_1 = network_ids[1] - for i in ids(pm, :storage, nw=n_1) - _PM.constraint_storage_state(pm, i, nw=n_1) - end - - for n_2 in network_ids[2:end] - for i in ids(pm, :storage, nw=n_2) - _PM.constraint_storage_state(pm, i, n_1, n_2) - end - n_1 = n_2 - end - _PM.objective_min_fuel_cost(pm) -end - - -"" -function _run_mn_mc_opf(file, model_type::Type, optimizer; kwargs...) - return run_mc_model(file, model_type, optimizer, _build_mn_mc_opf; multinetwork=true, kwargs...) -end - -"" -function _build_mn_mc_opf(pm::_PM.AbstractPowerModel) - for (n, network) in nws(pm) - variable_mc_bus_voltage(pm, nw=n) - variable_mc_branch_power(pm, nw=n) - variable_mc_transformer_power(pm, nw=n) - variable_mc_gen_power_setpoint(pm, nw=n) - - constraint_mc_model_voltage(pm, nw=n) - - for i in ids(pm, :ref_buses, nw=n) - constraint_mc_theta_ref(pm, i, nw=n) - end - - for i in ids(pm, :bus, nw=n) - constraint_mc_power_balance(pm, i, nw=n) - - end - - for i in ids(pm, :branch, nw=n) - constraint_mc_ohms_yt_from(pm, i, nw=n) - constraint_mc_ohms_yt_to(pm, i, nw=n) - - constraint_mc_voltage_angle_difference(pm, i, nw=n) - - constraint_mc_thermal_limit_from(pm, i, nw=n) - constraint_mc_thermal_limit_to(pm, i, nw=n) - end - - - for i in ids(pm, :transformer, nw=n) - constraint_mc_transformer_power(pm, i, nw=n) - end - - # for i in ids(pm, :dcline, nw=n) - # constraint_mc_dcline(pm, i, nw=n) - # end - end - _PM.objective_min_fuel_cost(pm) -end - - -"" -function _run_mn_mc_opf_strg(file, model_type::Type, optimizer; kwargs...) - return run_mc_model(file, model_type, optimizer, _build_mn_mc_opf_strg; multinetwork=true, kwargs...) -end - -"warning: this model is not realistic or physically reasonable, it is only for test coverage" -function _build_mn_mc_opf_strg(pm::_PM.AbstractPowerModel) - - for (n, network) in nws(pm) - variable_mc_bus_voltage(pm, nw=n) - variable_mc_branch_power(pm, nw=n) - variable_mc_transformer_power(pm, nw=n) - variable_mc_gen_power_setpoint(pm, nw=n) - variable_mc_load_setpoint(pm, nw=n) - - variable_mc_storage_power(pm, nw=n) - - constraint_mc_model_voltage(pm, nw=n) - - for i in ids(pm, :ref_buses, nw=n) - constraint_mc_theta_ref(pm, i, nw=n) - end - - # generators should be constrained before KCL, or Pd/Qd undefined - for id in ids(pm, :gen) - constraint_mc_gen_setpoint(pm, id, nw=n) - end - - # loads should be constrained before KCL, or Pd/Qd undefined - for id in ids(pm, :load) - constraint_mc_load_setpoint(pm, id, nw=n) - end - - for i in ids(pm, :bus, nw=n) - constraint_mc_load_power_balance(pm, i, nw=n) - - end - - for i in ids(pm, :branch, nw=n) - constraint_mc_ohms_yt_from(pm, i, nw=n) - constraint_mc_ohms_yt_to(pm, i, nw=n) - - constraint_mc_voltage_angle_difference(pm, i, nw=n) - - constraint_mc_thermal_limit_from(pm, i, nw=n) - constraint_mc_thermal_limit_to(pm, i, nw=n) - end - - - for i in ids(pm, :transformer, nw=n) - constraint_mc_transformer_power(pm, i, nw=n) - end - - for i in ids(pm, :storage, nw=n) - _PM.constraint_storage_complementarity_nl(pm, i; nw=n) - constraint_mc_storage_losses(pm, i; nw=n) - constraint_mc_storage_thermal_limit(pm, i, nw=n) - end - - # for i in ids(pm, :dcline, nw=n) - # constraint_mc_dcline(pm, i, nw=n) - # end - - end - network_ids = sort(collect(nw_ids(pm))) - - n_1 = network_ids[1] - for i in ids(pm, :storage, nw=n_1) - _PM.constraint_storage_state(pm, i, nw=n_1) - end - - for n_2 in network_ids[2:end] - for i in ids(pm, :storage, nw=n_2) - _PM.constraint_storage_state(pm, i, n_1, n_2) - end - n_1 = n_2 - end - _PM.objective_min_fuel_cost(pm) -end From f06a6efb50878029e31854f8aab49157ff8b7fcd Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 12:00:31 -0600 Subject: [PATCH 185/224] UPD: data model documentation --- docs/src/data_model.md | 636 +++++++++++++++++++++++++++++------------ 1 file changed, 452 insertions(+), 184 deletions(-) diff --git a/docs/src/data_model.md b/docs/src/data_model.md index 751cfe81d..3b1f0b9f2 100644 --- a/docs/src/data_model.md +++ b/docs/src/data_model.md @@ -1,193 +1,461 @@ +# Data model -# Shared patterns +This document describes the `ENGINEERING` data model type in PowerModelsDistribution, which is transformed at runtime, or at the user's direction into a `MATHEMATICAL` data model for optimization. -- Each component has a unique (amonst components of the same type) identifier `id`. -- Everything is defined in SI units, except when a base is explicitly mentioned in the description. -- The default sometimes only mentions the default element, not the default value (e.g. `true` and not `fill(fill(true, 3), 3)`) +In this document, -# Data model +- `nphases` refers to the number of non-neutral, non-ground active phases connected to a component, +- `nconductors` refers to all active conductors connected to a component, _i.e._ `length(connections)`, and +- `nwindings` refers to the number of windings of a transformer. + +The data structure is in the following format + +```julia +Dict{String,Any}( + "data_model" => ENGINEERING, + "component_type" => Dict{Any,Dict{String,Any}}( + id => Dict{String,Any}( + "parameter" => value, + ... + ), + ... + ), + ... +) +``` + +Valid component types are those that are documented in the sectios below. Each component object is identified by an `id`, which can be any immutable value (`id <: Any`), but `id` does not appear inside of the component dictionary, and only appears as keys to the component dictionaries under each component type. Note that by default, if using one of the parsers, component `id` will be of type `String`, and if a model is created that uses `id` which is not type `String`, it will not be JSON serializable (_i.e._ the `id` will be converted to its `String` representation on export to JSON). + +Each edge or node component (_i.e._ all those that are not data objects or buses), are expected to have `status` fields to specify whether the component is active or disabled, `bus` or `f_bus` and `t_bus`, to specify the buses that are connected to the component, and `connections` or `f_connections` and `t_connections`, to specify the terminals of the buses that are actively connected in an ordered list. Terminals/connections can be any immutable value, as can bus ids. __NOTE__: `terminals`, `connections`, `f_connections`, and `t_connections`, can either be type `Vector{Int}` _or_ `Vector{String}`, as long as they are consistant across the whole model. + +Parameter values on components are expected to be specified in SI units by default (where applicable) in the engineering data model. Relevant expected units are noted in the sections below. It is possible for the user to select universal scalar factors for power and voltages. For example, if `power_scalar_factor` and `voltage_scalar_factor` are their default values given below, where units is listed as watt or var, real units will be kW and kvar. Where units are listed as volt, real units will be kV (multiplied by `vm_nom`, where that value exists). + +The Used column describes the situtations where certain parameters are used. "always" indicates those values are used in all contexts, `opf`, `mld`, or any other problem name abbreviation indicate they are used in particular for those problems. "solution" indicates that those parameters are outputs from the solvers. "multinetwork" indictes these values are only used to build multinetwork problems. + +Those parameters that have a default may be omitted by the user from the data model, they will be populated by the specified default values. + +Components that support "codes", such as lines, switches, and transformers, behave such that any property on said object that conflicts with a value in the code will override the value given in the code object. This is noted on each object where this is relevant. + +## Root-Level Properties + +At the root level of the data structure, the following fields can be found. + +| Name | Default | Type | Used | Description | +| ------------ | ------------- | -------------------- | ------ | ------------------------------------------------------------------------------------------------------------------- | +| `name` | | `String` | | Case name | +| `data_model` | `ENGINEERING` | `DataModel` | always | `ENGINEERING`, `MATHEMATICAL`, or `DSS`. Type of the data model (this document describes `data_model==ENGINEERING`) | +| `settings` | `Dict()` | `Dict{String,<:Any}` | always | Base settings for the data model, see Settings section below for details | + +## Settings (`settings`) + +At the root-level of the data model a `settings` dictionary object is expected, containing the following fields. + +| Name | Default | Type | Units | Used | Description | +| ---------------------- | ------- | ------------------ | ----- | ------ | ---------------------------------------------------------------------------- | +| `voltage_scale_factor` | `1e3` | `Real` | | always | Scalar multiplier for voltage values | +| `power_scale_factor` | `1e3` | `Real` | | always | Scalar multiplier for power values | +| `vbases_default` | | `Dict{<:Any,Real}` | | always | Instruction to set the vbase at a number of buses for non-dimensionalization | +| `sbase_default` | | `Real` | | always | Instruction to set the power base (baseMVA) for non-dimensionalization | +| `base_frequency` | `60.0` | `Real` | Hz | always | Frequency base, _i.e._ the base frequency of the whole circuit | + +The parameters `voltage_scale_factor` and `power_scale_factor`determine the base +for all voltage and power parameters in this data model. For example, + +- `voltage_scale_factor=1E3` and `vm_nom=4.0`: `vm_nom` is `4.0 kV`/`4.0E3 V`, +- `power_scale_factor=1E6` and `pd_nom=2.0`: `pd_nom` is `2.0 MW`/`2.0E6 W`, +- `power_scale_factor=1E6` and `qd_nom=5.0`: `qd_nom` is `5.0 MVAr`/`5.0E6 VAr`, + +where the mentioned fields `vm_nom`, `pd_nom` and `qd_nom` are sample voltage and power variables which are defined later. + +On the other hand,`vbase_default` and `sbase_default` provide default values for a 'per unit' conversion; these do not affect the interpretation of the parameters in this model, like the scale factors do. Note that `vbase_default` is a `Dict{Any,Real}`, with pairs of bus ids and voltage magnitude levels, since in per unit conversion, the voltage base can change from bus to bus. The power base is the same everywhere, and therefore `sbase_default` has a single value. -## Bus +## Buses (`bus`) -The data model below allows us to include buses of arbitrary many terminals (i.e., more than the usual four). This would be useful for +The data model below allows us to include buses of arbitrary many terminals (_i.e._, more than the usual four). This would be useful for - underground lines with multiple neutrals which are not joined at every bus; - distribution lines that carry several conventional lines in parallel (see for example the quad circuits in NEVTestCase). -| name | default | type | description | -| --------- | --------- | ------------- | --------------------------------------------- | -| terminals | [1,2,3,4] | Vector | | -| vm_max | / | Vector | maximum conductor-to-ground voltage magnitude | -| vm_min | / | Vector | minimum conductor-to-ground voltage magnitude | -| vm_cd_max | / | Vector{Tuple} | e.g. [(1,2,210)] means \|U1-U2\|>210 | -| vm_cd_min | / | Vector{Tuple} | e.g. [(1,2,230)] means \|U1-U2\|<230 | -| grounded | [] | Vector | a list of terminals which are grounded | -| rg | [] | Vector | resistance of each defined grounding | -| xg | [] | Vector | reactance of each defined grounding | - -The tricky part is how to encode bounds for these type of buses. The most general is defining a list of three-tupples. Take for example a typical bus in a three-phase, four-wire network, where `terminals=[a,b,c,n]`. Such a bus might have - -- phase-to-neutral bounds `vm_pn_max=250`, `vm_pn_min=210` -- and phase-to-phase bounds `vm_pp_max=440`, `vm_pp_min=360`. - -We can then define this equivalently as - -- `vm_cd_max = [(a,n,250), (b,n,250), (c,n,250), (a,b,440), (b,c,440), (c,a,440)]` -- `vm_cd_min = [(a,n,210), (b,n,210), (c,n,210), (a,b,360), (b,c,360), (c,a,360)]` - -Since this might be confusing for novice users, we also allow the user to define bounds through the following properties. - - -| name | default | type | description | -| --------- | ------- | ------ | --------------------------------------------------------- | -| id | / | | unique identifier | -| phases | [1,2,3] | Vector | | -| neutral | 4 | | maximum conductor-to-ground voltage magnitude | -| vm_pn_max | / | Real | maximum phase-to-neutral voltage magnitude for all phases | -| vm_pn_min | / | Real | minimum phase-to-neutral voltage magnitude for all phases | -| vm_pp_max | / | Real | maximum phase-to-phase voltage magnitude for all phases | -| vm_pp_min | / | Real | minimum phase-to-phase voltage magnitude for all phases | - -## Line - -This is a pi-model branch. A linecode implies `rs`, `xs`, `b_fr`, `b_to`, `g_fr` and `g_to`; when these properties are additionally specified, they overwrite the one supplied through the linecode. - -| name | default | type | description | -| ------------- | ------- | ---- | ------------------------------------------------------------------------ | -| id | / | | unique identifier | -| f_bus | / | | | -| f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | -| t_bus | / | | | -| t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | -| linecode | / | | a linecode | -| rs | / | | series resistance matrix, size of n_conductors x n_conductors | -| xs | / | | series reactance matrix, size of n_conductors x n_conductors | -| g_fr | / | | from-side conductance | -| b_fr | / | | from-side susceptance | -| g_to | / | | to-side conductance | -| b_to | / | | to-side susceptance | -| c_rating | / | | symmetrically applicable current rating | -| s_rating | / | | symmetrically applicable power rating | - -## Linecode - -- Should the linecode also include a `c_rating` and/`s_rating`? - -| name | default | type | description | -| ---- | ------- | ---- | ------------------------------------ | -| id | / | | unique identifier | -| rs | / | | series resistance matrix | -| xs | / | | series reactance matrix n_conductors | -| g_fr | / | | from-side conductance | -| b_fr | / | | from-side susceptance | -| g_to | / | | to-side conductance | -| b_to | / | | to-side susceptance | - -## Shunt - -| name | default | type | description | -| ----------- | ------- | ---- | ---------------------------------------------------------- | -| id | / | | unique identifier | -| bus | / | | | -| connections | / | | | -| g | / | | conductance, size should be \|connections\|x\|connections\ | -| b | / | | susceptance, size should be \|connections\|x\|connections\ | - -## Capacitor - -| name | default | type | description | -| ----------- | ------- | ---- | ---------------------------------------------------------- | -| id | / | | unique identifier | -| bus | / | | | -| connections | / | | | -| qd_ref | / | | conductance, size should be \|connections\|x\|connections\ | -| vnom | / | | conductance, size should be \|connections\|x\|connections\ | - -## Load - -| name | default | type | description | -| ------------- | ------- | ------------ | --------------------------------------------------------------- | -| id | / | | unique identifier | -| bus | / | | | -| connections | / | | | -| configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | -| model | / | | indicates the type of voltage-dependency | - -### `model=constant_power` - -| name | default | type | description | -| ---- | ------- | ------------ | ----------- | -| pd | / | Vector{Real} | | -| qd | / | Vector{Real} | | - -### `model=constant_current/impedance` - -| name | default | type | description | -| ------ | ------- | ------------ | ----------- | -| pd_ref | / | Vector{Real} | | -| qd_ref | / | Vector{Real} | | -| vnom | / | Real | | - -### `model=exponential` - -| name | default | type | description | -| ------ | ------- | ------------ | ----------- | -| pd_ref | / | Vector{Real} | | -| qd_ref | / | Vector{Real} | | -| vnom | / | Real | | -| exp_p | / | Vector{Real} | | -| exp_q | / | Vector{Real} | | - -## Generator - -| name | default | type | description | -| ------------- | ------- | ------------ | --------------------------------------------------------------- | -| id | / | | unique identifier | -| bus | / | | | -| connections | / | | | -| configuration | / | {wye, delta} | if wye-connected, the last connection will indicate the neutral | -| pg_min | / | | lower bound on active power generation per phase | -| pg_max | / | | upper bound on active power generation per phase | -| qg_min | / | | lower bound on reactive power generation per phase | -| qg_max | / | | upper bound on reactive power generation per phase | - -## Assymetric, Lossless, Two-Winding (AL2W) Transformer - -These are transformers are assymetric (A), lossless (L) and two-winding (2W). Assymetric refers to the fact that the secondary is always has a `wye` configuration, whilst the primary can be `delta`. - -| name | default | type | description | -| ------------- | ---------------------- | ------------ | ------------------------------------------------------------------------ | -| id | / | | unique identifier | -| n_phases | size(rs)[1] | Int>0 | number of phases | -| f_bus | / | | | -| f_connections | / | | indicates for each conductor, to which terminal of the f_bus it connects | -| t_bus | / | | | -| t_connections | / | | indicates for each conductor, to which terminal of the t_bus it connects | -| configuration | / | {wye, delta} | for the from-side; the to-side is always connected in wye | -| tm_nom | / | Real | nominal tap ratio for the transformer | -| tm_max | / | Vector | maximum tap ratio for each phase (base=`tm_nom`) | -| tm_min | / | Vector | minimum tap ratio for each phase (base=`tm_nom`) | -| tm_set | fill(1.0, `n_phases`) | Vector | set tap ratio for each phase (base=`tm_nom`) | -| tm_fix | fill(true, `n_phases`) | Vector | indicates for each phase whether the tap ratio is fixed | - -TODO: add tm stuff - -## Transformer - -These are n-winding, n-phase, lossy transformers. Note that most properties are now Vectors (or Vectors of Vectors), indexed over the windings. - -| name | default | type | description | -| -------------- | ----------- | -------------------- | -------------------------------------------------------------------------------------------------------------- | -| id | / | | unique identifier | -| n_phases | size(rs)[1] | Int>0 | number of phases | -| n_windings | size(rs)[1] | Int>0 | number of windings | -| bus | / | Vector | list of bus for each winding | -| connections | | Vector{Vector} | list of connection for each winding | -| configurations | | Vector{{wye, delta}} | list of configuration for each winding | -| xsc | 0.0 | Vector | list of short-circuit reactances between each pair of windings; enter as a list of the upper-triangle elements | -| rs | 0.0 | Vector | list of the winding resistance for each winding | -| tm_nom | / | Vector{Real} | nominal tap ratio for the transformer | -| tm_max | / | Vector{Vector} | maximum tap ratio for each winding and phase (base=`tm_nom`) | -| tm_min | / | Vector{Vector} | minimum tap ratio for for each winding and phase (base=`tm_nom`) | -| tm_set | 1.0 | Vector{Vector} | set tap ratio for each winding and phase (base=`tm_nom`) | -| tm_fix | true | Vector{Vector} | indicates for each winding and phase whether the tap ratio is fixed | +| Name | Default | Type | Units | Used | Description | +| ------------- | ----------- | ----------------------------- | ------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `terminals` | `[1,2,3,4]` | `Vector{Int}||Vector{String}` | | always | Terminals for which the bus has active connections; __NOTE__: type can be either `Vector{Int}` or `Vector{String}`, but has to be consistent across all fields referring to terminals | +| `vm_lb` | | `Vector{Real}` | volt | opf | Minimum conductor-to-ground voltage magnitude, `size=nphases` | +| `vm_ub` | | `Vector{Real}` | volt | opf | Maximum conductor-to-ground voltage magnitude, `size=nphases` | +| `vm_pair_ub` | | `Vector{Tuple}` | | opf | _e.g._ `[(1,2,210)]` means \|U1-U2\|>210 | +| `vm_pair_lb` | | `Vector{Tuple}` | | opf | _e.g._ `[(1,2,230)]` means \|U1-U2\|<230 | +| `grounded` | `[]` | `Vector{Int}` | | always | List of terminals which are grounded | +| `rg` | `[]` | `Vector{Real}` | | always | Resistance of each defined grounding, `size=length(grounded)` | +| `xg` | `[]` | `Vector{Real}` | | always | Reactance of each defined grounding, `size=length(grounded)` | +| `vm` | | `Vector{Real}` | volt | always | Voltage magnitude at bus. If set, voltage magnitude at bus is fixed | +| `va` | | `Vector{Real}` | degree | always | Voltage angle at bus. If set, voltage angle at bus is fixed | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | +| `time_series` | | `Dict{String,Any}` | | multinetwork | Dictionary containing time series parameters. | + +Each terminal `c` of the bus has an associated complex voltage phasor `v[c]`. There are two types of voltage magnitude bounds. The first type bounds the voltage magnitude of each `v[c]` individually, + +- `lb <= |v[c]| <= ub` + +However, especially in four-wire networks, bounds are more naturally imposed on the difference of two terminal voltages instead, e.g. for terminals `c` and `d`, + +- `lb <= |v[c]-v[d]| <= ub` + +This is why we introduce the fields `vm_pair_lb` and `vm_pair_ub`, which define bounds for pairs of terminals, + +- $\forall$ `(c,d,lb)` $\in$ `vm_pair_lb`: `|v[c]-v[d]| >= lb` +- $\forall$ `(c,d,ub)` $\in$ `vm_pair_ub`: `|v[c]-v[d]| <= ub` + +Finally, we give an example of how grounding impedances should be entered. If terminal `4` is grounded through an impedance `Z=1+j2`, we write + +- `grounded=[4]`, `rg=[1]`, `xg=[2]` + +### Special Case: three-phase bus + +For three-phase buses, instead of specifying bounds explicitly for each pair of windings, often we want to specify 'phase-to-phase', 'phase-to-neutral' and 'neutral-to-ground' bounds. This can be done conveniently with a number of additional fields. First, `phases` is a list of the phase terminals, and `neutral` designates a single terminal to be the neutral. + +- The bounds `vm_pn_lb` and `vm_pn_ub` specify the same lower and upper bound for the magnitude of the difference of each phase terminal and the neutral. +- The bounds `vm_pp_lb` and `vm_pp_ub` specify the same lower and upper bound for the magnitude of the difference of all phase terminals. +- `vm_ng_ub` specifies an upper bound for the neutral terminal, the lower bound is typically zero. + +If all of these are specified, these bounds also imply valid bounds for the individual voltage magnitudes, + +- $\forall$ `c` $\in$ `phases`: `vm_pn_lb - vm_ng_ub <= |v[c]| <= vm_pn_ub + vm_ng_ub` +- `0 <= |v[neutral]|<= vm_ng_ub` + +Instead of defining the bounds directly, they can be specified through an associated voltage zone. + +| Name | Default | Type | Units | Used | Description | +| -------------- | ------- | ----------------------------- | ----- | ------ | ------------------------------------------------------------- | +| `phases` | | `Vector{Int}||Vector{String}` | | always | Identifies the terminal that represents the neutral conductor | +| `neutral` | | `Int||String` | | always | Identifies the terminal that represents the neutral conductor | +| `vm_pn_lb` | | `Real` | | opf | Minimum phase-to-neutral voltage magnitude for all phases | +| `vm_pn_ub` | | `Real` | | opf | Maximum phase-to-neutral voltage magnitude for all phases | +| `vm_pp_lb` | | `Real` | | opf | Minimum phase-to-phase voltage magnitude for all phases | +| `vm_pp_ub` | | `Real` | | opf | Maximum phase-to-phase voltage magnitude for all phases | +| `vm_ng_ub` | | `Real` | | opf | Maximum neutral-to-ground voltage magnitude | + +## Edge Objects + +These objects represent edges on the power grid and therefore require `f_bus` and `t_bus` (or `buses` in the case of transformers), and `f_connections` and `t_connections` (or `connections` in the case of transformers). + +### Lines (`line`) + +This is a pi-model branch. When a `linecode` is given, and any of `rs`, `xs`, `b_fr`, `b_to`, `g_fr` or `g_to` are specified, any of those overwrite the values on the linecode. + +| Name | Default | Type | Units | Used | Description | +| --------------- | --------------------------------- | ----------------------------- | ---------------- | ------ | ------------------------------------------------------------------------------------ | +| `f_bus` | | `Any` | | always | id of from-side bus connection | +| `t_bus` | | `Any` | | always | id of to-side bus connection | +| `f_connections` | | `Vector{Int}||Vector{String}` | | always | Indicates for each conductor, to which terminal of the `f_bus` it connects | +| `t_connections` | | `Vector{Int}||Vector{String}` | | always | Indicates for each conductor, to which terminal of the `t_bus` it connects | +| `linecode` | | `Any` | | always | id of an associated linecode | +| `rs` | | `Matrix{Real}` | ohm/meter | always | Series resistance matrix, `size=(nconductors,nconductors)` | +| `xs` | | `Matrix{Real}` | ohm/meter | always | Series reactance matrix, `size=(nconductors,nconductors)` | +| `g_fr` | `zeros(nconductors, nconductors)` | `Matrix{Real}` | siemens/meter/Hz | always | From-side conductance, `size=(nconductors,nconductors)` | +| `b_fr` | `zeros(nconductors, nconductors)` | `Matrix{Real}` | siemens/meter/Hz | always | From-side susceptance, `size=(nconductors,nconductors)` | +| `g_to` | `zeros(nconductors, nconductors)` | `Matrix{Real}` | siemens/meter/Hz | always | To-side conductance, `size=(nconductors,nconductors)` | +| `b_to` | `zeros(nconductors, nconductors)` | `Matrix{Real}` | siemens/meter/Hz | always | To-side susceptance, `size=(nconductors,nconductors)` | +| `length` | `1.0` | `Real` | meter | always | Length of the line | +| `cm_ub` | | `Vector{Real}` | amp | opf | Symmetrically applicable current rating, `size=nconductors` | +| `sm_ub` | | `Vector{Real}` | watt | opf | Symmetrically applicable power rating, `size=nconductors` | +| `vad_lb` | | `Vector{Real}` | degree | opf | Voltage angle difference lower bound | +| `vad_ub` | | `Vector{Real}` | degree | opf | Voltage angle difference upper bound | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | + +### Transformers (`transformer`) + +These are n-winding (`nwinding`), n-phase (`nphase`), lossy transformers. Note that most properties are now Vectors (or Vectors of Vectors), indexed over the windings. + +| Name | Default | Type | Units | Used | Description | +| ---------------- | ------------------------------------ | ------------------------------------- | ----------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `buses` | | `Vector{Any}` | | always | List of bus for each winding, `size=nwindings` | +| `connections` | | `Vector{Vector{Int}||Vector{String}}` | | always | List of connection for each winding, `size=((nconductors),nwindings)` | +| `configurations` | `fill(WYE, nwindings)` | `Vector{ConnConfig}` | | always | `WYE` or `DELTA`. List of configuration for each winding, `size=nwindings` | +| `xfmrcode` | | `Any` | | always | id of | +| `xsc` | `zeros(nwindings*(nwindings-1)/2)` | `Vector{Real}` | `sm_nom[1]` | always | List of short-circuit reactances between each pair of windings, relative to the VA rating of the first winding; enter as a list of the upper-triangle elements | +| `rw` | `zeros(nwindings)` | `Vector{Real}` | `sm_nom[1]` | always | Active power lost due to resistance of each winding, relative to the VA rating of each winding winding | +| `imag` | `0.0` | `Real` | `sm_nom[1]` | always | Total no-load reactive power drawn by the transformer, relative to VA rating of the first winding | +| `noloadloss` | `0.0` | `Real` | `sm_nom[1]` | always | Total no-load active power drawn by the transformer, relative to VA rating of the first winding | +| `tm_nom` | `ones(nwindings)` | `Vector{Real}` | | always | Nominal tap ratio for the transformer, `size=nwindings` (multiplier) | +| `tm_ub` | | `Vector{Vector{Real}}` | | opf | Maximum tap ratio for each winding and phase, `size=((nphases),nwindings)` (base=`tm_nom`) | +| `tm_lb` | | `Vector{Vector{Real}}` | | opf | Minimum tap ratio for for each winding and phase, `size=((nphases),nwindings)` (base=`tm_nom`) | +| `tm_set` | `fill(fill(1.0,nphases),nwindings)` | `Vector{Vector{Real}}` | | always | Set tap ratio for each winding and phase, `size=((nphases),nwindings)` (base=`tm_nom`) | +| `tm_fix` | `fill(fill(true,nphases),nwindings)` | `Vector{Vector{Bool}}` | | oltc | Indicates for each winding and phase whether the tap ratio is fixed, `size=((nphases),nwindings)` | +| `polarity` | `fill(1,nwindings)` | `Vector{Int}` | | always | | +| `vm_nom` | | `Vector{Real}` | volt | always | | +| `sm_nom` | | `Vector{Real}` | watt | always | | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | + +#### Assymetric, Lossless, Two-Winding (AL2W) Transformers (`transformer`) + +Special case of the Generic transformer, which is still a `transformer` object, but has a simplified method for its definition. These are transformers are assymetric (A), lossless (L) and two-winding (2W). Assymetric refers to the fact that the secondary is always has a `WYE` configuration, whilst the primary can be `DELTA`. The table below indicates alternate, more simple ways to specify the special case of an AL2W Transformer. `xsc` and `rw` cannot be specified for an AL2W transformer, because it is lossless. To use this definition format, all of `f_bus`, `t_bus`, `f_connections`, `t_connections`, and `configuration` must be used, and none of `buses`, `connections`, `configurations` may be used. `xfmrcode` is ignored for this component. + +| Name | Default | Type | Units | Used | Description | +| --------------- | -------------------- | ----------------------------- | ----- | ------ | ----------------------------------------------------------------------------------------------------------- | +| `f_bus` | | `Any` | | always | Alternative way to specify `buses`, requires both `f_bus` and `t_bus` | +| `t_bus` | | `Any` | | always | Alternative way to specify `buses`, requires both `f_bus` and `t_bus` | +| `f_connections` | | `Vector{Int}||Vector{String}` | | always | Alternative way to specify `connections`, requires both `f_connections` and `t_connections`, `size=nphases` | +| `t_connections` | | `Vector{Int}||Vector{String}` | | always | Alternative way to specify `connections`, requires both `f_connections` and `t_connections`, `size=nphases` | +| `configuration` | `WYE` | `ConnConfig` | | always | `WYE` or `DELTA`. Alternative way to specify the from-side configuration, to-side is always `WYE` | +| `tm_nom` | `1.0` | `Real` | | always | Nominal tap ratio for the transformer (multiplier) | +| `tm_ub` | | `Vector{Real}` | | opf | Maximum tap ratio for each phase (base=`tm_nom`), `size=nphases` | +| `tm_lb` | | `Vector{Real}` | | opf | Minimum tap ratio for each phase (base=`tm_nom`), `size=nphases` | +| `tm_set` | `fill(1.0,nphases)` | `Vector{Real}` | | always | Set tap ratio for each phase (base=`tm_nom`), `size=nphases` | +| `tm_fix` | `fill(true,nphases)` | `Vector{Bool}` | | oltc | Indicates for each phase whether the tap ratio is fixed, `size=nphases` | + +### Switches (`switch`) + +Switches without `rs`, `xs` or a linecode (conductance/susceptance not considered), defined the switch will be treated as lossless. If lossy parameters are defined, `switch` objects will be decomposed into virtual `branch` & `bus`, and an ideal `switch`. + +| Name | Default | Type | Units | Used | Description | +| --------------- | ------------------------ | ----------------------------- | ----- | ------ | ------------------------------------------------------------------------------------------------ | +| `f_bus` | | `Any` | | always | id of from-side bus connection | +| `t_bus` | | `Any` | | always | id of to-side bus connection | +| `f_connections` | | `Vector{Int}||Vector{String}` | | always | Indicates for each conductor, to which terminal of the `f_bus` it connects | +| `t_connections` | | `Vector{Int}||Vector{String}` | | always | Indicates for each conductor, to which terminal of the `t_bus` it connects | +| `cm_ub` | | `Vector{Real}` | amp | opf | Symmetrically applicable current rating | +| `sm_ub` | | `Vector{Real}` | watt | opf | Symmetrically applicable power rating | +| `linecode` | | `Any` | | always | id of an associated linecode, does not take into account conductance/susceptance | +| `rs` | `zeros(nphases,nphases)` | `Matrix{Real}` | ohm | always | Series resistance matrix, `size=(nphases,nphases)` | +| `xs` | `zeros(nphases,nphases)` | `Matrix{Real}` | ohm | always | Series reactance matrix, `size=(nphases,nphases)` | +| `dispatchable` | `NO` | `Dispatchable` | | | `NO` or `YES`, indicates whether switch state can be changed in a switching optimization problem | +| `state` | `CLOSED` | `SwitchState` | | always | `CLOSED`: closed or `OPEN`: open, to indicate state of switch | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | + +## Node Objects + +These are objects that have single bus connections. Every object will have at least `bus`, `connections`, and `status`. + +### Shunts (`shunt`) + +| Name | Default | Type | Units | Used | Description | +| -------------- | --------- | ----------------------------- | ------- | ------------ | ------------------------------------------------------------------------------------------------------------------------- | +| `bus` | | `Any` | | always | id of bus connection | +| `connections` | | `Vector{Int}||Vector{String}` | | always | Ordered list of connected conductors, `size=nconductors` | +| `gs` | | `Matrix{Real}` | siemens | always | Conductance, `size=(nconductors,nconductors)` | +| `bs` | | `Matrix{Real}` | siemens | always | Susceptance, `size=(nconductors,nconductors)` | +| `model` | `GENERIC` | `ShuntModel` | | | `GENERIC`, `CAPACITOR`, or `REACTOR`. Indicates the type of shunt which may be necessary for transient stability analysis | +| `dispatchable` | `NO` | `Dispatchable` | | mld | `NO` or `YES`, indicates whether a shunt can be shed | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | +| `time_series` | | `Dict{String,Any}` | | multinetwork | Dictionary containing time series parameters. | + +### Loads (`load`) + +| Name | Default | Type | Units | Used | Description | +| --------------- | --------- | ----------------------------- | ----- | -------------- | -------------------------------------------------------------------------------------------------- | +| `bus` | | `Any` | | always | id of bus connection | +| `connections` | | `Vector{Int}||Vector{String}` | | always | Ordered list of connected conductors, `size=nconductors` | +| `configuration` | `WYE` | `ConnConfig` | | always | `WYE` or `DELTA`. If `WYE`, `connections[end]=neutral` | +| `model` | `POWER` | `LoadModel` | | always | `POWER`, `IMPEDANCE`, `CURRENT`, `EXPONENTIAL`, or `ZIP`. Indicates the type of voltage-dependency | +| `pd_nom` | | `Vector{Real}` | watt | always | Nominal active load, with respect to `vm_nom`, `size=nphases` | +| `qd_nom` | | `Vector{Real}` | var | always | Nominal reactive load, with respect to `vm_nom`, `size=nphases` | +| `vm_nom` | | `Real` | volt | `model!=POWER` | Nominal voltage (multiplier) | +| `dispatchable` | `NO` | `Dispatchable` | | mld | `NO` or `YES`, indicates whether a load can be shed | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | +| `time_series` | | `Dict{String,Any}` | | multinetwork | Dictionary containing time series parameters. | + +Multi-phase loads define a number of individual loads connected between two terminals each. How they are connected, is defined both by `configuration` and `connections`. The table below indicates the value of `configuration` and lengths of the other properties for a consistent definition, + +| `configuration` | `|connections|` | `|pd_nom|=|qd_nom|=|pd_exp|=...` | +| --------------- | --------------- | -------------------------------- | +| `DELTA` | `2` | `1` | +| `DELTA` | `3` | `3` | +| `WYE` | `2` | `1` | +| `WYE` | `3` | `2` | +| `WYE` | `N` | `N-1` | + +Note that for delta loads, only 2 and 3 connections are allowed. Each individual load `i` is connected between two terminals, exposed to a voltage magnitude `v[i]`, which leads to a consumption `pd[i]+j*qd[i]`. The `model` then defines the relationship between these quantities, + +| model | `pd[i]/pd_nom[i]=` | `qd[i]/qd_nom[i]=` | +| ----------- | ------------------ | ------------------ | +| `POWER` | `1` | `1` | +| `CURRENT` | `(v[i]/vm_nom)` | `(v[i]/vm_nom)` | +| `IMPEDANCE` | `(v[i]/vm_nom)^2` | `(v[i]/vm_nom)^2` | + +Two more model types are supported, which need additional fields and are defined below. + +#### `model == EXPONENTIAL` + +- `(pd[i]/pd_nom[i]) = (v[i]/vm_nom)^pd_exp[i]` +- `(qd[i]/qd_nom[i]) = (v[i]/vm_nom)^qd_exp[i]` + +| Name | Default | Type | Units | Used | Description | +| -------- | ------- | ------ | ----- | -------------------- | ----------- | +| `pd_exp` | | `Real` | | `model==EXPONENTIAL` | | +| `qd_exp` | | `Real` | | `model==EXPONENTIAL` | | + +#### `model == ZIP` + +- `(pd[i]/pd_nom) = pd_cz[i]*(v[i]/vm_nom)^2 + pd_ci[i]*(v[i]/vm_nom) + pd_cp[i]` +- `(qd[i]/qd_nom) = qd_cz[i]*(v[i]/vm_nom)^2 + qd_ci[i]*(v[i]/vm_nom) + qd_cp[i]` + +| Name | Default | Type | Units | Used | Description | +| -------- | ------- | ------ | ----- | ------------ | ---------------------------- | +| `vm_nom` | | `Real` | volt | `model==ZIP` | Nominal voltage (multiplier) | +| `pd_cz` | | `Real` | | `model==ZIP` | | +| `pd_ci` | | `Real` | | `model==ZIP` | | +| `pd_cp` | | `Real` | | `model==ZIP` | | +| `qd_cz` | | `Real` | | `model==ZIP` | | +| `qd_ci` | | `Real` | | `model==ZIP` | | +| `qd_cp` | | `Real` | | `model==ZIP` | | + +### Generators `generator` (or Synchronous Machines `synchronous_machine`?) + +| Name | Default | Type | Units | Used | Description | +| --------------- | -------------------- | ----------------------------- | ----- | --------------------------- | ------------------------------------------------------------------------------------ | +| `bus` | | `Any` | | always | id of bus connection | +| `connections` | | `Vector{Int}||Vector{String}` | | always | Ordered list of connected conductors, `size=nconductors` | +| `configuration` | `WYE` | `ConnConfig` | | always | `WYE` or `DELTA`. If `WYE`, `connections[end]=neutral` | +| `vg` | | `Vector{Real}` | volt | `control_mode==ISOCHRONOUS` | Voltage magnitude setpoint | +| `pg_lb` | `zeros(nphases)` | `Vector{Real}` | watt | opf | Lower bound on active power generation per phase, `size=nphases` | +| `pg_ub` | `fill(Inf, nphases)` | `Vector{Real}` | watt | opf | Upper bound on active power generation per phase, `size=nphases` | +| `qg_lb` | `-pg_ub` | `Vector{Real}` | var | opf | Lower bound on reactive power generation per phase, `size=nphases` | +| `qg_ub` | `pg_ub` | `Vector{Real}` | var | opf | Upper bound on reactive power generation per phase, `size=nphases` | +| `pg` | | `Vector{Real}` | watt | solution | Present active power generation per phase, `size=nphases` | +| `qg` | | `Vector{Real}` | var | solution | Present reactive power generation per phase, `size=nphases` | +| `control_mode` | `DROOP` | `ControlMode` | | | `DROOP` or `ISOCHRONOUS` | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | +| `time_series` | | `Dict{String,Any}` | | multinetwork | Dictionary containing time series parameters. | + +#### `generator` Cost Model + +The generator cost model is currently specified by the following fields. + +| Name | Default | Type | Units | Used | Description | +| -------------------- | ----------------- | -------------- | ----- | ---- | --------------------------------------------------------- | +| `cost_pg_model` | `2` | `Int` | | opf | Cost model type, `1` = piecewise-linear, `2` = polynomial | +| `cost_pg_parameters` | `[0.0, 1.0, 0.0]` | `Vector{Real}` | $/MVA | opf | Cost model polynomial | + +### Photovoltaic Systems (`solar`) + +TODO Loss model, Inverter settings, Irradiance Model + +| Name | Default | Type | Units | Used | Description | +| --------------- | --------- | ----------------------------- | ----- | ------------ | ------------------------------------------------------------------------------------ | +| `bus` | | `Any` | | always | id of bus connection | +| `connections` | | `Vector{Int}||Vector{String}` | | always | Ordered list of connected conductors, `size=nconductors` | +| `configuration` | `WYE` | `ConnConfig` | | always | `WYE` or `DELTA`. If `WYE`, `connections[end]=neutral` | +| `pg_lb` | | `Vector{Real}` | watt | opf | Lower bound on active power generation per phase, `size=nphases` | +| `pg_ub` | | `Vector{Real}` | watt | opf | Upper bound on active power generation per phase, `size=nphases` | +| `qg_lb` | | `Vector{Real}` | var | opf | Lower bound on reactive power generation per phase, `size=nphases` | +| `qg_ub` | | `Vector{Real}` | var | opf | Upper bound on reactive power generation per phase, `size=nphases` | +| `pg` | | `Vector{Real}` | watt | solution | Present active power generation per phase, `size=nphases` | +| `qg` | | `Vector{Real}` | var | solution | Present reactive power generation per phase, `size=nphases` | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | +| `time_series` | | `Dict{String,Any}` | | multinetwork | Dictionary containing time series parameters. | + +#### `solar` Cost Model + +The cost model for a photovoltaic system currently matches that of generators. + +| Name | Default | Type | Units | Used | Description | +| -------------------- | ----------------- | -------------- | ----- | ---- | --------------------------------------------------------- | +| `cost_pg_model` | `2` | `Int` | | opf | Cost model type, `1` = piecewise-linear, `2` = polynomial | +| `cost_pg_parameters` | `[0.0, 1.0, 0.0]` | `Vector{Real}` | $/MVA | opf | Cost model polynomial | + +### Wind Turbine Systems (`wind`) + +Wind turbine systems are most closely approximated by induction machines, also known as asynchornous machines. These are not currently supported, but there is plans to support them in the future. + +### Storage (`storage`) + +A storage object is a flexible component that can represent a variety of energy storage objects, like Li-ion batteries, hydrogen fuel cells, flywheels, etc. + +- How to include the inverter model for this? Similar issue as for a PV generator + +| Name | Default | Type | Units | Used | Description | +| ---------------------- | --------- | ----------------------------- | ------- | ------------ | ------------------------------------------------------------------------------------ | +| `bus` | | `Any` | | always | id of bus connection | +| `connections` | | `Vector{Int}||Vector{String}` | | always | Ordered list of connected conductors, `size=nconductors` | +| `configuration` | `WYE` | `ConnConfig` | | always | `WYE` or `DELTA`. If `WYE`, `connections[end]=neutral` | +| `energy` | | `Real` | watt-hr | always | Stored energy | +| `energy_ub` | | `Real` | | opf | maximum energy rating | +| `charge_ub` | | `Real` | | opf | maximum charge rating | +| `discharge_ub` | | `Real` | | opf | maximum discharge rating | +| `sm_ub` | | `Vector{Real}` | watt | opf | Power rating, `size=nphases` | +| `cm_ub` | | `Vector{Real}` | amp | opf | Current rating, `size=nphases` | +| `charge_efficiency` | | `Real` | percent | always | charging efficiency (losses) | +| `discharge_efficiency` | | `Real` | percent | always | disharging efficiency (losses) | +| `qs_ub` | | `Vector{Real}` | | opf | Maximum reactive power injection, `size=nphases` | +| `qs_lb` | | `Vector{Real}` | | opf | Minimum reactive power injection, `size=nphases` | +| `rs` | | `Vector{Real}` | ohm | always | converter resistance | +| `xs` | | `Vector{Real}` | ohm | always | converter reactance | +| `pex` | | `Real` | | always | Total active power standby exogenous flow (loss) | +| `qex` | | `Real` | | always | Total reactive power standby exogenous flow (loss) | +| `ps` | | `Vector{Real}` | watt | solution | Present active power injection | +| `qs` | | `Vector{Real}` | var | solution | Present reactive power injection | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | +| `time_series` | | `Dict{String,Any}` | | multinetwork | Dictionary containing time series parameters. | + +### Voltage Sources (`voltage_source`) + +A voltage source is a source of power at a set voltage magnitude and angle connected to a slack bus. If `rs` or `xs` are not specified, the voltage source is assumed to be lossless, otherwise virtual `branch` and `bus` will be created in the mathematical model to represent the internal losses of the voltage source. + +| Name | Default | Type | Units | Used | Description | +| --------------- | -------------------------------- | ----------------------------- | ------ | ------------ | ------------------------------------------------------------------------------------ | +| `bus` | | `Any` | | always | id of bus connection | +| `connections` | | `Vector{Int}||Vector{String}` | | always | Ordered list of connected conductors, `size=nconductors` | +| `configuration` | `WYE` | `ConnConfig` | | always | `WYE` or `DELTA`. If `WYE`, `connections[end]=neutral` | +| `vm` | `ones(nphases)` | `Vector{Real}` | volt | always | Voltage magnitude set at slack bus, `size=nphases` | +| `va` | `zeros(nphases)` | `Real` | degree | always | Voltage angle offsets at slack bus, applies symmetrically to each phase angle | +| `rs` | `zeros(nconductors,nconductors)` | `Matrix{Real}` | ohm | always | Internal series resistance of voltage source, `size=(nconductors,nconductors)` | +| `xs` | `zeros(nconductors,nconductors)` | `Matrix{Real}` | ohm | always | Internal series reactance of voltage soure, `size=(nconductors,nconductors)` | +| `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | +| `time_series` | | `Dict{String,Any}` | | multinetwork | Dictionary containing time series parameters. | + +## Data Objects (codes, time series, etc.) + +These objects are referenced by node and edge objects, but are not part of the network themselves, only containing data. + +### Linecodes (`linecode`) + +Linecodes are easy ways to specify properties common to multiple lines. + +| Name | Default | Type | Units | Used | Description | +| ------- | -------------------------------- | -------------- | ---------------- | ------ | ------------------------------------------------------- | +| `rs` | | `Matrix{Real}` | ohm/meter | always | Series resistance, `size=(nconductors,nconductors)` | +| `xs` | | `Matrix{Real}` | ohm/meter | always | Series reactance, `size=(nconductors,nconductors)` | +| `g_fr` | `zeros(nconductors,nconductors)` | `Matrix{Real}` | siemens/meter/Hz | always | From-side conductance, `size=(nconductors,nconductors)` | +| `b_fr` | `zeros(nconductors,nconductors)` | `Matrix{Real}` | siemens/meter/Hz | always | From-side susceptance, `size=(nconductors,nconductors)` | +| `g_to` | `zeros(nconductors,nconductors)` | `Matrix{Real}` | siemens/meter/Hz | always | To-side conductance, `size=(nconductors,nconductors)` | +| `b_to` | `zeros(nconductors,nconductors)` | `Matrix{Real}` | siemens/meter/Hz | always | To-side susceptance, `size=(nconductors,nconductors)` | +| `cm_ub` | | `Vector{Real}` | ampere | always | maximum current per conductor, symmetrically applicable | + +### Transformer Codes (`xfmrcode`) + +Transformer codes are easy ways to specify properties common to multiple transformers + +| Name | Default | Type | Units | Used | Description | +| ---------------- | -------------------------------------- | ---------------------- | ----- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `configurations` | `fill(WYE, nwindings)` | `Vector{ConnConfig}` | | always | `WYE` or `DELTA`. List of configuration for each winding, `size=nwindings` | +| `xsc` | `[0.0]` | `Vector{Real}` | ohm | always | List of short-circuit reactances between each pair of windings; enter as a list of the upper-triangle elements, `size=(nwindings == 2 ? 1 : 3)` | +| `rw` | `zeros(nwindings)` | `Vector{Real}` | ohm | always | List of the winding resistance for each winding, `size=nwindings` | +| `tm_nom` | `ones(nwindings)` | `Vector{Real}` | | always | Nominal tap ratio for the transformer, `size=nwindings` (multiplier) | +| `tm_ub` | | `Vector{Vector{Real}}` | | opf | Maximum tap ratio for each winding and phase, `size=((nphases), nwindings)` (base=`tm_nom`) | +| `tm_lb` | | `Vector{Vector{Real}}` | | opf | Minimum tap ratio for for each winding and phase, `size=((nphases), nwindings)` (base=`tm_nom`) | +| `tm_set` | `fill(fill(1.0, nphases), nwindings)` | `Vector{Vector{Real}}` | | always | Set tap ratio for each winding and phase, `size=((nphases), nwindings)` (base=`tm_nom`) | +| `tm_fix` | `fill(fill(true, nphases), nwindings)` | `Vector{Vector{Bool}}` | | always | Indicates for each winding and phase whether the tap ratio is fixed, `size=((nphases), nwindings)` | + +### Time Series (`time_series`) + +Time series objects are used to specify time series for _e.g._ load or generation forecasts. + +Some parameters for components specified in this document can support a time series by inserting a referece to a `time_series` object into the `time_series` dictionary inside a component under the relevant parameter name. For example, for a `load`, if `pd_nom` is supposed to be a time series, the user would specify `"time_series" => Dict("pd_nom" => time_series_id)` where `time_series_id` is the `id` of an object in `time_series`, and has type `Any`. + +| Name | Default | Type | Units | Used | Description | +| --------- | ------- | -------------- | ----- | ------ | ------------------------------------------------------------------------------------- | +| `time` | | `Vector{Real}` | hour | always | Time points at which values are specified | +| `values` | | `Vector{Real}` | | always | Multipers at each time step given in `time` | +| `offset` | 0 | `Real` | hour | always | Start time offset | +| `replace` | `true` | `Bool` | | always | Indicates to replace with data, instead of multiply. Will only work on non-Array data | + +### Fuses (`fuse`) + +Fuses can be defined on any terminal of any physical component + +| Name | Default | Type | Units | Used | Description | +| ----------------------- | ------- | ----------------------------- | ----- | ---- | ---------------------------------------------------- | +| `component_type` | | `String` | | | | +| `component_id` | | `Any` | | | | +| `terminals` | | `Vector{Int}||Vector{String}` | | | | +| `fuse_curve` | | `Array{Vector{Real},2}` | | | specifies the fuse blowing condition | +| `minimum_melting_curve` | | `Array{Vector{Real},2}` | | | specifies the minimum melting conditions of the fuse | From 6c331427c4293e28ab051c86b7feed97ae4ced25 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 13:13:51 -0600 Subject: [PATCH 186/224] FIX: documenter build --- docs/Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 5ed439199..a8526fabc 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,5 +4,5 @@ InfrastructureModels = "2030c09a-7f63-5d83-885d-db604e0e9cc0" PowerModels = "c36e90e8-916a-50a6-bd94-075b64ef4655" [compat] -InfrastructureModels = "~0.4" -PowerModels = "~0.15" +InfrastructureModels = "~0.5" +PowerModels = "~0.17" From 7ba109dbf949f56c79d09e9754f6bf5f7bf860bd Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 14:03:02 -0600 Subject: [PATCH 187/224] FIX: powermodels warnings --- src/data_model/eng2math.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 01259bb9d..4e779ed51 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -212,7 +212,11 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any ) _apply_filter!(math_obj, ["angmin", "angmax", "tap", "shift"], filter) connections = eng_obj["f_connections"][filter] - _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "shift"], connections, kr_phases) + _pad_properties!(math_obj, ["angmin"], connections, kr_phases; pad_value=-60.0) + _pad_properties!(math_obj, ["angmax"], connections, kr_phases; pad_value=60.0) + _pad_properties!(math_obj, ["tap"], connections, kr_phases; pad_value=1.0) + else math_obj["f_connections"] = eng_obj["f_connections"] math_obj["t_connections"] = eng_obj["t_connections"] @@ -401,7 +405,10 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A ) _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) connections = eng_obj["f_connections"][filter] - _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "angmin", "angmax", "tap", "shift"], connections, kr_phases) + _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "shift"], connections, kr_phases) + _pad_properties!(math_obj, ["angmin"], connections, kr_phases; pad_value=-60.0) + _pad_properties!(math_obj, ["angmax"], connections, kr_phases; pad_value=60.0) + _pad_properties!(math_obj, ["tap"], connections, kr_phases; pad_value=1.0) else branch_obj["f_connections"] = eng_obj["f_connections"] branch_obj["f_connections"] = eng_obj["t_connections"] From 261e7c6a29b683c70a4a71d13d388531227372c6 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 14:03:20 -0600 Subject: [PATCH 188/224] REF: checks cleanup --- src/data_model/checks.jl | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index e99a8b438..749646509 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -6,17 +6,12 @@ const _eng_model_checks = Dict{Symbol,Symbol}( :line => :_check_line, :transformer => :_check_transformer, # :switch => :_check_switch, - # :line_reactor => :_check_line_reactor, - # :series_capacitor => :_check_series_capacitor, :load => :_check_load, - :shunt_capacitor => :_check_shunt_capacitor, - # :shunt_reactor => :_check_shunt_reactor, :shunt => :_check_shunt, :generator => :_check_generator, :voltage_source => :_check_voltage_source, # :solar => :_check_solar, # :storage => :_check_storage, - # :grounding => :_check_grounding, ) "Data types of accepted fields in the engineering data model" @@ -246,18 +241,10 @@ const _eng_model_dtypes = Dict{Symbol,Dict{Symbol,Type}}( :values => Vector{<:Real}, :replace => Bool, ), - :grounding => Dict{Symbol,Type}( - :bus => Any, - :rg => Real, - :xg => Real, - ), # Future Components # :ev => Dict{Symbol,Type}(), # :wind => Dict{Symbol,Type}(), # :autotransformer => Dict{Symbol,Type}(), - # :synchronous_generator => Dict{Symbol,Type}(), - # :zip_load => Dict{Symbol,Type}(), - # :boundary => Dict{Symbol,Type}(), # :meter => Dict{Symbol,Type}() ) @@ -324,7 +311,6 @@ const _eng_model_req_fields= Dict{Symbol,Vector{Symbol}}( # :ev => Vector{Symbol}([]), # :wind => Vector{Symbol}([]), # :autotransformer => Vector{Symbol}([]), - # :synchronous_generator => Vector{Symbol}([]), # same as generator? # :meter => Vector{Symbol}([]) ) @@ -493,10 +479,6 @@ function _check_line(data_eng::Dict{String,<:Any}, name::Any) @assert haskey(data_eng, "linecode") && haskey(data_eng["linecode"], "$linecode_obj_name") "line $name: the linecode $linecode_obj_name is not defined." linecode = data_eng["linecode"]["$linecode_obj_name"] - # for key in ["n_conductors", "rs", "xs", "g_fr", "g_to", "b_fr", "b_to"] - # @assert !haskey(line, key) "line $name: a line with a linecode, should not specify $key; this is already done by the linecode." - # end - N = size(linecode["rs"])[1] @assert length(line["f_connections"])==N "line $name: the number of terminals should match the number of conductors in the linecode." @assert length(line["t_connections"])==N "line $name: the number of terminals should match the number of conductors in the linecode." From 2ae2d6a8a33570724ca75c1f873ca326dfbc05d3 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 15:15:02 -0600 Subject: [PATCH 189/224] REF: merge problem variants Merge problems that are clearly just formulation variants by using multiple dispatch opf_iv, opf_bf, opf_bf_lm, pf_bf, and pf_iv are elimintated in favor of only using opf an pf run commands This greatly simplifies the user experience --- CHANGELOG.md | 1 + README.md | 18 +++--- src/PowerModelsDistribution.jl | 5 -- src/core/data.jl | 1 + src/prob/opf.jl | 99 ++++++++++++++++++++++++++++++++ src/prob/opf_bf.jl | 43 -------------- src/prob/opf_bf_lm.jl | 52 ----------------- src/prob/opf_iv.jl | 53 ----------------- src/prob/pf.jl | 100 +++++++++++++++++++++++++++++++++ src/prob/pf_bf.jl | 51 ----------------- src/prob/pf_iv.jl | 57 ------------------- test/delta_gens.jl | 29 ++++------ test/loadmodels.jl | 2 +- test/opf_bf.jl | 10 ++-- test/opf_iv.jl | 6 +- test/pf.jl | 4 +- test/shunt.jl | 2 +- test/transformer.jl | 6 +- 18 files changed, 239 insertions(+), 300 deletions(-) delete mode 100644 src/prob/opf_bf.jl delete mode 100644 src/prob/opf_bf_lm.jl delete mode 100644 src/prob/opf_iv.jl delete mode 100644 src/prob/pf_bf.jl delete mode 100644 src/prob/pf_iv.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 389a74f34..6efbb6354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## v0.9.0 +- Remove `run_mc_opf_iv`, `run_mc_opf_bf`, `run_mc_opf_bf_lm`, `run_mc_pf_bf`, `run_mc_pf_iv`, these can be accessed by using the correct formulation with `run_mc_opf` and `run_mc_pf` - Add support for Memento 1.1 - Add support for PowerModels v0.17 (breaking) - Add support for InfrastructureModels v0.5 diff --git a/README.md b/README.md index d354b5e4b..3ae5d3cdc 100644 --- a/README.md +++ b/README.md @@ -21,18 +21,22 @@ This enables the definition of a wide variety of power network formulations and ## Core Network Formulations -- AC (polar and rectangular coordinates) -- SDP BFM relaxation -- SOC BFM and BIM relaxation (W-space) -- Linear approximation (LinDist3Flow and simplified unbalanced DistFlow) +- Nonlinear + - ACP + - ACR + - IVR +- Relaxations + - SDP BFM + - SOC BFM and BIM relaxation (W-space) +- Linear Approximations + - LinDist3Flow + - NFA + - DCP ## Network Data Formats -- Matlab ".m" files (extended for three-phase) - OpenDSS ".dss" files -**Warning:** This package is under active development and may change drastically without warning. - ## Development Community-driven development and enhancement of PowerModelsDistribution are welcome and encouraged. Please fork this repository and share your contributions to the master with pull requests. diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index 4be288680..c580ad6c2 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -61,13 +61,8 @@ module PowerModelsDistribution include("prob/common.jl") include("prob/mld.jl") include("prob/opf.jl") - include("prob/opf_iv.jl") include("prob/opf_oltc.jl") - include("prob/opf_bf.jl") - include("prob/opf_bf_lm.jl") include("prob/pf.jl") - include("prob/pf_bf.jl") - include("prob/pf_iv.jl") include("prob/debug.jl") include("prob/test.jl") diff --git a/src/core/data.jl b/src/core/data.jl index f6fa28168..f1107b418 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -226,6 +226,7 @@ function _load_expmodel_params(load::Dict, bus::Dict) if load["model"]==POWER return (pd, zeros(ncnds), qd, zeros(ncnds)) else + @warn load["model"] # get exponents if load["model"]==CURRENT alpha = ones(ncnds) diff --git a/src/prob/opf.jl b/src/prob/opf.jl index 2208e55b3..44858d1a5 100644 --- a/src/prob/opf.jl +++ b/src/prob/opf.jl @@ -62,3 +62,102 @@ function build_mc_opf(pm::_PM.AbstractPowerModel) _PM.objective_min_fuel_cost(pm) end + + +"constructor for OPF in current-voltage variable space" +function build_mc_opf(pm::_PM.AbstractIVRModel) + # Variables + variable_mc_bus_voltage(pm) + variable_mc_branch_current(pm) + variable_mc_transformer_current(pm) + variable_mc_gen_power_setpoint(pm) + variable_mc_load_setpoint(pm) + + # Constraints + for i in ids(pm, :ref_buses) + constraint_mc_theta_ref(pm, i) + end + + # gens should be constrained before KCL, or Pd/Qd undefined + for id in ids(pm, :gen) + constraint_mc_gen_setpoint(pm, id) + end + + # loads should be constrained before KCL, or Pd/Qd undefined + for id in ids(pm, :load) + constraint_mc_load_setpoint(pm, id) + end + + for i in ids(pm, :bus) + constraint_mc_load_current_balance(pm, i) + end + + for i in ids(pm, :branch) + constraint_mc_current_from(pm, i) + constraint_mc_current_to(pm, i) + + constraint_mc_bus_voltage_drop(pm, i) + + constraint_mc_voltage_angle_difference(pm, i) + + constraint_mc_thermal_limit_from(pm, i) + constraint_mc_thermal_limit_to(pm, i) + end + + for i in ids(pm, :transformer) + constraint_mc_transformer_power(pm, i) + end + + # Objective + _PM.objective_min_fuel_cost(pm) +end + + +"constructor for branch flow opf" +function build_mc_opf(pm::AbstractUBFModels) + # Variables + variable_mc_bus_voltage(pm) + variable_mc_branch_current(pm) + variable_mc_branch_power(pm) + variable_mc_transformer_power(pm) + variable_mc_gen_power_setpoint(pm) + variable_mc_load_setpoint(pm) + + # Constraints + constraint_mc_model_current(pm) + + for i in ids(pm, :ref_buses) + constraint_mc_theta_ref(pm, i) + end + + for i in ids(pm, :bus) + constraint_mc_power_balance(pm, i) + end + + for i in ids(pm, :branch) + constraint_mc_power_losses(pm, i) + constraint_mc_model_voltage_magnitude_difference(pm, i) + + constraint_mc_voltage_angle_difference(pm, i) + + constraint_mc_thermal_limit_from(pm, i) + constraint_mc_thermal_limit_to(pm, i) + end + + for i in ids(pm, :load) + constraint_mc_load_setpoint(pm, i) + end + + for i in ids(pm, :gen) + constraint_mc_gen_setpoint(pm, i) + end + + for i in ids(pm, :transformer) + constraint_mc_transformer_power(pm, i) + end + + # Objective + _PM.objective_min_fuel_cost(pm) +end + + diff --git a/src/prob/opf_bf.jl b/src/prob/opf_bf.jl deleted file mode 100644 index c5c19ccec..000000000 --- a/src/prob/opf_bf.jl +++ /dev/null @@ -1,43 +0,0 @@ -"branch flow opf" -function run_mc_opf_bf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) - return run_mc_model(data, model_type, solver, build_mc_opf_bf; kwargs...) -end - - -"constructor for branch flow opf" -function build_mc_opf_bf(pm::_PM.AbstractPowerModel) - # Variables - variable_mc_bus_voltage(pm) - variable_mc_branch_current(pm) - variable_mc_branch_power(pm) - variable_mc_transformer_power(pm) - variable_mc_gen_power_setpoint(pm) - - # Constraints - constraint_mc_model_current(pm) - - for i in ids(pm, :ref_buses) - constraint_mc_theta_ref(pm, i) - end - - for i in ids(pm, :bus) - constraint_mc_power_balance(pm, i) - end - - for i in ids(pm, :branch) - constraint_mc_power_losses(pm, i) - constraint_mc_model_voltage_magnitude_difference(pm, i) - - constraint_mc_voltage_angle_difference(pm, i) - - constraint_mc_thermal_limit_from(pm, i) - constraint_mc_thermal_limit_to(pm, i) - end - - for i in ids(pm, :transformer) - constraint_mc_transformer_power(pm, i) - end - - # Objective - _PM.objective_min_fuel_cost(pm) -end diff --git a/src/prob/opf_bf_lm.jl b/src/prob/opf_bf_lm.jl deleted file mode 100644 index 53f6b06f1..000000000 --- a/src/prob/opf_bf_lm.jl +++ /dev/null @@ -1,52 +0,0 @@ -# This problem includes load models beyond simple constant power ones; this is -# handled in variable_mc_load_setpoint and constraint_mc_load_setpoint. - - -"branch flow with loadmodels" -function run_mc_opf_bf_lm(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) - return run_mc_model(data, model_type, solver, build_mc_opf_bf_lm; kwargs...) -end - - -"constructor for branch flow with loadmodels" -function build_mc_opf_bf_lm(pm::_PM.AbstractPowerModel) - # Variables - variable_mc_bus_voltage(pm) - variable_mc_branch_current(pm) - variable_mc_branch_power(pm) - - variable_mc_gen_power_setpoint(pm) - variable_mc_load_setpoint(pm) - - # Constraints - constraint_mc_model_current(pm) - - for i in ids(pm, :ref_buses) - constraint_mc_theta_ref(pm, i) - end - - for i in ids(pm, :branch) - constraint_mc_power_losses(pm, i) - constraint_mc_model_voltage_magnitude_difference(pm, i) - - constraint_mc_voltage_angle_difference(pm, i) - - constraint_mc_thermal_limit_from(pm, i) - constraint_mc_thermal_limit_to(pm, i) - end - - for i in ids(pm, :load) - constraint_mc_load_setpoint(pm, i) - end - - for i in ids(pm, :gen) - constraint_mc_gen_setpoint(pm, i) - end - - for i in ids(pm, :bus) - constraint_mc_power_balance(pm, i) - end - - # Objective - _PM.objective_min_fuel_cost(pm) -end diff --git a/src/prob/opf_iv.jl b/src/prob/opf_iv.jl deleted file mode 100644 index 95d6313e8..000000000 --- a/src/prob/opf_iv.jl +++ /dev/null @@ -1,53 +0,0 @@ -"OPF in current-voltage variable space" -function run_mc_opf_iv(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) - return run_mc_model(data, model_type, solver, build_mc_opf_iv; kwargs...) -end - - -"constructor for OPF in current-voltage variable space" -function build_mc_opf_iv(pm::_PM.AbstractPowerModel) - # Variables - variable_mc_bus_voltage(pm) - variable_mc_branch_current(pm) - variable_mc_transformer_current(pm) - variable_mc_gen_power_setpoint(pm) - variable_mc_load_setpoint(pm) - - # Constraints - for i in ids(pm, :ref_buses) - constraint_mc_theta_ref(pm, i) - end - - # gens should be constrained before KCL, or Pd/Qd undefined - for id in ids(pm, :gen) - constraint_mc_gen_setpoint(pm, id) - end - - # loads should be constrained before KCL, or Pd/Qd undefined - for id in ids(pm, :load) - constraint_mc_load_setpoint(pm, id) - end - - for i in ids(pm, :bus) - constraint_mc_load_current_balance(pm, i) - end - - for i in ids(pm, :branch) - constraint_mc_current_from(pm, i) - constraint_mc_current_to(pm, i) - - constraint_mc_bus_voltage_drop(pm, i) - - constraint_mc_voltage_angle_difference(pm, i) - - constraint_mc_thermal_limit_from(pm, i) - constraint_mc_thermal_limit_to(pm, i) - end - - for i in ids(pm, :transformer) - constraint_mc_transformer_power(pm, i) - end - - # Objective - _PM.objective_min_fuel_cost(pm) -end diff --git a/src/prob/pf.jl b/src/prob/pf.jl index 44abf7985..f8088075e 100644 --- a/src/prob/pf.jl +++ b/src/prob/pf.jl @@ -61,3 +61,103 @@ function build_mc_pf(pm::_PM.AbstractPowerModel) constraint_mc_transformer_power(pm, i) end end + + +"Constructor for Power Flow in current-voltage variable space" +function build_mc_pf(pm::_PM.AbstractIVRModel) + # Variables + variable_mc_bus_voltage(pm, bounded = false) + variable_mc_branch_current(pm, bounded = false) + variable_mc_transformer_current(pm, bounded = false) + variable_mc_gen_power_setpoint(pm, bounded = false) + variable_mc_load_setpoint(pm, bounded = false) + + # Constraints + for (i,bus) in ref(pm, :ref_buses) + @assert bus["bus_type"] == 3 + constraint_mc_theta_ref(pm, i) + constraint_mc_voltage_magnitude_only(pm, i) + end + + # gens should be constrained before KCL, or Pd/Qd undefined + for id in ids(pm, :gen) + constraint_mc_gen_setpoint(pm, id) + end + + # loads should be constrained before KCL, or Pd/Qd undefined + for id in ids(pm, :load) + constraint_mc_load_setpoint(pm, id) + end + + for (i,bus) in ref(pm, :bus) + constraint_mc_load_current_balance(pm, i) + + # PV Bus Constraints + if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) + # this assumes inactive generators are filtered out of bus_gens + @assert bus["bus_type"] == 2 + constraint_mc_voltage_magnitude_only(pm, i) + for j in ref(pm, :bus_gens, i) + constraint_mc_gen_power_setpoint_real(pm, j) + end + end + end + + for i in ids(pm, :branch) + constraint_mc_current_from(pm, i) + constraint_mc_current_to(pm, i) + + constraint_mc_bus_voltage_drop(pm, i) + end + + for i in ids(pm, :transformer) + constraint_mc_transformer_power(pm, i) + end +end + + +"Constructor for Branch Flow Power Flow" +function build_mc_pf(pm::AbstractUBFModels) + # Variables + variable_mc_bus_voltage(pm; bounded=false) + variable_mc_branch_current(pm) + variable_mc_branch_power(pm) + variable_mc_gen_power_setpoint(pm; bounded=false) + + # Constraints + constraint_mc_model_current(pm) + + for (i,bus) in ref(pm, :ref_buses) + constraint_mc_theta_ref(pm, i) + + @assert bus["bus_type"] == 3 + constraint_mc_voltage_magnitude_only(pm, i) + end + + for i in ids(pm, :bus) + constraint_mc_power_balance(pm, i) + + # PV Bus Constraints + if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) + # this assumes inactive generators are filtered out of bus_gens + @assert bus["bus_type"] == 2 + + constraint_mc_voltage_magnitude_only(pm, i) + for j in ref(pm, :bus_gens, i) + constraint_mc_gen_power_setpoint_real(pm, j) + end + end + end + + for i in ids(pm, :branch) + constraint_mc_power_losses(pm, i) + constraint_mc_model_voltage_magnitude_difference(pm, i) + constraint_mc_voltage_angle_difference(pm, i) + + constraint_mc_thermal_limit_from(pm, i) + constraint_mc_thermal_limit_to(pm, i) + end + + # Objective + _PM.objective_min_fuel_cost(pm) +end diff --git a/src/prob/pf_bf.jl b/src/prob/pf_bf.jl deleted file mode 100644 index 3b793347b..000000000 --- a/src/prob/pf_bf.jl +++ /dev/null @@ -1,51 +0,0 @@ -"Branch Flow Power Flow Problem" -function run_mc_pf_bf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) - return run_mc_model(data, model_type, solver, build_mc_pf_bf; kwargs...) -end - - -"Constructor for Branch Flow Power Flow" -function build_mc_pf_bf(pm::_PM.AbstractPowerModel) - # Variables - variable_mc_bus_voltage(pm; bounded=false) - variable_mc_branch_current(pm) - variable_mc_branch_power(pm) - variable_mc_gen_power_setpoint(pm; bounded=false) - - # Constraints - constraint_mc_model_current(pm) - - for (i,bus) in ref(pm, :ref_buses) - constraint_mc_theta_ref(pm, i) - - @assert bus["bus_type"] == 3 - constraint_mc_voltage_magnitude_only(pm, i) - end - - for i in ids(pm, :bus) - constraint_mc_power_balance(pm, i) - - # PV Bus Constraints - if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) - # this assumes inactive generators are filtered out of bus_gens - @assert bus["bus_type"] == 2 - - constraint_mc_voltage_magnitude_only(pm, i) - for j in ref(pm, :bus_gens, i) - constraint_mc_gen_power_setpoint_real(pm, j) - end - end - end - - for i in ids(pm, :branch) - constraint_mc_power_losses(pm, i) - constraint_mc_model_voltage_magnitude_difference(pm, i) - constraint_mc_voltage_angle_difference(pm, i) - - constraint_mc_thermal_limit_from(pm, i) - constraint_mc_thermal_limit_to(pm, i) - end - - # Objective - _PM.objective_min_fuel_cost(pm) -end diff --git a/src/prob/pf_iv.jl b/src/prob/pf_iv.jl deleted file mode 100644 index b0b55982f..000000000 --- a/src/prob/pf_iv.jl +++ /dev/null @@ -1,57 +0,0 @@ -"Power Flow in current-voltage variable space" -function run_mc_pf_iv(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) - return run_mc_model(data, model_type, solver, build_mc_pf_iv; kwargs...) -end - - -"Constructor for Power Flow in current-voltage variable space" -function build_mc_pf_iv(pm::_PM.AbstractPowerModel) - # Variables - variable_mc_bus_voltage(pm, bounded = false) - variable_mc_branch_current(pm, bounded = false) - variable_mc_transformer_current(pm, bounded = false) - variable_mc_gen_power_setpoint(pm, bounded = false) - variable_mc_load_setpoint(pm, bounded = false) - - # Constraints - for (i,bus) in ref(pm, :ref_buses) - @assert bus["bus_type"] == 3 - constraint_mc_theta_ref(pm, i) - constraint_mc_voltage_magnitude_only(pm, i) - end - - # gens should be constrained before KCL, or Pd/Qd undefined - for id in ids(pm, :gen) - constraint_mc_gen_setpoint(pm, id) - end - - # loads should be constrained before KCL, or Pd/Qd undefined - for id in ids(pm, :load) - constraint_mc_load_setpoint(pm, id) - end - - for (i,bus) in ref(pm, :bus) - constraint_mc_load_current_balance(pm, i) - - # PV Bus Constraints - if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) - # this assumes inactive generators are filtered out of bus_gens - @assert bus["bus_type"] == 2 - constraint_mc_voltage_magnitude_only(pm, i) - for j in ref(pm, :bus_gens, i) - constraint_mc_gen_power_setpoint_real(pm, j) - end - end - end - - for i in ids(pm, :branch) - constraint_mc_current_from(pm, i) - constraint_mc_current_to(pm, i) - - constraint_mc_bus_voltage_drop(pm, i) - end - - for i in ids(pm, :transformer) - constraint_mc_transformer_power(pm, i) - end -end diff --git a/test/delta_gens.jl b/test/delta_gens.jl index d22a84552..dd67b760b 100644 --- a/test/delta_gens.jl +++ b/test/delta_gens.jl @@ -13,13 +13,13 @@ # create data model with equivalent generators pmd_2 = deepcopy(pmd_1) - pmd_2["load"] = Dict() - gen2load = Dict() - for (i,(id, load)) in enumerate(pmd_1["load"]) + pmd_2["load"] = Dict{String,Any}() + gen2load = Dict{String,Any}() + for (i, (id, load)) in enumerate(pmd_1["load"]) load = pmd_1["load"][id] gen = deepcopy(pmd_1["gen"]["1"]) - gen["index"] = i+1 + gen["index"] = i + 1 gen["cost"] *= 0 gen["configuration"] = load["configuration"] gen["pmax"] = gen["pmin"] = -load["pd"] @@ -31,17 +31,12 @@ end # check ACP and ACR - for (form, build_method) in zip( - [ACPPowerModel, ACRPowerModel, IVRPowerModel], - [build_mc_opf, build_mc_opf, build_mc_opf_iv] - ) - pm_1 = PM.instantiate_model(pmd_1, form, build_method, ref_extensions=[PMD.ref_add_arcs_transformer!]) - sol_1 = PM.optimize_model!(pm_1, optimizer=ipopt_solver) - @assert(sol_1["termination_status"]==LOCALLY_SOLVED) + for form in [ACPPowerModel, ACRPowerModel, IVRPowerModel] + sol_1 = run_mc_opf(pmd_1, form, ipopt_solver) + @test sol_1["termination_status"] == LOCALLY_SOLVED - pm_2 = PM.instantiate_model(pmd_2, form, build_method, ref_extensions=[PMD.ref_add_arcs_transformer!]) - sol_2 = PM.optimize_model!(pm_2, optimizer=ipopt_solver) - @assert(sol_2["termination_status"]==LOCALLY_SOLVED) + sol_2 = run_mc_opf(pmd_2, form, ipopt_solver) + @test sol_2["termination_status"] == LOCALLY_SOLVED # check that gens are equivalent to the loads for (gen, load) in gen2load @@ -49,8 +44,8 @@ qd_bus = sol_1["solution"]["load"][load]["qd"] pg_bus = sol_2["solution"]["gen"][gen]["pg"] qg_bus = sol_2["solution"]["gen"][gen]["qg"] - @test(isapprox(pd_bus, -pg_bus, atol=1E-5)) - @test(isapprox(qd_bus, -qg_bus, atol=1E-5)) + @test isapprox(pd_bus, -pg_bus, atol=1E-5) + @test isapprox(qd_bus, -qg_bus, atol=1E-5) end end end @@ -77,7 +72,7 @@ # gen["model"] = 2 # end # - # pm_ivr = PMs.instantiate_model(pmd, PMs.IVRPowerModel, PMD.build_mc_opf_iv, ref_extensions=[PMD.ref_add_arcs_transformer!]) + # pm_ivr = PMs.instantiate_model(pmd, PMs.IVRPowerModel, PMD.build_mc_opf, ref_extensions=[PMD.ref_add_arcs_transformer!]) # sol_ivr = PMs.optimize_model!(pm_ivr, optimizer=ipopt_solver) # @assert(sol_1["termination_status"]==LOCALLY_SOLVED) # diff --git a/test/loadmodels.jl b/test/loadmodels.jl index f16ea9d2a..451b46d8d 100644 --- a/test/loadmodels.jl +++ b/test/loadmodels.jl @@ -76,7 +76,7 @@ end @testset "loadmodels 1/2/5 in ivr pf" begin pmd = parse_file("../test/data/opendss/case3_lm_models.dss") - sol = run_mc_pf_iv(pmd, IVRPowerModel, ipopt_solver; make_si=false) + sol = run_mc_pf(pmd, IVRPowerModel, ipopt_solver; make_si=false) # voltage magnitude at load bus @test isapprox(calc_vm_acr(sol, "loadbus"), [0.83072, 0.99653, 1.0059], atol=1.5E-4) # delta and wye single-phase load models diff --git a/test/opf_bf.jl b/test/opf_bf.jl index 09f497cdb..8c9331664 100644 --- a/test/opf_bf.jl +++ b/test/opf_bf.jl @@ -6,7 +6,7 @@ @testset "test linearised distflow opf_bf" begin @testset "5-bus lpubfdiag opf_bf" begin - result = run_mc_opf_bf(case5, LPUBFDiagPowerModel, ipopt_solver) + result = run_mc_opf(case5, LPUBFDiagPowerModel, ipopt_solver) @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 44880; atol = 1e0) @@ -18,7 +18,7 @@ @testset "3-bus balanced lpubfdiag opf_bf" begin pmd = parse_file("../test/data/opendss/case3_balanced.dss") - sol = run_mc_opf_bf(pmd, LPUBFDiagPowerModel, ipopt_solver; make_si=false) + sol = run_mc_opf(pmd, LPUBFDiagPowerModel, ipopt_solver; make_si=false) @test sol["termination_status"] == LOCALLY_SOLVED @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.0183456; atol=2e-3) @@ -27,7 +27,7 @@ @testset "3-bus unbalanced lpubfdiag opf_bf" begin pmd = parse_file("../test/data/opendss/case3_unbalanced.dss") - sol = run_mc_opf_bf(pmd, LPUBFDiagPowerModel, ipopt_solver; make_si=false) + sol = run_mc_opf(pmd, LPUBFDiagPowerModel, ipopt_solver; make_si=false) @test sol["termination_status"] == LOCALLY_SOLVED @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["pg"] * sol["solution"]["settings"]["sbase"]), 0.0214812; atol=2e-3) @@ -37,7 +37,7 @@ @testset "test linearised distflow opf_bf in diagonal matrix form" begin @testset "5-bus lpdiagubf opf_bf" begin - result = run_mc_opf_bf(case5, LPUBFDiagPowerModel, ipopt_solver) + result = run_mc_opf(case5, LPUBFDiagPowerModel, ipopt_solver) @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 44880; atol = 1e0) @@ -46,7 +46,7 @@ @testset "test linearised distflow opf_bf in full matrix form" begin @testset "5-bus lpfullubf opf_bf" begin - result = run_mc_opf_bf(case5, LPUBFDiagPowerModel, ipopt_solver) + result = run_mc_opf(case5, LPUBFDiagPowerModel, ipopt_solver) @test result["termination_status"] == LOCALLY_SOLVED @test isapprox(result["objective"], 44880; atol = 1e0) diff --git a/test/opf_iv.jl b/test/opf_iv.jl index 97ad1f56d..1ce39089f 100644 --- a/test/opf_iv.jl +++ b/test/opf_iv.jl @@ -4,7 +4,7 @@ @testset "test IVR opf_iv" begin @testset "2-bus diagonal acp opf" begin pmd = parse_file("../test/data/opendss/case2_diag.dss") - sol = run_mc_opf_iv(pmd, IVRPowerModel, ipopt_solver; make_si=false) + sol = run_mc_opf(pmd, IVRPowerModel, ipopt_solver; make_si=false) @test sol["termination_status"] == LOCALLY_SOLVED @test isapprox(sol["objective"], 0.018208969542066918; atol = 1e-4) @@ -15,7 +15,7 @@ @testset "3-bus balanced acp opf" begin pmd = parse_file("../test/data/opendss/case3_balanced.dss") - sol = run_mc_opf_iv(pmd, IVRPowerModel, ipopt_solver; make_si=false) + sol = run_mc_opf(pmd, IVRPowerModel, ipopt_solver; make_si=false) @test sol["termination_status"] == LOCALLY_SOLVED @test isapprox(sol["objective"], 0.018345004773175046; atol = 1e-4) @@ -26,7 +26,7 @@ @testset "3-bus unbalanced acp opf" begin pmd = parse_file("../test/data/opendss/case3_unbalanced.dss") - sol = run_mc_opf_iv(pmd, IVRPowerModel, ipopt_solver; make_si=false) + sol = run_mc_opf(pmd, IVRPowerModel, ipopt_solver; make_si=false) @test sol["termination_status"] == LOCALLY_SOLVED @test isapprox(sol["objective"], 0.021481176584287; atol = 1e-4) diff --git a/test/pf.jl b/test/pf.jl index 91efe1e35..ce5d47a92 100644 --- a/test/pf.jl +++ b/test/pf.jl @@ -163,7 +163,7 @@ end @testset "2-bus diagonal ivr pf" begin - sol = run_mc_pf_iv(case2_diag, IVRPowerModel, ipopt_solver) + sol = run_mc_pf(case2_diag, IVRPowerModel, ipopt_solver) @test sol["termination_status"] == LOCALLY_SOLVED @@ -172,7 +172,7 @@ end @testset "3-bus balanced ivr pf" begin - sol = run_mc_pf_iv(case3_balanced, IVRPowerModel, ipopt_solver) + sol = run_mc_pf(case3_balanced, IVRPowerModel, ipopt_solver) @test sol["termination_status"] == LOCALLY_SOLVED diff --git a/test/shunt.jl b/test/shunt.jl index e49766d1d..fa0b09725 100644 --- a/test/shunt.jl +++ b/test/shunt.jl @@ -13,7 +13,7 @@ sol_acp_diag = run_mc_pf(data_diag_shunt, ACPPowerModel, ipopt_solver) sol_acp = run_mc_pf(data, ACPPowerModel, ipopt_solver) sol_acr = run_mc_pf(data, ACRPowerModel, ipopt_solver) - sol_iv = run_mc_pf_iv(data, IVRPowerModel, ipopt_solver) + sol_iv = run_mc_pf(data, IVRPowerModel, ipopt_solver) # check the results are different with only diagonal elements @test(!isapprox(sol_acp["solution"]["bus"]["2"]["vm"], sol_acp_diag["solution"]["bus"]["2"]["vm"])) diff --git a/test/transformer.jl b/test/transformer.jl index 4f1df5398..8769e0d4c 100644 --- a/test/transformer.jl +++ b/test/transformer.jl @@ -28,21 +28,21 @@ @testset "test transformer ivr pf" begin @testset "2w transformer ivr pf yy" begin eng = parse_file("../test/data/opendss/ut_trans_2w_yy.dss") - sol = run_mc_pf_iv(eng, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + sol = run_mc_pf(eng, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87451, 0.8613, 0.85348], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[-0.1, -120.4, 119.8], Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lead" begin eng = parse_file("../test/data/opendss/ut_trans_2w_dy_lead.dss") - sol = run_mc_pf_iv(eng, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + sol = run_mc_pf(eng, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.87391, 0.86055, 0.85486], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[29.8, -90.4, 149.8], Inf) <= 0.1 end @testset "2w transformer ivr pf dy_lag" begin eng = parse_file("../test/data/opendss/ut_trans_2w_dy_lag.dss") - sol = run_mc_pf_iv(eng, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) + sol = run_mc_pf(eng, IVRPowerModel, ipopt_solver; solution_processors=[sol_polar_voltage!], make_si=false) @test norm(sol["solution"]["bus"]["3"]["vm"]-[0.92092, 0.91012, 0.90059], Inf) <= 1.5E-5 @test norm(sol["solution"]["bus"]["3"]["va"]-[-30.0, -150.4, 89.8], Inf) <= 0.1 end From ba1bc8049bf0970939e700fef246b26899adc346 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 15:19:22 -0600 Subject: [PATCH 190/224] DOC: update readme --- README.md | 10 ++++++++-- src/core/data.jl | 1 - 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3ae5d3cdc..d094a85f3 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,14 @@ This enables the definition of a wide variety of power network formulations and ## Core Problem Specifications - Power Flow (pf) -- Optimal Power Flow (opf), for the Bus Injection Model (BIM) as well as the Branch Flow Model (BFM) -- Continuous load shed, minimum load delta (mld), for the Branch Flow Model (LPLinUBFPowerModel), AC Polar (ACPPowerModel), and Network Flow Approximation (NFAPowerModel) + - ACP, ACR, IVR, LinDist3Flow, NFA, DCP +- Optimal Power Flow (opf) + - ACP, ACR, IVR, LinDist3Flow, NFA, DCP +- Continuous load shed, minimum load delta (mld) + - ACP, LinDist3Flow, NFA +- Optimal Power Flow with on-load tap-changer (opf_oltc) + +**Note: The documentation is somewhat lagging behind development and the parings of network features with problem specifications with formulations has not been enumerated. We are working to correct this for you.** ## Core Network Formulations diff --git a/src/core/data.jl b/src/core/data.jl index f1107b418..f6fa28168 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -226,7 +226,6 @@ function _load_expmodel_params(load::Dict, bus::Dict) if load["model"]==POWER return (pd, zeros(ncnds), qd, zeros(ncnds)) else - @warn load["model"] # get exponents if load["model"]==CURRENT alpha = ones(ncnds) From 9406fb00e1696b711ec0e9a20e86c6445a5355a2 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Fri, 1 May 2020 15:20:09 -0600 Subject: [PATCH 191/224] FIX: oltc docstring --- src/prob/opf_oltc.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/prob/opf_oltc.jl b/src/prob/opf_oltc.jl index 82ec1c8b0..f4ffcf1e6 100644 --- a/src/prob/opf_oltc.jl +++ b/src/prob/opf_oltc.jl @@ -1,16 +1,16 @@ -"Online tap changing OPF with ACPPowerModel" +"on-load tap-changer OPF with ACPPowerModel" function run_ac_mc_opf_oltc(data::Union{Dict{String,<:Any},String}, solver; kwargs...) return run_mc_opf_oltc(data, ACPPowerModel, solver; kwargs...) end -"Online tap changing OPF" +"on-load tap-changer OPF" function run_mc_opf_oltc(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_opf_oltc; kwargs...) end -"constructor for Online tap changing OPF" +"constructor for on-load tap-changer OPF" function build_mc_opf_oltc(pm::_PM.AbstractPowerModel) variable_mc_bus_voltage(pm) variable_mc_branch_power(pm) From df9e13c796f688a2fd542140c8ae11a671f1bfcb Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 4 May 2020 07:07:03 -0600 Subject: [PATCH 192/224] ADD: Citation --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index d094a85f3..860620d79 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,21 @@ This code has been developed as part of the Advanced Network Science Initiative - Sander Claeys (@sanderclaeys) KU Leuven, transformer models and ACR formulation - Frederik Geth (@frederikgeth) CSIRO, Distribution modeling advise +## Citing PowerModelsDistribution + +If you find PowerModelsDistribution useful for your work, we kindly request that you cite the following [https://arxiv.org/abs/2004.10081](publication): + +```bibtex +@misc{fobes2020powermodelsdistributionjl, + title={PowerModelsDistribution.jl: An Open-Source Framework for Exploring Distribution Power Flow Formulations}, + author={David M Fobes and Sander Claeys and Frederik Geth and Carleton Coffrin}, + year={2020}, + eprint={2004.10081}, + archivePrefix={arXiv}, + primaryClass={cs.CE} +} +``` + ## License This code is provided under a BSD license as part of the Multi-Infrastructure Control and Optimization Toolkit (MICOT) project, LA-CC-13-108. From 398ebc99a70bd50d4b0d902f77f21f8329b50748 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 4 May 2020 16:16:01 -0600 Subject: [PATCH 193/224] FIX: PR#262 -> data-model refactor Fixes broken SDP implementations submitted originally by @frederikgeth ADD: default bounds from OpenDSS ADD: error message for dy transformer in LPUBFModels UPD: Changelog FIX: JuMP.set_()_bound -> set_()_bound to avoid NaNs UPD: delta_gen tests -> use eng data model Closes #262 --- CHANGELOG.md | 1 + src/core/constraint_template.jl | 2 +- src/core/data.jl | 2 + src/data_model/eng2math.jl | 53 +++-- src/data_model/units.jl | 2 + src/data_model/utils.jl | 39 ++-- src/form/apo.jl | 24 +-- src/form/bf.jl | 6 + src/form/bf_mx.jl | 363 ++++++++++++++++++-------------- src/form/bf_mx_lin.jl | 12 +- src/form/bf_mx_sdp.jl | 13 ++ src/form/bf_mx_soc.jl | 18 ++ src/form/dcp.jl | 4 +- src/form/shared.jl | 4 +- src/io/opendss.jl | 15 +- src/prob/opf.jl | 22 +- src/prob/pf.jl | 25 ++- test/delta_gens.jl | 57 ++--- test/opf.jl | 1 - test/opf_bf.jl | 75 ++++++- test/pf_bf.jl | 116 ++++++++++ test/runtests.jl | 30 +-- 22 files changed, 607 insertions(+), 277 deletions(-) create mode 100644 test/pf_bf.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 6efbb6354..f61c652c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## v0.9.0 +- SDP and SOC relaxations were broken but are fixed again (unit tests added) - Remove `run_mc_opf_iv`, `run_mc_opf_bf`, `run_mc_opf_bf_lm`, `run_mc_pf_bf`, `run_mc_pf_iv`, these can be accessed by using the correct formulation with `run_mc_opf` and `run_mc_pf` - Add support for Memento 1.1 - Add support for PowerModels v0.17 (breaking) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 03b42e389..3c473d1cf 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -172,7 +172,7 @@ function constraint_mc_power_balance(pm::_PM.AbstractPowerModel, i::Int; nw::Int bus_gs = Dict(k => ref(pm, nw, :shunt, k, "gs") for k in bus_shunts) bus_bs = Dict(k => ref(pm, nw, :shunt, k, "bs") for k in bus_shunts) - constraint_mc_power_balance(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) + constraint_mc_power_balance(pm, nw, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) end diff --git a/src/core/data.jl b/src/core/data.jl index f6fa28168..5226afe79 100644 --- a/src/core/data.jl +++ b/src/core/data.jl @@ -719,6 +719,7 @@ end _sum_rm_nan(X::Vector) = sum([X[(!).(isnan.(X))]..., 0.0]) +"" function _mat_mult_rm_nan(A::Matrix, B::Union{Matrix, Adjoint}) where T N, A_ncols = size(A) B_nrows, M = size(B) @@ -731,6 +732,7 @@ _mat_mult_rm_nan(A::Union{Matrix, Adjoint}, b::Vector) = dropdims(_mat_mult_rm_n _mat_mult_rm_nan(a::Vector, B::Union{Matrix, Adjoint}) = _mat_mult_rm_nan(reshape(a, length(a), 1), B) +"" function _nan2zero(b, a; val=0) and(x, y) = x && y b[and.(isnan.(b), a.==val)] .= 0.0 diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 4e779ed51..83127be4f 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -9,7 +9,7 @@ const _1to1_maps = Dict{String,Vector{String}}( "line_reactor" => ["f_connections", "t_connections", "source_id", "dss"], "shunt" => ["status", "dispatchable", "gs", "bs", "connections", "source_id", "dss"], "load" => ["model", "configuration", "connections", "dispatchable", "status", "source_id", "dss"], - "generator" => ["pg", "qg", "configuration", "connections", "source_id", "dss"], + "generator" => ["pg", "qg", "vg", "configuration", "connections", "source_id", "dss"], "solar" => ["pg", "qg", "configuration", "connections", "source_id", "dss"], "storage" => ["status", "energy", "ps", "qs", "connections", "source_id", "dss"], "voltage_source" => ["source_id", "dss"], @@ -194,8 +194,15 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["b_fr"] = _admittance_conversion(data_eng, eng_obj, "b_fr") math_obj["b_to"] = _admittance_conversion(data_eng, eng_obj, "b_to") - math_obj["angmin"] = fill(-60.0, nphases) - math_obj["angmax"] = fill( 60.0, nphases) + math_obj["angmin"] = get(eng_obj, "vad_lb", fill(-60.0, nphases)) + math_obj["angmax"] = get(eng_obj, "vad_ub", fill( 60.0, nphases)) + + for (f_key, t_key) in [("cm_ub", "c_rating_a"), ("cm_ub_b", "c_rating_b"), ("cm_ub_c", "c_rating_c"), + ("sm_ub", "rate_a"), ("sm_ub_b", "rate_b"), ("sm_ub_c", "rate_c")] + if haskey(eng_obj, f_key) + math_obj[t_key] = eng_obj[f_key] + end + end math_obj["transformer"] = false math_obj["shift"] = zeros(nphases) @@ -217,6 +224,12 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any _pad_properties!(math_obj, ["angmax"], connections, kr_phases; pad_value=60.0) _pad_properties!(math_obj, ["tap"], connections, kr_phases; pad_value=1.0) + for key in ["c_rating_a", "c_rating_b", "c_rating_c", "rate_a", "rate_b", "rate_c"] + if haskey(math_obj, key) + _apply_filter!(math_obj, [key], filter) + _pad_properties!(math_obj, [key], connections, kr_phases) + end + end else math_obj["f_connections"] = eng_obj["f_connections"] math_obj["t_connections"] = eng_obj["t_connections"] @@ -504,24 +517,22 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ for (name, eng_obj) in get(data_eng, "generator", Dict{String,Any}()) math_obj = _init_math_obj("generator", name, eng_obj, length(data_math["gen"])+1) - phases = eng_obj["phases"] connections = eng_obj["connections"] nconductors = data_math["conductors"] math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] + math_obj["gen_status"] = Int(eng_obj["status"]) + math_obj["control_mode"] = get(eng_obj, "control_mode", DROOP) - math_obj["vg"] = eng_obj["vg"] - - math_obj["qmin"] = eng_obj["qg_lb"] - math_obj["qmax"] = eng_obj["qg_ub"] - - math_obj["pmax"] = eng_obj["pg"] - math_obj["pmin"] = zeros(phases) + for (f_key, t_key) in [("qg_lb", "qmin"), ("qg_ub", "qmax"), ("pg_lb", "pmin"), ("pg_ub", "pmax")] + if haskey(eng_obj, f_key) + math_obj[t_key] = eng_obj[f_key] + end + end _add_gen_cost_model!(math_obj, eng_obj) - math_obj["configuration"] = eng_obj["configuration"] + math_obj["configuration"] = get(eng_obj, "configuration", WYE) if kron_reduced if math_obj["configuration"]==WYE @@ -535,7 +546,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ end # if PV generator mode convert attached bus to PV bus - if eng_obj["control_mode"] == 3 + if math_obj["control_mode"] == ISOCHRONOUS data_math["bus"]["$(data_math["bus_lookup"][eng_obj["bus"]])"]["bus_type"] = 2 end @@ -559,7 +570,7 @@ function _map_eng2math_solar!(data_math::Dict{String,<:Any}, data_eng::Dict{<:An nconductors = data_math["conductors"] math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] + math_obj["gen_status"] = Int(eng_obj["status"]) for (fr_k, to_k) in [("vg", "vg"), ("pg_lb", "pmin"), ("pg_ub", "pmax"), ("qg_lb", "qmin"), ("qg_ub", "qmax")] if haskey(eng_obj, fr_k) @@ -645,9 +656,13 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: math_obj["name"] = "_virtual_gen.voltage_source.$name" math_obj["gen_bus"] = gen_bus = data_math["bus_lookup"][eng_obj["bus"]] - math_obj["gen_status"] = eng_obj["status"] + math_obj["gen_status"] = Int(eng_obj["status"]) math_obj["pg"] = fill(0.0, nconductors) math_obj["qg"] = fill(0.0, nconductors) + math_obj["pmin"] = get(eng_obj, "pg_lb", fill(-Inf, nconductors)) + math_obj["pmax"] = get(eng_obj, "pg_ub", fill( Inf, nconductors)) + math_obj["qmin"] = get(eng_obj, "qg_lb", fill(-Inf, nconductors)) + math_obj["qmax"] = get(eng_obj, "qg_ub", fill( Inf, nconductors)) math_obj["configuration"] = WYE math_obj["source_id"] = "_virtual_gen.$(eng_obj["source_id"])" @@ -712,6 +727,12 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: data_math["branch"]["$(branch_obj["index"])"] = branch_obj map_to = [map_to, "bus.$(bus_obj["index"])", "branch.$(branch_obj["index"])"] + else + data_math["bus"]["$gen_bus"]["vmin"] = [eng_obj["vm"]..., 0.0] + data_math["bus"]["$gen_bus"]["vmax"] = [eng_obj["vm"]..., 0.0] + data_math["bus"]["$gen_bus"]["vm"] = [eng_obj["vm"]..., 0.0] + data_math["bus"]["$gen_bus"]["va"] = [eng_obj["va"]..., 0.0] + data_math["bus"]["$gen_bus"]["bus_type"] = 3 end data_math["gen"]["$(math_obj["index"])"] = math_obj diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 444af683c..ea74d4695 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -256,9 +256,11 @@ function _rebase_pu_branch!(branch::Dict{String,<:Any}, vbase::Real, sbase::Real z_new = vbase^2/sbase*voltage_scale_factor z_scale = z_old/z_new y_scale = 1/z_scale + sbase_scale = sbase_old/sbase _scale_props!(branch, ["br_r", "br_x"], z_scale) _scale_props!(branch, ["b_fr", "g_fr", "b_to", "g_to"], y_scale) + _scale_props!(branch, ["c_rating_a", "c_rating_b", "c_rating_c", "rate_a", "rate_b", "rate_c"], sbase_scale) branch["angmin"] = deg2rad.(branch["angmin"]) branch["angmax"] = deg2rad.(branch["angmax"]) diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index abdfc21cb..c14ed6f73 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -7,7 +7,6 @@ const _pmd_eng_global_keys = Set{String}([ function _init_math_obj(obj_type::String, eng_id::Any, eng_obj::Dict{String,<:Any}, index::Int)::Dict{String,Any} math_obj = Dict{String,Any}( "name" => "$eng_id", - "status" => Int(eng_obj["status"]) ) for key in _1to1_maps[obj_type] @@ -372,25 +371,27 @@ function _pad_properties_delta!(object::Dict{<:Any,<:Any}, properties::Vector{St @assert(length(phases)==3, "Padding only possible to a |phases|==3!") for property in properties - val = object[property] - val_length = length(connections)==2 ? 1 : length(connections) - @assert(isa(val, Vector) && length(val)==val_length) - - # build tmp - tmp = Dict() - sign = invert ? -1 : 1 - if val_length==1 - tmp[(connections[1], connections[2])] = val[1] - tmp[(connections[2], connections[1])] = sign*val[1] - else - tmp[(connections[1], connections[2])] = val[1] - tmp[(connections[2], connections[3])] = val[2] - tmp[(connections[3], connections[1])] = val[3] - end - merge!(tmp, Dict((k[2], k[1])=>sign*v for (k,v) in tmp)) - get_val(x,y) = haskey(tmp, (x,y)) ? tmp[(x,y)] : 0.0 + if haskey(object, property) + val = object[property] + val_length = length(connections)==2 ? 1 : length(connections) + @assert(isa(val, Vector) && length(val)==val_length) + + # build tmp + tmp = Dict() + sign = invert ? -1 : 1 + if val_length==1 + tmp[(connections[1], connections[2])] = val[1] + tmp[(connections[2], connections[1])] = sign*val[1] + else + tmp[(connections[1], connections[2])] = val[1] + tmp[(connections[2], connections[3])] = val[2] + tmp[(connections[3], connections[1])] = val[3] + end + merge!(tmp, Dict((k[2], k[1])=>sign*v for (k,v) in tmp)) + get_val(x,y) = haskey(tmp, (x,y)) ? tmp[(x,y)] : 0.0 - object[property] = [get_val(phases[1], phases[2]), get_val(phases[2], phases[3]), get_val(phases[3], phases[1])] + object[property] = [get_val(phases[1], phases[2]), get_val(phases[2], phases[3]), get_val(phases[3], phases[1])] + end end end diff --git a/src/form/apo.jl b/src/form/apo.jl index 0aa3de113..372f9ac76 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -173,9 +173,9 @@ function constraint_mc_thermal_limit_from(pm::_PM.AbstractActivePowerModel, n::I p_fr =var(pm, n, :p, f_idx)[c] if isa(p_fr, JuMP.VariableRef) && JuMP.has_lower_bound(p_fr) push!(mu_sm_fr,JuMP.LowerBoundRef(p_fr)) - JuMP.lower_bound(p_fr) < -rate_a[c] && JuMP.set_lower_bound(p_fr, -rate_a[c]) + JuMP.lower_bound(p_fr) < -rate_a[c] && set_lower_bound(p_fr, -rate_a[c]) if JuMP.has_upper_bound(p_fr) - JuMP.upper_bound(p_fr) > rate_a[c] && JuMP.set_upper_bound(p_fr, rate_a[c]) + JuMP.upper_bound(p_fr) > rate_a[c] && set_upper_bound(p_fr, rate_a[c]) end else push!(mu_sm_fr, JuMP.@constraint(pm.model, p_fr <= rate_a[c])) @@ -198,9 +198,9 @@ function constraint_mc_thermal_limit_to(pm::_PM.AbstractActivePowerModel, n::Int p_to =var(pm, n, :p, t_idx)[c] if isa(p_to, JuMP.VariableRef) && JuMP.has_lower_bound(p_to) push!(mu_sm_to, JuMP.LowerBoundRef(p_to)) - JuMP.lower_bound(p_to) < -rate_a[c] && JuMP.set_lower_bound(p_to, -rate_a[c]) + JuMP.lower_bound(p_to) < -rate_a[c] && set_lower_bound(p_to, -rate_a[c]) if JuMP.has_upper_bound(p_to) - JuMP.upper_bound(p_to) > rate_a[c] && JuMP.set_upper_bound(p_to, rate_a[c]) + JuMP.upper_bound(p_to) > rate_a[c] && set_upper_bound(p_to, rate_a[c]) end else push!(mu_sm_to, JuMP.@constraint(pm.model, p_to <= rate_a[c])) @@ -220,8 +220,8 @@ function constraint_mc_current_limit(pm::_PM.AbstractActivePowerModel, n::Int, f ncnds = length(cnds) for c in 1:ncnds - JuMP.lower_bound(p_fr[c]) < -c_rating_a[c] && JuMP.set_lower_bound(p_fr[c], -c_rating_a[c]) - JuMP.upper_bound(p_fr[c]) > c_rating_a[c] && JuMP.set_upper_bound(p_fr[c], c_rating_a[c]) + JuMP.lower_bound(p_fr[c]) < -c_rating_a[c] && set_lower_bound(p_fr[c], -c_rating_a[c]) + JuMP.upper_bound(p_fr[c]) > c_rating_a[c] && set_upper_bound(p_fr[c], c_rating_a[c]) end end @@ -273,8 +273,8 @@ function constraint_mc_switch_thermal_limit(pm::_PM.AbstractActivePowerModel, n: ncnds = length(cnds) for c in 1:ncnds - JuMP.lower_bound(psw[c]) < -rating[c] && JuMP.set_lower_bound(psw[c], -rating[c]) - JuMP.upper_bound(psw[c]) > rating[c] && JuMP.set_upper_bound(psw[c], rating[c]) + JuMP.lower_bound(psw[c]) < -rating[c] && set_lower_bound(psw[c], -rating[c]) + JuMP.upper_bound(psw[c]) > rating[c] && set_upper_bound(psw[c], rating[c]) end end @@ -287,8 +287,8 @@ function constraint_mc_storage_thermal_limit(pm::_PM.AbstractActivePowerModel, n ncnds = length(cnds) for c in 1:ncnds - JuMP.lower_bound(ps[c]) < -rating[c] && JuMP.set_lower_bound(ps[c], -rating[c]) - JuMP.upper_bound(ps[c]) > rating[c] && JuMP.set_upper_bound(ps[c], rating[c]) + JuMP.lower_bound(ps[c]) < -rating[c] && set_lower_bound(ps[c], -rating[c]) + JuMP.upper_bound(ps[c]) > rating[c] && set_upper_bound(ps[c], rating[c]) end end @@ -301,8 +301,8 @@ function constraint_mc_storage_current_limit(pm::_PM.AbstractActivePowerModel, n ncnds = length(cnds) for c in 1:ncnds - JuMP.lower_bound(ps[c]) < -rating[c] && JuMP.set_lower_bound(ps[c], -rating[c]) - JuMP.upper_bound(ps[c]) > rating[c] && JuMP.set_upper_bound(ps[c], rating[c]) + JuMP.lower_bound(ps[c]) < -rating[c] && set_lower_bound(ps[c], -rating[c]) + JuMP.upper_bound(ps[c]) > rating[c] && set_upper_bound(ps[c], rating[c]) end end diff --git a/src/form/bf.jl b/src/form/bf.jl index 3532f2cc6..7182c334c 100644 --- a/src/form/bf.jl +++ b/src/form/bf.jl @@ -47,3 +47,9 @@ end "nothing to do, this model is symmetric" function constraint_mc_transformer_power_yy(pm::LPUBFDiagModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) end + + +"TODO" +function constraint_mc_transformer_power_dy(pm::LPUBFDiagModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) + Memento.error(_LOGGER, "Delta-Wye transformers are not currently supported in the LPUBFDiagModel type of power flow models") +end diff --git a/src/form/bf_mx.jl b/src/form/bf_mx.jl index c15fd80d3..8fe03bb85 100644 --- a/src/form/bf_mx.jl +++ b/src/form/bf_mx.jl @@ -10,11 +10,19 @@ end "" function variable_mc_bus_voltage(pm::AbstractUBFModels; kwargs...) variable_mc_bus_voltage_prod_hermitian(pm; kwargs...) + + nw = get(kwargs, :nw, pm.cnw) + allbuses = Set(ids(pm, nw, :bus)) + startingbuses = Set(i for (l,i,j) in ref(pm, nw, :arcs_from)) + leafnodes = setdiff(allbuses, startingbuses) + for i in leafnodes + constraint_mc_voltage_psd(pm, nw, i) + end end "" -function variable_mc_bus_voltage_prod_hermitian(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_bus_voltage_prod_hermitian(pm::AbstractUBFModels; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) bus_ids = collect(ids(pm, nw, :bus)) if bounded @@ -29,6 +37,19 @@ function variable_mc_bus_voltage_prod_hermitian(pm::AbstractUBFModels; n_cond::I (Wr,Wi) = variable_mx_hermitian(pm.model, bus_ids, n_cond; set_lower_bound_diag_to_zero=true, name="W", prefix="$nw") end + v_start = exp.((im*2*pi/3).*[0; -1; 1]) #TODO this should be made more generic eventually + W_start = v_start*v_start' + for (id,_) in Wr + for i in 1:3 + for j in 1:i + JuMP.set_start_value(Wr[id][i,j], real.(W_start)[i,j]) + if j JuMP.@variable(pm.model, - [c in 1:ncnds], base_name="$(nw)_pl_$(i)", - lower_bound=pmin[i][c], upper_bound=pmax[i][c] + pd = Dict(i => JuMP.@variable(pm.model, + [c in 1:ncnds], base_name="$(nw)_pd_$(i)" ) for i in load_ids ) - ql = Dict(i => JuMP.@variable(pm.model, - [c in 1:ncnds], base_name="$(nw)_ql_$(i)", - lower_bound=qmin[i][c], upper_bound=qmax[i][c] + qd = Dict(i => JuMP.@variable(pm.model, + [c in 1:ncnds], base_name="$(nw)_qd_$(i)" ) for i in load_ids ) + + if bounded + for i in load_ids + load = ref(pm, nw, :load, i) + bus = ref(pm, nw, :bus, load["load_bus"]) + pmin, pmax, qmin, qmax = _calc_load_pq_bounds(load, bus) + set_lower_bound.(pd[i], pmin) + set_upper_bound.(pd[i], pmax) + set_lower_bound.(qd[i], qmin) + set_upper_bound.(qd[i], qmax) + end + end + #store in dict, but do not overwrite for i in load_ids - var(pm, nw)[:pl][i] = pl[i] - var(pm, nw)[:ql][i] = ql[i] + var(pm, nw)[:pd][i] = pd[i] + var(pm, nw)[:qd][i] = qd[i] end - report && _IM.sol_component_value(pm, nw, :load, :pl, load_ids, pl) - report && _IM.sol_component_value(pm, nw, :load, :ql, load_ids, ql) + report && _IM.sol_component_value(pm, nw, :load, :pd, load_ids, pd) + report && _IM.sol_component_value(pm, nw, :load, :qd, load_ids, qd) end @@ -391,14 +441,14 @@ function variable_mc_load_power_bus(pm::SDPUBFKCLMXModel, load_ids::Array{Int,1} bound[id] = bus["vmax"]*cmax' end # create matrix variables - (Pd,Qd) = variable_mx_complex_with_diag(pm.model, load_ids, ncnds; symm_bound=bound, name=("Pd", "Qd"), prefix="$nw") + (Pd_bus,Qd_bus) = variable_mx_complex_with_diag(pm.model, load_ids, ncnds; symm_bound=bound, name=("Pd_bus", "Qd_bus"), prefix="$nw") for id in load_ids - var(pm, nw, :Pd)[id] = Pd[id] - var(pm, nw, :Qd)[id] = Qd[id] + var(pm, nw, :Pd_bus)[id] = Pd_bus[id] + var(pm, nw, :Qd_bus)[id] = Qd_bus[id] end - report && _IM.sol_component_value(pm, nw, :load, :Pd, load_ids, Pd) - report && _IM.sol_component_value(pm, nw, :load, :Qd, load_ids, Qd) + report && _IM.sol_component_value(pm, nw, :load, :Pd_bus, load_ids, Pd_bus) + report && _IM.sol_component_value(pm, nw, :load, :Qd_bus, load_ids, Qd_bus) end @@ -472,21 +522,13 @@ function variable_mc_load_current(pm::AbstractUBFModels, load_ids::Array{Int,1}; end -""" -Only KCLModels need to further constrain the generator variables. -""" -function constraint_mc_gen_setpoint(pm::AbstractUBFModels, gen_id::Int; nw::Int=pm.cnw) - # do nothing -end - - """ Link the current and power withdrawn by a generator at the bus through a PSD constraint. The rank-1 constraint is dropped in this formulation. """ function constraint_mc_gen_setpoint(pm::SDPUBFKCLMXModel, gen_id::Int; nw::Int=pm.cnw) - Pg = var(pm, nw, :Pg, gen_id) - Qg = var(pm, nw, :Qg, gen_id) + Pg = var(pm, nw, :Pg_bus, gen_id) + Qg = var(pm, nw, :Qg_bus, gen_id) bus_id = ref(pm, nw, :gen, gen_id)["gen_bus"] Wr = var(pm, nw, :Wr, bus_id) Wi = var(pm, nw, :Wi, bus_id) @@ -562,7 +604,7 @@ end """ Creates the constraints modelling the (relaxed) voltage-dependent loads. """ -function constraint_mc_load_setpoint(pm::AbstractUBFModels, load_id::Int; nw::Int=pm.cnw) +function constraint_mc_load_setpoint(pm::AbstractUBFModels, load_id::Int; nw::Int=pm.cnw, report::Bool=true) # shared variables and parameters load = ref(pm, nw, :load, load_id) pd0 = load["pd"] @@ -581,24 +623,33 @@ function constraint_mc_load_setpoint(pm::AbstractUBFModels, load_id::Int; nw::In # take care of connections if load["configuration"]==WYE if load["model"]==POWER - var(pm, nw, :pl)[load_id] = pd0 - var(pm, nw, :ql)[load_id] = qd0 + var(pm, nw, :pd)[load_id] = pd0 + var(pm, nw, :qd)[load_id] = qd0 elseif load["model"]==IMPEDANCE w = var(pm, nw, :w)[bus_id] - var(pm, nw, :pl)[load_id] = a.*w - var(pm, nw, :ql)[load_id] = b.*w - # in this case, :pl has a JuMP variable + var(pm, nw, :pd)[load_id] = a.*w + var(pm, nw, :qd)[load_id] = b.*w + # in this case, :pd has a JuMP variable else - pl = var(pm, nw, :pl)[load_id] - ql = var(pm, nw, :ql)[load_id] + Wr = var(pm, nw, :Wr, bus_id) + pd = var(pm, nw, :pd)[load_id] + qd = var(pm, nw, :qd)[load_id] for c in 1:ncnds - constraint_pqw(pm.model, Wr[c,c], pl[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) - constraint_pqw(pm.model, Wr[c,c], ql[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) + constraint_pqw(pm.model, Wr[c,c], pd[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) + constraint_pqw(pm.model, Wr[c,c], qd[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) end end - # :pd is identical to :pl now - var(pm, nw, :pd)[load_id] = var(pm, nw, :pl)[load_id] - var(pm, nw, :qd)[load_id] = var(pm, nw, :ql)[load_id] + # :pd_bus is identical to :pd now + var(pm, nw, :pd_bus)[load_id] = var(pm, nw, :pd)[load_id] + var(pm, nw, :qd_bus)[load_id] = var(pm, nw, :qd)[load_id] + + ## reporting + if report + sol(pm, nw, :load, load_id)[:pd] = var(pm, nw, :pd)[load_id] + sol(pm, nw, :load, load_id)[:qd] = var(pm, nw, :qd)[load_id] + sol(pm, nw, :load, load_id)[:pd_bus] = var(pm, nw, :pd_bus)[load_id] + sol(pm, nw, :load, load_id)[:qd_bus] = var(pm, nw, :qd_bus)[load_id] + end elseif load["configuration"]==DELTA # link Wy, CCd and X Wr = var(pm, nw, :Wr, bus_id) @@ -609,34 +660,42 @@ function constraint_mc_load_setpoint(pm::AbstractUBFModels, load_id::Int; nw::In Xdi = var(pm, nw, :Xdi, load_id) Td = [1 -1 0; 0 1 -1; -1 0 1] constraint_SWL_psd(pm.model, Xdr, Xdi, Wr, Wi, CCdr, CCdi) - # define pd/qd and pl/ql as affine transformations of X - pd = LinearAlgebra.diag(Xdr*Td) - qd = LinearAlgebra.diag(Xdi*Td) - pl = LinearAlgebra.diag(Td*Xdr) - ql = LinearAlgebra.diag(Td*Xdi) - + # define pd/qd and pd_bus/qd_bus as affine transformations of X + pd_bus = LinearAlgebra.diag(Xdr*Td) + qd_bus = LinearAlgebra.diag(Xdi*Td) + pd = LinearAlgebra.diag(Td*Xdr) + qd = LinearAlgebra.diag(Td*Xdi) + + var(pm, nw, :pd_bus)[load_id] = pd_bus + var(pm, nw, :qd_bus)[load_id] = qd_bus var(pm, nw, :pd)[load_id] = pd var(pm, nw, :qd)[load_id] = qd - var(pm, nw, :pl)[load_id] = pl - var(pm, nw, :ql)[load_id] = ql # |Vd|^2 is a linear transformation of Wr wd = LinearAlgebra.diag(Td*Wr*Td') if load["model"]==POWER for c in 1:ncnds - JuMP.@constraint(pm.model, pl[c]==pd0[c]) - JuMP.@constraint(pm.model, ql[c]==qd0[c]) + JuMP.@constraint(pm.model, pd[c]==pd0[c]) + JuMP.@constraint(pm.model, qd[c]==qd0[c]) end elseif load["model"]==IMPEDANCE for c in 1:ncnds - JuMP.@constraint(pm.model, pl[c]==a[c]*wd[c]) - JuMP.@constraint(pm.model, ql[c]==b[c]*wd[c]) + JuMP.@constraint(pm.model, pd[c]==a[c]*wd[c]) + JuMP.@constraint(pm.model, qd[c]==b[c]*wd[c]) end else for c in 1:ncnds - constraint_pqw(pm.model, wd[c], pl[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) - constraint_pqw(pm.model, wd[c], ql[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) + constraint_pqw(pm.model, wd[c], pd[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) + constraint_pqw(pm.model, wd[c], qd[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) + end end + + ## reporting; for delta these are not available as saved variables! + if report + sol(pm, nw, :load, load_id)[:pd] = pd + sol(pm, nw, :load, load_id)[:qd] = qd + sol(pm, nw, :load, load_id)[:pd_bus] = pd_bus + sol(pm, nw, :load, load_id)[:qd_bus] = qd_bus end end end @@ -670,29 +729,29 @@ function constraint_mc_load_setpoint(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int if load["configuration"]==WYE if load["model"]==POWER - var(pm, nw, :pl)[load_id] = pd0 - var(pm, nw, :ql)[load_id] = qd0 + var(pm, nw, :pd)[load_id] = pd0 + var(pm, nw, :qd)[load_id] = qd0 elseif load["model"]==IMPEDANCE w = var(pm, nw, :w, bus_id) # for c in 1:ncnds - var(pm, nw, :pl)[load_id] = a.*w - var(pm, nw, :ql)[load_id] = b.*w + var(pm, nw, :pd)[load_id] = a.*w + var(pm, nw, :qd)[load_id] = b.*w # end - # in this case, :pl has a JuMP variable + # in this case, :pd has a JuMP variable else - pl = var(pm, nw, :pl)[load_id] - ql = var(pm, nw, :ql)[load_id] + pd = var(pm, nw, :pd)[load_id] + qd = var(pm, nw, :qd)[load_id] for c in 1:ncnds - constraint_pqw(pm.model, Wr[c,c], pl[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) - constraint_pqw(pm.model, Wr[c,c], ql[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) + constraint_pqw(pm.model, Wr[c,c], pd[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) + constraint_pqw(pm.model, Wr[c,c], qd[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) end end - # diagonal of :Pd is identical to :pl now - Pd = var(pm, nw, :Pd)[load_id] - Qd = var(pm, nw, :Qd)[load_id] + # diagonal of :Pd is identical to :pd now + Pd_bus = var(pm, nw, :Pd_bus)[load_id] + Qd_bus = var(pm, nw, :Qd_bus)[load_id] for c in 1:ncnds - Pd[c,c] = var(pm, nw, :pl)[load_id][c] - Qd[c,c] = var(pm, nw, :ql)[load_id][c] + Pd_bus[c,c] = var(pm, nw, :pd)[load_id][c] + Qd_bus[c,c] = var(pm, nw, :qd)[load_id][c] end elseif load["configuration"]==DELTA @@ -701,33 +760,33 @@ function constraint_mc_load_setpoint(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int Xdi = var(pm, nw, :Xdi, load_id) Td = [1 -1 0; 0 1 -1; -1 0 1] constraint_SWL_psd(pm.model, Xdr, Xdi, Wr, Wi, CCdr, CCdi) - # define pd/qd and pl/ql as affine transformations of X - Pd = Xdr*Td - Qd = Xdi*Td - pl = LinearAlgebra.diag(Td*Xdr) - ql = LinearAlgebra.diag(Td*Xdi) - - var(pm, nw, :Pd)[load_id] = Pd - var(pm, nw, :Qd)[load_id] = Qd - var(pm, nw, :pl)[load_id] = pl - var(pm, nw, :ql)[load_id] = ql + # define pd_bus/qd_bus and pd/qd as affine transformations of X + Pd_bus = Xdr*Td + Qd_bus = Xdi*Td + pd = LinearAlgebra.diag(Td*Xdr) + qd = LinearAlgebra.diag(Td*Xdi) + + var(pm, nw, :Pd_bus)[load_id] = Pd_bus + var(pm, nw, :Qd_bus)[load_id] = Qd_bus + var(pm, nw, :pd)[load_id] = pd + var(pm, nw, :qd)[load_id] = qd # |Vd|^2 is a linear transformation of Wr wd = LinearAlgebra.diag(Td*Wr*Td') if load["model"]==POWER for c in 1:ncnds - JuMP.@constraint(pm.model, pl[c]==pd0[c]) - JuMP.@constraint(pm.model, ql[c]==qd0[c]) + JuMP.@constraint(pm.model, pd[c]==pd0[c]) + JuMP.@constraint(pm.model, qd[c]==qd0[c]) end elseif load["model"]==IMPEDANCE for c in 1:ncnds - JuMP.@constraint(pm.model, pl[c]==a[c]*wd[c]) - JuMP.@constraint(pm.model, ql[c]==b[c]*wd[c]) + JuMP.@constraint(pm.model, pd[c]==a[c]*wd[c]) + JuMP.@constraint(pm.model, qd[c]==b[c]*wd[c]) end else for c in 1:ncnds - constraint_pqw(pm.model, wd[c], pl[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) - constraint_pqw(pm.model, wd[c], ql[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) + constraint_pqw(pm.model, wd[c], pd[c], a[c], alpha[c], wmin[c], wmax[c], pmin[c], pmax[c]) + constraint_pqw(pm.model, wd[c], qd[c], b[c], beta[c], wmin[c], wmax[c], qmin[c], qmax[c]) end end end @@ -760,24 +819,6 @@ function constraint_M_psd(model::JuMP.Model, M_re, M_im) end -""" -For KCLMXModels, a new power balance constraint is required. -""" -function constraint_mc_power_balance(pm::KCLMXModels, i::Int; nw::Int=pm.cnw) - bus = ref(pm, nw, :bus, i) - bus_arcs = ref(pm, nw, :bus_arcs, i) - bus_arcs_dc = ref(pm, nw, :bus_arcs_dc, i) - bus_gens = ref(pm, nw, :bus_gens, i) - bus_loads = ref(pm, nw, :bus_loads, i) - bus_shunts = ref(pm, nw, :bus_shunts, i) - - bus_Gs = Dict(k => LinearAlgebra.diagm(0=>ref(pm, nw, :shunt, k, "gs")) for k in bus_shunts) - bus_Bs = Dict(k => LinearAlgebra.diagm(0=>ref(pm, nw, :shunt, k, "bs")) for k in bus_shunts) - - constraint_mc_power_balance(pm, nw, i, bus_arcs, bus_arcs_dc, bus_gens, bus_loads, bus_Gs, bus_Bs) -end - - """ Shunt handling in matrix form: I = Y.U @@ -786,33 +827,47 @@ S = U.I' = U.(Y.U)' = U.U'.Y' = W.Y' P = Wr.G'+Wi.B' Q = -Wr.B'+Wi.G' """ -function constraint_mc_power_balance(pm::KCLMXModels, n::Int, i::Int, bus_arcs, bus_arcs_dc, bus_gens, bus_loads, bus_Gs, bus_Bs) +function constraint_mc_load_power_balance(pm::KCLMXModels, n::Int, i::Int, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) Wr = var(pm, n, :Wr, i) Wi = var(pm, n, :Wi, i) P = get(var(pm, n), :P, Dict()); _PM._check_var_keys(P, bus_arcs, "active power", "branch") Q = get(var(pm, n), :Q, Dict()); _PM._check_var_keys(Q, bus_arcs, "reactive power", "branch") - Pg = get(var(pm, n), :Pg, Dict()); _PM._check_var_keys(Pg, bus_gens, "active power", "generator") - Qg = get(var(pm, n), :Qg, Dict()); _PM._check_var_keys(Qg, bus_gens, "reactive power", "generator") - Pd = get(var(pm, n), :Pd, Dict()); _PM._check_var_keys(Pd, bus_loads, "active power", "load") - Qd = get(var(pm, n), :Qd, Dict()); _PM._check_var_keys(Qd, bus_loads, "reactive power", "load") - - # ignore dc for now - #TODO add DC in matrix version? - ncnds = size(Wr)[1] + Pg = get(var(pm, n), :Pg_bus, Dict()); _PM._check_var_keys(Pg, bus_gens, "active power", "generator") + Qg = get(var(pm, n), :Qg_bus, Dict()); _PM._check_var_keys(Qg, bus_gens, "reactive power", "generator") + Pd = get(var(pm, n), :Pd_bus, Dict()); _PM._check_var_keys(Pd, bus_loads, "active power", "load") + Qd = get(var(pm, n), :Qd_bus, Dict()); _PM._check_var_keys(Qd, bus_loads, "reactive power", "load") + + cnds = conductor_ids(pm; nw=n) + ncnds = length(cnds) + Gt = isempty(bus_gs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_gs)) Bt = isempty(bus_bs) ? fill(0.0, ncnds, ncnds) : sum(values(bus_bs)) - # changed the ordering - # LHS: all variables with generator sign convention - # RHS: all variables with load sign convention - # con(pm, n, :kcl_P)[i] = - cp = JuMP.@constraint(pm.model, sum(Pg[g] for g in bus_gens) .== sum(P[a] for a in bus_arcs) + sum(Pd[d] for d in bus_loads) + ( Wr*Gt'+Wi*Bt')) - # con(pm, n, :kcl_Q)[i] = - cq = JuMP.@constraint(pm.model, sum(Qg[g] for g in bus_gens) .== sum(Q[a] for a in bus_arcs) + sum(Qd[d] for d in bus_loads) + (-Wr*Bt'+Wi*Gt')) + cstr_p = JuMP.@constraint(pm.model, + sum(P[a] for a in bus_arcs) + # + sum(Psw[a_sw] for a_sw in bus_arcs_sw) + # + sum(Pt[a_trans] for a_trans in bus_arcs_trans) + .== + sum(Pg[g] for g in bus_gens) + # - sum(ps[s] for s in bus_storage) + - sum(Pd[d] for d in bus_loads) + - diag(Wr*Gt'+Wi*Bt') + ) + + cstr_q = JuMP.@constraint(pm.model, + sum(Q[a] for a in bus_arcs) + # + sum(diag(Qsw[a_sw]) for a_sw in bus_arcs_sw) + # + sum(diag(Qt[a_trans]) for a_trans in bus_arcs_trans) + .== + sum(Qg[g] for g in bus_gens) + # - sum(qs[s] for s in bus_storage) + - sum(Qd[d] for d in bus_loads) + - diag(-Wr*Bt'+Wi*Gt') + ) if _IM.report_duals(pm) - sol(pm, n, :bus, i)[:lam_kcl_r] = cp - sol(pm, n, :bus, i)[:lam_kcl_i] = cq + sol(pm, n, :bus, i)[:lam_kcl_r] = cstr_p + sol(pm, n, :bus, i)[:lam_kcl_i] = cstr_q end -end +end \ No newline at end of file diff --git a/src/form/bf_mx_lin.jl b/src/form/bf_mx_lin.jl index 4d5c41fbc..1e1d9baf8 100644 --- a/src/form/bf_mx_lin.jl +++ b/src/form/bf_mx_lin.jl @@ -12,13 +12,13 @@ end "" -function variable_mc_bus_voltage_prod_hermitian(pm::LPUBFDiagModel; n_cond::Int=3, nw::Int=pm.cnw, bounded = true) +function variable_mc_bus_voltage(pm::LPUBFDiagModel; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true) variable_mc_bus_voltage_magnitude_sqr(pm, nw=nw) end "" -function variable_mc_branch_power(pm::LPUBFDiagModel; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) +function variable_mc_branch_power(pm::LPUBFDiagModel; n_cond::Int=3, nw::Int=pm.cnw, bounded::Bool=true, report::Bool=true) @assert(n_cond == 3) variable_mc_branch_power_real(pm, nw=nw, bounded=bounded) variable_mc_branch_power_imaginary(pm, nw=nw, bounded=bounded) @@ -74,7 +74,7 @@ end "" -function constraint_mc_power_balance(pm::LPUBFDiagModel, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_pd, bus_qd, bus_gs, bus_bs) +function constraint_mc_load_power_balance(pm::LPUBFDiagModel, nw::Int, i, bus_arcs, bus_arcs_sw, bus_arcs_trans, bus_gens, bus_storage, bus_loads, bus_gs, bus_bs) w = var(pm, nw, :w, i) p = get(var(pm, nw), :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch") @@ -85,6 +85,8 @@ function constraint_mc_power_balance(pm::LPUBFDiagModel, nw::Int, i, bus_arcs, b pt = get(var(pm, nw), :pt, Dict()); _PM._check_var_keys(pt, bus_arcs_trans, "active power", "transformer") qt = get(var(pm, nw), :qt, Dict()); _PM._check_var_keys(qt, bus_arcs_trans, "reactive power", "transformer") + pd = get(var(pm, nw), :pd, Dict()); _PM._check_var_keys(pd, bus_loads, "active power", "load") + qd = get(var(pm, nw), :qd, Dict()); _PM._check_var_keys(qd, bus_loads, "reactive power", "load") pg = get(var(pm, nw), :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") qg = get(var(pm, nw), :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") @@ -100,7 +102,7 @@ function constraint_mc_power_balance(pm::LPUBFDiagModel, nw::Int, i, bus_arcs, b .== sum(pg[g] for g in bus_gens) - sum(ps[s] for s in bus_storage) - - sum(pd for pd in values(bus_pd)) + - sum(pd[d] for d in bus_loads) - sum(gs.*w for gs in values(bus_gs)) ) @@ -111,7 +113,7 @@ function constraint_mc_power_balance(pm::LPUBFDiagModel, nw::Int, i, bus_arcs, b .== sum(qg[g] for g in bus_gens) - sum(qs[s] for s in bus_storage) - - sum(qd for qd in values(bus_qd)) + - sum(qd[d] for d in bus_loads) + sum(bs.*w for bs in values(bus_bs)) ) diff --git a/src/form/bf_mx_sdp.jl b/src/form/bf_mx_sdp.jl index 3b75769e9..8a406b111 100644 --- a/src/form/bf_mx_sdp.jl +++ b/src/form/bf_mx_sdp.jl @@ -29,3 +29,16 @@ function constraint_mc_model_current(pm::SDPUBFModel, n::Int, i, f_bus, f_idx, g mat_imag mat_real ] in JuMP.PSDCone()) end + + +"Add explicit PSD-ness of W for nodes where it is not implied" +function constraint_mc_voltage_psd(pm::SDPUBFModel, n::Int, i) + Wr = var(pm, n, :Wr)[i] + Wi = var(pm, n, :Wi)[i] + + JuMP.@constraint(pm.model, + [ + Wr -Wi; + Wi Wr + ] in JuMP.PSDCone()) +end diff --git a/src/form/bf_mx_soc.jl b/src/form/bf_mx_soc.jl index 4e3a39ce4..4d32ceed1 100644 --- a/src/form/bf_mx_soc.jl +++ b/src/form/bf_mx_soc.jl @@ -34,6 +34,15 @@ function constraint_mc_model_current(pm::SOCUBFModels, n::Int, i, f_bus, f_idx, end +"Add explicit PSD-ness of W for nodes where it is not implied" +function constraint_mc_voltage_psd(pm::SOCUBFModels, n::Int, i) + Wr = var(pm, n, :Wr)[i] + Wi = var(pm, n, :Wi)[i] + + relaxation_psd_to_soc(pm.model, Wr, Wi) +end + + "Defines relationship between branch (series) power flow, branch (series) current and node voltage magnitude" function constraint_mc_model_current(pm::SOCConicUBFModel, n::Int, i, f_bus, f_idx, g_sh_fr, b_sh_fr) p_fr = var(pm, n, :P)[f_idx] @@ -60,3 +69,12 @@ function constraint_mc_model_current(pm::SOCConicUBFModel, n::Int, i, f_bus, f_i relaxation_psd_to_soc_conic(pm.model, mat_real, mat_imag, complex=true) end + + +"Add explicit PSD-ness of W for nodes where it is not implied" +function constraint_mc_voltage_psd(pm::SOCConicUBFModel, n::Int, i) + Wr = var(pm, n, :Wr)[i] + Wi = var(pm, n, :Wi)[i] + + relaxation_psd_to_soc_conic(pm.model, Wr, Wi) +end diff --git a/src/form/dcp.jl b/src/form/dcp.jl index 2ce0cb375..f7ee03c0b 100644 --- a/src/form/dcp.jl +++ b/src/form/dcp.jl @@ -83,8 +83,8 @@ function variable_mc_branch_power_real(pm::_PM.AbstractAPLossLessModels; nw::Int for (l,i,j) in ref(pm, nw, :arcs_from) smax = _calc_branch_power_max(ref(pm, nw, :branch, l), ref(pm, nw, :bus, i)) if !ismissing(smax) - JuMP.set_upper_bound.(p[(l,i,j)], smax) - JuMP.set_lower_bound.(p[(l,i,j)], -smax) + set_upper_bound.(p[(l,i,j)], smax) + set_lower_bound.(p[(l,i,j)], -smax) end end end diff --git a/src/form/shared.jl b/src/form/shared.jl index 672987f32..bad6c4737 100644 --- a/src/form/shared.jl +++ b/src/form/shared.jl @@ -145,8 +145,8 @@ function constraint_mc_load_power_balance(pm::_PM.AbstractWModels, nw::Int, i, b Pt = get(var(pm, nw), :Pt, Dict()); _PM._check_var_keys(Pt, bus_arcs_trans, "active power", "transformer") Qt = get(var(pm, nw), :Qt, Dict()); _PM._check_var_keys(Qt, bus_arcs_trans, "reactive power", "transformer") - pd = get(var(pm, nw), :pd_bus, Dict()); _PM._check_var_keys(pg, bus_loads, "active power", "load") - qd = get(var(pm, nw), :qd_bus, Dict()); _PM._check_var_keys(qg, bus_loads, "reactive power", "load") + pd = get(var(pm, nw), :pd_bus, Dict()); _PM._check_var_keys(pd, bus_loads, "active power", "load") + qd = get(var(pm, nw), :qd_bus, Dict()); _PM._check_var_keys(qd, bus_loads, "reactive power", "load") pg = get(var(pm, nw), :pg_bus, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator") qg = get(var(pm, nw), :qg_bus, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator") ps = get(var(pm, nw), :ps, Dict()); _PM._check_var_keys(ps, bus_storage, "active power", "storage") diff --git a/src/io/opendss.jl b/src/io/opendss.jl index fd6e3da34..535cbcb96 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -18,16 +18,12 @@ end function _dss2eng_bus!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:Any}, import_all::Bool=false) buses = _discover_buses(data_dss) - for bus in buses - @assert !startswith(bus, "_virtual") "Bus $bus: identifiers should not start with _virtual" + for id in buses + @assert !startswith(id, "_virtual") "Bus $id: identifiers should not start with _virtual" eng_obj = create_bus(status=ENABLED) - if !haskey(data_eng, "bus") - data_eng["bus"] = Dict{String,Any}() - end - - data_eng["bus"][bus] = eng_obj + _add_eng_obj!(data_eng, "bus", id, eng_obj) end end @@ -333,6 +329,8 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String "vg" => fill(defaults["kv"], nphases), "qg_lb" => fill(defaults["minkvar"] / nphases, nphases), "qg_ub" => fill(defaults["maxkvar"] / nphases, nphases), + "pg_lb" => fill(0.0, nphases), + "pg_ub" => fill(defaults["kw"] / nphases, nphases), "control_mode" => DROOP, "configuration" => WYE, "status" => defaults["enabled"] ? ENABLED : DISABLED, @@ -451,6 +449,9 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An "length" => defaults["switch"] || _like_is_switch ? 0.001 : defaults["length"], "f_connections" => f_connections, "t_connections" => t_connections, + "cm_ub" => fill(defaults["normamps"], nphases), + "cm_ub_b" => fill(defaults["emergamps"], nphases), + "cm_ub_c" => fill(defaults["emergamps"], nphases), "status" => defaults["enabled"] ? ENABLED : DISABLED, "source_id" => "line.$id" ) diff --git a/src/prob/opf.jl b/src/prob/opf.jl index 44858d1a5..a5f7b1d8e 100644 --- a/src/prob/opf.jl +++ b/src/prob/opf.jl @@ -130,8 +130,18 @@ function build_mc_opf(pm::AbstractUBFModels) constraint_mc_theta_ref(pm, i) end + # gens should be constrained before KCL, or Pd/Qd undefined + for id in ids(pm, :gen) + constraint_mc_gen_setpoint(pm, id) + end + + # loads should be constrained before KCL, or Pd/Qd undefined + for id in ids(pm, :load) + constraint_mc_load_setpoint(pm, id) + end + for i in ids(pm, :bus) - constraint_mc_power_balance(pm, i) + constraint_mc_load_power_balance(pm, i) end for i in ids(pm, :branch) @@ -144,14 +154,6 @@ function build_mc_opf(pm::AbstractUBFModels) constraint_mc_thermal_limit_to(pm, i) end - for i in ids(pm, :load) - constraint_mc_load_setpoint(pm, i) - end - - for i in ids(pm, :gen) - constraint_mc_gen_setpoint(pm, i) - end - for i in ids(pm, :transformer) constraint_mc_transformer_power(pm, i) end @@ -159,5 +161,3 @@ function build_mc_opf(pm::AbstractUBFModels) # Objective _PM.objective_min_fuel_cost(pm) end - - diff --git a/src/prob/pf.jl b/src/prob/pf.jl index f8088075e..e5e9a870c 100644 --- a/src/prob/pf.jl +++ b/src/prob/pf.jl @@ -119,23 +119,35 @@ end "Constructor for Branch Flow Power Flow" function build_mc_pf(pm::AbstractUBFModels) # Variables - variable_mc_bus_voltage(pm; bounded=false) + variable_mc_bus_voltage(pm; bounded=true) # TODO should be false variable_mc_branch_current(pm) variable_mc_branch_power(pm) + variable_mc_transformer_power(pm; bounded=false) variable_mc_gen_power_setpoint(pm; bounded=false) + variable_mc_load_setpoint(pm) # Constraints constraint_mc_model_current(pm) for (i,bus) in ref(pm, :ref_buses) - constraint_mc_theta_ref(pm, i) + if !(typeof(pm)<:LPUBFDiagPowerModel) + constraint_mc_theta_ref(pm, i) + end @assert bus["bus_type"] == 3 constraint_mc_voltage_magnitude_only(pm, i) end - for i in ids(pm, :bus) - constraint_mc_power_balance(pm, i) + for id in ids(pm, :gen) + constraint_mc_gen_setpoint(pm, id) + end + + for id in ids(pm, :load) + constraint_mc_load_setpoint(pm, id) + end + + for (i,bus) in ref(pm, :bus) + constraint_mc_load_power_balance(pm, i) # PV Bus Constraints if length(ref(pm, :bus_gens, i)) > 0 && !(i in ids(pm,:ref_buses)) @@ -158,6 +170,7 @@ function build_mc_pf(pm::AbstractUBFModels) constraint_mc_thermal_limit_to(pm, i) end - # Objective - _PM.objective_min_fuel_cost(pm) + for i in _PM.ids(pm, :transformer) + constraint_mc_transformer_power(pm, i) + end end diff --git a/test/delta_gens.jl b/test/delta_gens.jl index dd67b760b..eead62106 100644 --- a/test/delta_gens.jl +++ b/test/delta_gens.jl @@ -4,46 +4,51 @@ # This test checks the generators are connected properly by comparing them # to equivalent constant-power loads. This is achieved by fixing their bounds. @testset "ACP/ACR tests" begin - pmd_1 = parse_file("$pmd_path/test/data/opendss/case3_delta_gens.dss"; data_model=MATHEMATICAL) + eng_1 = parse_file("../test/data/opendss/case3_delta_gens.dss") - # convert to constant power loads - for (_, load) in pmd_1["load"] + for (_,load) in eng_1["load"] load["model"] = POWER end - # create data model with equivalent generators - pmd_2 = deepcopy(pmd_1) - pmd_2["load"] = Dict{String,Any}() - gen2load = Dict{String,Any}() - for (i, (id, load)) in enumerate(pmd_1["load"]) - load = pmd_1["load"][id] - gen = deepcopy(pmd_1["gen"]["1"]) + for (_,line) in eng_1["line"] + line["cm_ub"] = fill(1e4, size(line["cm_ub"])...) + end + + eng_2 = deepcopy(eng_1) + eng_2["load"] = Dict{Any,Any}() + eng_2["generator"] = Dict{Any,Any}() + for (id,load) in eng_1["load"] + gen = Dict{String,Any}( + "source_id" => load["source_id"], + "configuration" => load["configuration"], + "bus" => load["bus"], + "connections" => load["connections"], + "cost_pg_parameters" => [0, 0, 0], + "control_mode" => DROOP, + "pg_lb" => -load["pd_nom"], + "pg_ub" => -load["pd_nom"], + "qg_lb" => -load["qd_nom"], + "qg_ub" => -load["qd_nom"], + "status" => ENABLED, + ) - gen["index"] = i + 1 - gen["cost"] *= 0 - gen["configuration"] = load["configuration"] - gen["pmax"] = gen["pmin"] = -load["pd"] - gen["qmin"] = gen["qmax"] = -load["qd"] - gen["gen_bus"] = load["load_bus"] - gen["model"] = 2 - gen2load["$(i+1)"] = id - pmd_2["gen"]["$(i+1)"] = gen + eng_2["generator"][id] = gen end # check ACP and ACR for form in [ACPPowerModel, ACRPowerModel, IVRPowerModel] - sol_1 = run_mc_opf(pmd_1, form, ipopt_solver) + sol_1 = run_mc_opf(eng_1, form, ipopt_solver) @test sol_1["termination_status"] == LOCALLY_SOLVED - sol_2 = run_mc_opf(pmd_2, form, ipopt_solver) + sol_2 = run_mc_opf(eng_2, form, ipopt_solver) @test sol_2["termination_status"] == LOCALLY_SOLVED # check that gens are equivalent to the loads - for (gen, load) in gen2load - pd_bus = sol_1["solution"]["load"][load]["pd"] - qd_bus = sol_1["solution"]["load"][load]["qd"] - pg_bus = sol_2["solution"]["gen"][gen]["pg"] - qg_bus = sol_2["solution"]["gen"][gen]["qg"] + for (id,_) in eng_1["load"] + pd_bus = sol_1["solution"]["load"][id]["pd"] + qd_bus = sol_1["solution"]["load"][id]["qd"] + pg_bus = sol_2["solution"]["generator"][id]["pg"] + qg_bus = sol_2["solution"]["generator"][id]["qg"] @test isapprox(pd_bus, -pg_bus, atol=1E-5) @test isapprox(qd_bus, -qg_bus, atol=1E-5) end diff --git a/test/opf.jl b/test/opf.jl index 6195e3e9d..e5f805c92 100644 --- a/test/opf.jl +++ b/test/opf.jl @@ -90,7 +90,6 @@ end @testset "5-bus phase drop acp opf" begin - result = run_mc_opf(case5_phase_drop, ACPPowerModel, ipopt_solver; make_si=false) @test result["termination_status"] == LOCALLY_SOLVED diff --git a/test/opf_bf.jl b/test/opf_bf.jl index 8c9331664..92f82fe21 100644 --- a/test/opf_bf.jl +++ b/test/opf_bf.jl @@ -1,6 +1,6 @@ @info "running branch-flow optimal power flow (opf_bf) tests" -@testset "test distflow formulations" begin +@testset "test distflow formulations in opf" begin case5 = PowerModels.parse_file("../test/data/matpower/case5.m") make_multiconductor!(case5, 3) @@ -52,4 +52,77 @@ @test isapprox(result["objective"], 44880; atol = 1e0) end end + + data = parse_file("../test/data/opendss/case3_unbalanced.dss"; transformations=[make_lossless!]) + data["settings"]["sbase_default"] = 0.001 * 1e3 + data["generator"] = Dict{Any,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 + + data = transform_data_model(data) + + for (_,bus) in data["bus"] + if bus["name"] != "sourcebus" + bus["vmin"] = fill(0.9, 3) + bus["vmax"] = fill(1.1, 3) + bus["vm"] = fill(1.0, 3) + bus["va"] = deg2rad.([0., -120, 120]) + end + end + + @testset "test sdp distflow opf_bf" begin + @testset "3-bus SDPUBF opf_bf" begin + result = run_mc_opf(data, SDPUBFPowerModel, scs_solver) + + @test result["termination_status"] == OPTIMAL + @test isapprox(result["objective"], 21.48; atol = 1e-2) + end + end + + @testset "test sdp distflow opf_bf in full matrix form" begin + @testset "3-bus SDPUBFKCLMX opf_bf" begin + result = run_mc_opf(data, SDPUBFKCLMXPowerModel, scs_solver) + + @test result["termination_status"] == OPTIMAL + @test isapprox(result["objective"], 21.48; atol = 1e-2) + end + end + + + @testset "test soc distflow opf_bf" begin + @testset "3-bus SOCNLPUBF opf_bf" begin + result = run_mc_opf(data, SOCNLPUBFPowerModel, ipopt_solver) + + @test result["termination_status"] == LOCALLY_SOLVED + @test isapprox(result["objective"], 21.179; atol = 1e-1) + end + # @testset "3-bus SOCConicUBF opf_bf" begin + # result = run_mc_opf(data, SOCConicUBFPowerModel, scs_solver) + # + # @test result["termination_status"] == ALMOST_OPTIMAL + # @test isapprox(result["objective"], 21.17; atol = 1e-2) + # end + end end diff --git a/test/pf_bf.jl b/test/pf_bf.jl new file mode 100644 index 000000000..0ecb3459e --- /dev/null +++ b/test/pf_bf.jl @@ -0,0 +1,116 @@ +@info "running branch-flow power flow (pf_bf) tests" + +@testset "test distflow formulations in pf" begin + @testset "test linearised distflow pf_bf" begin + @testset "5-bus lpubfdiag opf_bf" begin + mp_data = PowerModels.parse_file("../test/data/matpower/case5.m") + make_multiconductor!(mp_data, 3) + result = run_mc_pf(mp_data, LPUBFDiagPowerModel, ipopt_solver) + + @test result["termination_status"] == LOCALLY_SOLVED + @test isapprox(result["objective"], 0; atol = 1e0) + # @test isapprox(result["solution"]["bus"]["3"]["vm"], 0.911466*[1,1,1]; atol = 1e-3) + vm = calc_vm_w(result, "3") + @test isapprox(vm, [1,1,1]; atol = 1e-3) + + end + + @testset "3-bus balanced lpubfdiag pf_bf" begin + pmd = parse_file("../test/data/opendss/case3_balanced.dss"; data_model=MATHEMATICAL) + sol = run_mc_pf(pmd, LPUBFDiagPowerModel, ipopt_solver) + + @test sol["termination_status"] == LOCALLY_SOLVED + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0183456; atol=2e-3) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00923328; atol=2e-3) + end + + @testset "3-bus unbalanced lpubfdiag pf_bf" begin + pmd = parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model=MATHEMATICAL) + sol = run_mc_pf(pmd, LPUBFDiagPowerModel, ipopt_solver) + + @test sol["termination_status"] == LOCALLY_SOLVED + @test isapprox(sum(sol["solution"]["gen"]["1"]["pg"] * sol["solution"]["baseMVA"]), 0.0214812; atol=2e-3) + @test isapprox(sum(sol["solution"]["gen"]["1"]["qg"] * sol["solution"]["baseMVA"]), 0.00927263; atol=2e-3) + end + end + + @testset "test linearised distflow pf_bf in diagonal matrix form" begin + @testset "5-bus lpdiagubf pf_bf" begin + mp_data = PowerModels.parse_file("../test/data/matpower/case5.m") + make_multiconductor!(mp_data, 3) + result = run_mc_pf(mp_data, LPUBFDiagPowerModel, ipopt_solver) + + @test result["termination_status"] == LOCALLY_SOLVED + @test isapprox(result["objective"], 0; atol = 1e0) + end + end + + @testset "test linearised distflow pf_bf in full matrix form" begin + @testset "5-bus lpfullubf pf_bf" begin + mp_data = PowerModels.parse_file("../test/data/matpower/case5.m") + make_multiconductor!(mp_data, 3) + result = run_mc_pf(mp_data, LPUBFDiagPowerModel, ipopt_solver) + + @test result["termination_status"] == LOCALLY_SOLVED + @test isapprox(result["objective"], 0; atol = 1e0) + end + end + + data = parse_file("../test/data/opendss/case3_unbalanced.dss"; transformations=[make_lossless!]) + data["settings"]["sbase_default"] = 0.001 * 1e3 + 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 + + data = transform_data_model(data) + + for (_,bus) in data["bus"] + if bus["name"] != "sourcebus" + bus["vmin"] = fill(0.9, 3) + bus["vmax"] = fill(1.1, 3) + end + end + + @testset "test sdp distflow pf_bf" begin + @testset "3-bus SDPUBF pf_bf" begin + result = run_mc_pf(data, SDPUBFPowerModel, scs_solver) + + @test result["termination_status"] == OPTIMAL + @test isapprox(result["objective"], 0; atol = 1e-2) + end + end + + # @testset "test sdp distflow pf_bf in full matrix form" begin + # @testset "3-bus SDPUBFKCLMX pf_bf" begin + # result = run_mc_pf(data, SDPUBFKCLMXPowerModel, scs_solver) + # + # @test result["termination_status"] == OPTIMAL + # @test isapprox(result["objective"], 0; atol = 1e-2) + # end + # end + + + @testset "test soc distflow pf_bf" begin + @testset "3-bus SOCNLPUBF pf_bf" begin + result = run_mc_pf(data, SOCNLPUBFPowerModel, ipopt_solver) + + @test result["termination_status"] == LOCALLY_SOLVED + @test isapprox(result["objective"], 0; atol = 1e-1) + end + @testset "3-bus SOCConicUBF pf_bf" begin + result = run_mc_pf(data, SOCConicUBFPowerModel, scs_solver) + + @test result["termination_status"] == OPTIMAL + @test isapprox(result["objective"], 0; atol = 1e-2) + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 789a60bec..043c98896 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -35,31 +35,33 @@ include("common.jl") @testset "PowerModelsDistribution" begin - include("opendss.jl") # three tests disabled temporarily + include("opendss.jl") - include("data.jl") # all passing + include("data.jl") - include("pf.jl") # all passing + include("pf.jl") - include("opf.jl") # all passing + include("pf_bf.jl") - include("opf_bf.jl") # all passing + include("opf.jl") - include("opf_iv.jl") # all passing + include("opf_bf.jl") - include("storage.jl") # all passing + include("opf_iv.jl") - include("debug.jl") # all passing + include("storage.jl") - include("multinetwork.jl") # all passing + include("debug.jl") - include("transformer.jl") # all passing + include("multinetwork.jl") - include("loadmodels.jl") # all passing + include("transformer.jl") - include("delta_gens.jl") # all passing + include("loadmodels.jl") - include("shunt.jl") # all passing + include("delta_gens.jl") - include("mld.jl") # all passing + include("shunt.jl") + + include("mld.jl") end From 6b51cf2009b1386384b64752570682d0ff72a1e2 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 4 May 2020 16:19:50 -0600 Subject: [PATCH 194/224] DOC: minor updates to data_model.md --- docs/src/data_model.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/data_model.md b/docs/src/data_model.md index 3b1f0b9f2..e4e28f8db 100644 --- a/docs/src/data_model.md +++ b/docs/src/data_model.md @@ -123,15 +123,15 @@ If all of these are specified, these bounds also imply valid bounds for the indi Instead of defining the bounds directly, they can be specified through an associated voltage zone. -| Name | Default | Type | Units | Used | Description | -| -------------- | ------- | ----------------------------- | ----- | ------ | ------------------------------------------------------------- | -| `phases` | | `Vector{Int}||Vector{String}` | | always | Identifies the terminal that represents the neutral conductor | -| `neutral` | | `Int||String` | | always | Identifies the terminal that represents the neutral conductor | -| `vm_pn_lb` | | `Real` | | opf | Minimum phase-to-neutral voltage magnitude for all phases | -| `vm_pn_ub` | | `Real` | | opf | Maximum phase-to-neutral voltage magnitude for all phases | -| `vm_pp_lb` | | `Real` | | opf | Minimum phase-to-phase voltage magnitude for all phases | -| `vm_pp_ub` | | `Real` | | opf | Maximum phase-to-phase voltage magnitude for all phases | -| `vm_ng_ub` | | `Real` | | opf | Maximum neutral-to-ground voltage magnitude | +| Name | Default | Type | Units | Used | Description | +| ---------- | ------- | ----------------------------- | ----- | ------ | ------------------------------------------------------------- | +| `phases` | | `Vector{Int}||Vector{String}` | | always | Identifies the terminal that represents the neutral conductor | +| `neutral` | | `Int||String` | | always | Identifies the terminal that represents the neutral conductor | +| `vm_pn_lb` | | `Real` | | opf | Minimum phase-to-neutral voltage magnitude for all phases | +| `vm_pn_ub` | | `Real` | | opf | Maximum phase-to-neutral voltage magnitude for all phases | +| `vm_pp_lb` | | `Real` | | opf | Minimum phase-to-phase voltage magnitude for all phases | +| `vm_pp_ub` | | `Real` | | opf | Maximum phase-to-phase voltage magnitude for all phases | +| `vm_ng_ub` | | `Real` | | opf | Maximum neutral-to-ground voltage magnitude | ## Edge Objects @@ -445,7 +445,7 @@ Some parameters for components specified in this document can support a time ser | --------- | ------- | -------------- | ----- | ------ | ------------------------------------------------------------------------------------- | | `time` | | `Vector{Real}` | hour | always | Time points at which values are specified | | `values` | | `Vector{Real}` | | always | Multipers at each time step given in `time` | -| `offset` | 0 | `Real` | hour | always | Start time offset | +| `offset` | `0` | `Real` | hour | always | Start time offset | | `replace` | `true` | `Bool` | | always | Indicates to replace with data, instead of multiply. Will only work on non-Array data | ### Fuses (`fuse`) From fb7b49b05d1f58dddb1071a183134ba19d36d2ca Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 5 May 2020 07:36:09 -0600 Subject: [PATCH 195/224] FIX: make_lossless! for transformers --- src/data_model/transformations.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/data_model/transformations.jl b/src/data_model/transformations.jl index 4d4a0f83f..edd4ce943 100644 --- a/src/data_model/transformations.jl +++ b/src/data_model/transformations.jl @@ -14,7 +14,11 @@ function make_lossless!(data_eng::Dict{String,<:Any}) for (id, eng_obj) in data_eng[object_type] for parameter in parameters if haskey(eng_obj, parameter) - delete!(eng_obj, parameter) + if object_type == "transformer" + eng_obj[parameter] = 0 .* eng_obj[parameter] + else + delete!(eng_obj, parameter) + end end end end From 7eefe836761cba9011558f2fe51bcc2c70eaab4a Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 5 May 2020 15:01:23 -0600 Subject: [PATCH 196/224] FIX: vbase calculation for networks with switches FIX: make_lossless! for switches REF: information warning about terminals vs phases FIX: size of line bound vectors --- src/data_model/eng2math.jl | 29 ++++++++---- src/data_model/transformations.jl | 8 ++-- src/data_model/units.jl | 73 +++++++++++++++---------------- src/io/opendss.jl | 20 +++++---- src/io/utils.jl | 2 +- 5 files changed, 71 insertions(+), 61 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 83127be4f..ff4c9db56 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -350,7 +350,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A math_obj = _init_math_obj("switch", name, eng_obj, length(data_math["switch"])+1) math_obj["f_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] - math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["f_bus"]] + math_obj["t_bus"] = data_math["bus_lookup"][eng_obj["t_bus"]] math_obj["state"] = get(eng_obj, "state", CLOSED) @@ -363,7 +363,12 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A map_to = "switch.$(math_obj["index"])" - if any(haskey(eng_obj, k) for k in ["rs", "xs", "linecode"]) + if haskey(eng_obj, "linecode") + _apply_linecode!(eng_obj, data_eng) + end + + if true + # if !all(isapprox.(get(eng_obj, "rs", zeros(1, 1)), 0)) && !all(isapprox.(get(eng_obj, "xs", zeros(1, 1)), 0)) # TODO enable real switches # build virtual bus f_bus = data_math["bus"]["$(math_obj["f_bus"])"] @@ -382,8 +387,14 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A # data_math["bus"]["$(bus_obj["index"])"] = bus_obj =# - # build virtual branch - _apply_linecode!(eng_obj, data_eng) + # TODO remove after enabling real switches + if all(isapprox.(get(eng_obj, "rs", zeros(nphases, nphases)), 0)) + eng_obj["rs"] = fill(1e-4, nphases, nphases) + end + + if all(isapprox.(get(eng_obj, "xs", zeros(nphases, nphases)), 0)) + eng_obj["xs"] = fill(1e-3, nphases, nphases) + end branch_obj = _init_math_obj("line", name, eng_obj, length(data_math["branch"])+1) @@ -418,10 +429,10 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A ) _apply_filter!(branch_obj, ["angmin", "angmax", "tap", "shift"], filter) connections = eng_obj["f_connections"][filter] - _pad_properties!(math_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "shift"], connections, kr_phases) - _pad_properties!(math_obj, ["angmin"], connections, kr_phases; pad_value=-60.0) - _pad_properties!(math_obj, ["angmax"], connections, kr_phases; pad_value=60.0) - _pad_properties!(math_obj, ["tap"], connections, kr_phases; pad_value=1.0) + _pad_properties!(branch_obj, ["br_r", "br_x", "g_fr", "g_to", "b_fr", "b_to", "shift"], connections, kr_phases) + _pad_properties!(branch_obj, ["angmin"], connections, kr_phases; pad_value=-60.0) + _pad_properties!(branch_obj, ["angmax"], connections, kr_phases; pad_value=60.0) + _pad_properties!(branch_obj, ["tap"], connections, kr_phases; pad_value=1.0) else branch_obj["f_connections"] = eng_obj["f_connections"] branch_obj["f_connections"] = eng_obj["t_connections"] @@ -670,7 +681,7 @@ function _map_eng2math_voltage_source!(data_math::Dict{String,<:Any}, data_eng:: map_to = "gen.$(math_obj["index"])" - if haskey(eng_obj, "rs") && haskey(eng_obj, "xs") + if !all(isapprox.(get(eng_obj, "rs", zeros(1, 1)), 0)) && !all(isapprox.(get(eng_obj, "xs", zeros(1, 1)), 0)) bus_obj = Dict{String,Any}( "bus_i" => length(data_math["bus"])+1, "index" => length(data_math["bus"])+1, diff --git a/src/data_model/transformations.jl b/src/data_model/transformations.jl index edd4ce943..952630120 100644 --- a/src/data_model/transformations.jl +++ b/src/data_model/transformations.jl @@ -1,7 +1,7 @@ # This file contains useful transformation functions for the engineering data model const _loss_model_objects = Dict{String,Vector{String}}( - "switch" => Vector{String}(["linecode", "rs", "xs", "g_fr", "b_fr", "g_to", "b_to"]), + "switch" => Vector{String}(["linecode", "rs", "xs"]), "voltage_source" => Vector{String}(["rs", "xs"]), "transformer" => Vector{String}(["rs", "xsc", "imag", "noloadloss"]) ) @@ -14,10 +14,10 @@ function make_lossless!(data_eng::Dict{String,<:Any}) for (id, eng_obj) in data_eng[object_type] for parameter in parameters if haskey(eng_obj, parameter) - if object_type == "transformer" - eng_obj[parameter] = 0 .* eng_obj[parameter] - else + if parameter == "linecode" delete!(eng_obj, parameter) + else + eng_obj[parameter] = 0 .* eng_obj[parameter] end end end diff --git a/src/data_model/units.jl b/src/data_model/units.jl index ea74d4695..55b7ea37d 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -24,14 +24,14 @@ const _dimensionalize_math = Dict{String,Dict{String,Vector{String}}}( "converts data model between per-unit and SI units" -function make_per_unit!(data::Dict{String,<:Any}; vbases=nothing, sbase=nothing, data_model_type=get(data, "data_model", MATHEMATICAL)) +function make_per_unit!(data::Dict{String,<:Any}; vbases::Union{Dict{<:Any,<:Real},Missing}=missing, sbase::Union{Real,Missing}=missing, data_model_type::DataModel=get(data, "data_model", MATHEMATICAL)) if data_model_type == MATHEMATICAL if !get(data, "per_unit", false) - if vbases === nothing - vbases = Dict(string(data["bus_lookup"][id])=>vbase for (id, vbase) in data["settings"]["vbases_default"]) + if ismissing(vbases) + vbases = Dict{String,Real}("$(data["bus_lookup"][id])"=>vbase for (id, vbase) in data["settings"]["vbases_default"]) end - if sbase === nothing + if ismissing(sbase) sbase = data["settings"]["sbase_default"] end @@ -52,41 +52,44 @@ end "finds voltage zones" -function _find_zones(data_model::Dict{String,<:Any}) - unused_line_ids = Set(keys(data_model["branch"])) - bus_lines = Dict([(id,Set()) for id in keys(data_model["bus"])]) - for (line_id,line) in data_model["branch"] - f_bus = string(line["f_bus"]) - t_bus = string(line["t_bus"]) - push!(bus_lines[f_bus], (line_id,t_bus)) - push!(bus_lines[t_bus], (line_id,f_bus)) +function _find_zones(data_model::Dict{String,<:Any})::Dict{Int,Set{String}} + unused_components = Set("$comp_type.$id" for comp_type in ["branch", "switch"] for id in keys(data_model[comp_type])) + bus_connectors = Dict([(id,Set()) for id in keys(data_model["bus"])]) + for comp_type in ["branch", "switch"] + for (id,obj) in data_model[comp_type] + f_bus = string(obj["f_bus"]) + t_bus = string(obj["t_bus"]) + push!(bus_connectors[f_bus], ("$comp_type.$id",t_bus)) + push!(bus_connectors[t_bus], ("$comp_type.$id",f_bus)) + end end + zones = [] buses = Set(keys(data_model["bus"])) while !isempty(buses) stack = [pop!(buses)] - zone = Set() + zone = Set{String}() while !isempty(stack) bus = pop!(stack) delete!(buses, bus) push!(zone, bus) - for (line_id,bus_to) in bus_lines[bus] - if line_id in unused_line_ids && bus_to in buses - delete!(unused_line_ids, line_id) + for (id,bus_to) in bus_connectors[bus] + if id in unused_components && bus_to in buses + delete!(unused_components, id) push!(stack, bus_to) end end end append!(zones, [zone]) end - zones = Dict(enumerate(zones)) + zones = Dict{Int,Set{String}}(enumerate(zones)) return zones end "calculates voltage bases for each voltage zone" -function _calc_vbase(data_model::Dict{String,<:Any}, vbase_sources::Dict{String,<:Real}) +function _calc_vbase(data_model::Dict{String,<:Any}, vbase_sources::Dict{String,<:Real})::Tuple{Dict,Dict} # find zones of buses connected by lines zones = _find_zones(data_model) bus_to_zone = Dict([(bus,zone) for (zone, buses) in zones for bus in buses]) @@ -101,29 +104,23 @@ function _calc_vbase(data_model::Dict{String,<:Any}, vbase_sources::Dict{String, end # transformers form the edges between these zones - zone_edges = Dict([(zone,[]) for zone in keys(zones)]) - edges = Set() - for (i,(_,transformer)) in enumerate(get(data_model, "transformer", Dict{Any,Dict{String,Any}}())) - push!(edges,i) - f_zone = bus_to_zone[string(transformer["f_bus"])] - t_zone = bus_to_zone[string(transformer["t_bus"])] + zone_edges = Dict{Int,Vector{Tuple{Int,Real}}}([(zone,[]) for zone in keys(zones)]) + for (_,transformer) in get(data_model, "transformer", Dict{Any,Dict{String,Any}}()) + f_zone = bus_to_zone["$(transformer["f_bus"])"] + t_zone = bus_to_zone["$(transformer["t_bus"])"] tm_nom = transformer["configuration"]==DELTA ? transformer["tm_nom"]/sqrt(3) : transformer["tm_nom"] - push!(zone_edges[f_zone], (i, t_zone, 1/tm_nom)) - push!(zone_edges[t_zone], (i, f_zone, tm_nom)) + push!(zone_edges[f_zone], (t_zone, 1/tm_nom)) + push!(zone_edges[t_zone], (f_zone, tm_nom)) end # initialize the stack with all specified zones stack = [zone for (zone,vbase) in zone_vbase if !ismissing(vbase)] - while !isempty(stack) - zone = pop!(stack) - - for (edge_id, zone_to, scale) in zone_edges[zone] - delete!(edges, edge_id) - - if ismissing(zone_vbase[zone_to]) - zone_vbase[zone_to] = zone_vbase[zone]*scale - push!(stack, zone_to) + f_zone = pop!(stack) + for (t_zone, scale) in zone_edges[f_zone] + if ismissing(zone_vbase[t_zone]) + zone_vbase[t_zone] = zone_vbase[f_zone]*scale + push!(stack, t_zone) end end end @@ -135,7 +132,7 @@ end "converts to per unit from SI" -function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math; sbase::Union{Real,Missing}=missing, vbases::Union{Dict{String,<:Real},Missing}=missing) +function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math::Dict{String,<:Any}; sbase::Union{Real,Missing}=missing, vbases::Union{Dict{String,<:Real},Missing}=missing) if ismissing(sbase) if haskey(data_math["settings"], "sbase_default") sbase = data_math["settings"]["sbase_default"] @@ -153,7 +150,7 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math; sbase:: # automatically find a good vbase if not provided if ismissing(vbases) if haskey(data_math["settings"], "vbases_default") - vbases = data_math["settings"]["vbases_default"] + vbases = Dict{String,Real}("$(data_math["bus_lookup"][id])" => vbase for (id, vbase) in data_math["settings"]["vbases_default"]) else buses_type_3 = [(id, sum(bus["vm"])/length(bus["vm"])) for (id,bus) in data_model["bus"] if haskey(bus, "bus_type") && bus["bus_type"]==3] if !isempty(buses_type_3) @@ -193,7 +190,7 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math; sbase:: end for (id, switch) in data_model["switch"] - # TODO are there any properties that need to be converted to pu? + # TODO end if haskey(data_model, "transformer") # transformers are not required by PowerModels diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 535cbcb96..c8717ed34 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -443,15 +443,17 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An f_connections = _get_conductors_ordered(defaults["bus1"], default=collect(1:nphases)) t_connections = _get_conductors_ordered(defaults["bus2"], default=collect(1:nphases)) + ncond = length(f_connections) + eng_obj = Dict{String,Any}( "f_bus" => _parse_bus_id(defaults["bus1"])[1], "t_bus" => _parse_bus_id(defaults["bus2"])[1], "length" => defaults["switch"] || _like_is_switch ? 0.001 : defaults["length"], "f_connections" => f_connections, "t_connections" => t_connections, - "cm_ub" => fill(defaults["normamps"], nphases), - "cm_ub_b" => fill(defaults["emergamps"], nphases), - "cm_ub_c" => fill(defaults["emergamps"], nphases), + "cm_ub" => fill(defaults["normamps"], ncond), + "cm_ub_b" => fill(defaults["emergamps"], ncond), + "cm_ub_c" => fill(defaults["emergamps"], ncond), "status" => defaults["enabled"] ? ENABLED : DISABLED, "source_id" => "line.$id" ) @@ -461,18 +463,18 @@ function _dss2eng_line!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An end if any(haskey(dss_obj, key) && _is_after_linecode(dss_obj["prop_order"], key) for key in ["r0", "r1", "rg", "rmatrix"]) || !haskey(dss_obj, "linecode") - eng_obj["rs"] = reshape(defaults["rmatrix"], nphases, nphases) + eng_obj["rs"] = reshape(defaults["rmatrix"], ncond, ncond) end if any(haskey(dss_obj, key) && _is_after_linecode(dss_obj["prop_order"], key) for key in ["x0", "x1", "xg", "xmatrix"]) || !haskey(dss_obj, "linecode") - eng_obj["xs"] = reshape(defaults["xmatrix"], nphases, nphases) + eng_obj["xs"] = reshape(defaults["xmatrix"], ncond, ncond) end if any(haskey(dss_obj, key) && _is_after_linecode(dss_obj["prop_order"], key) for key in ["b0", "b1", "c0", "c1", "cmatrix"]) || !haskey(dss_obj, "linecode") - eng_obj["b_fr"] = reshape(defaults["cmatrix"], nphases, nphases) ./ 2.0 - eng_obj["b_to"] = reshape(defaults["cmatrix"], nphases, nphases) ./ 2.0 - eng_obj["g_fr"] = fill(0.0, nphases, nphases) - eng_obj["g_to"] = fill(0.0, nphases, nphases) + eng_obj["b_fr"] = reshape(defaults["cmatrix"], ncond, ncond) ./ 2.0 + eng_obj["b_to"] = reshape(defaults["cmatrix"], ncond, ncond) ./ 2.0 + eng_obj["g_fr"] = fill(0.0, ncond, ncond) + eng_obj["g_to"] = fill(0.0, ncond, ncond) end # if the ground is used directly, register diff --git a/src/io/utils.jl b/src/io/utils.jl index 6e45ad3ba..df984fa8e 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -497,7 +497,7 @@ function _get_conductors_ordered(busname::AbstractString; default::Vector{Int}=V if check_length && length(default)!=length(ret) # TODO - Memento.warn(_LOGGER, "An incorrect number of nodes was specified on $(parts[1]); |$(parts[2])|!=$(length(default)).") + Memento.info(_LOGGER, "An inconsistent number of nodes was specified on $(parts[1]); |$(parts[2])|!=$(length(default)).") end return ret end From c4d9bb3a4b7ae7bd2a6b5e03d46781bcfaa52486 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 5 May 2020 16:38:20 -0600 Subject: [PATCH 197/224] REF: vnom, snom -> vm_nom, sm_nom REF: generalize _find_zone and _calc_vbases These functions now work on eng and math models REF: rename _find_zone -> discover_voltage_zones, _calc_vbase -> calc_voltage_bases ADD: apply_voltage_bounds! allows a user to apply voltage bounds to the engineering model based on percent bounds by calculating the voltage bases for buses --- src/data_model/checks.jl | 10 ++--- src/data_model/eng2math.jl | 25 +++++++---- src/data_model/transformations.jl | 20 +++++++++ src/data_model/units.jl | 74 +++++++++++++++++++++---------- src/data_model/utils.jl | 3 +- src/io/opendss.jl | 8 ++-- src/io/utils.jl | 2 +- 7 files changed, 99 insertions(+), 43 deletions(-) diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index 749646509..8af46c50b 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -449,11 +449,11 @@ function _check_load(data_eng::Dict{String,<:Any}, name::Any) _check_has_keys(load, ["pd", "qd"], context="load $name, $model:") _check_has_size(load, ["pd", "qd"], N, context="load $name, $model:") elseif model==EXPONENTIAL - _check_has_keys(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], context="load $name, $model") - _check_has_size(load, ["pd_ref", "qd_ref", "vnom", "alpha", "beta"], N, context="load $name, $model:") + _check_has_keys(load, ["pd_ref", "qd_ref", "vm_nom", "alpha", "beta"], context="load $name, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vm_nom", "alpha", "beta"], N, context="load $name, $model:") else - _check_has_keys(load, ["pd_ref", "qd_ref", "vnom"], context="load $name, $model") - _check_has_size(load, ["pd_ref", "qd_ref", "vnom"], N, context="load $name, $model:") + _check_has_keys(load, ["pd_ref", "qd_ref", "vm_nom"], context="load $name, $model") + _check_has_size(load, ["pd_ref", "qd_ref", "vm_nom"], N, context="load $name, $model:") end _check_connectivity(data_eng, load; context="load $name") @@ -510,7 +510,7 @@ function _check_transformer(data_eng::Dict{String,<:Any}, name::Any) transformer = data_eng["transformer"][name] nrw = length(transformer["bus"]) - _check_has_size(transformer, ["bus", "connections", "vnom", "snom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_lb", "tm_ub", "tm_step"], nrw, context="trans $name") + _check_has_size(transformer, ["bus", "connections", "vm_nom", "sm_nom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_lb", "tm_ub", "tm_step"], nrw, context="trans $name") @assert length(transformer["xsc"])==(nrw^2-nrw)/2 diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index ff4c9db56..e3b75a43f 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -6,7 +6,6 @@ const _1to1_maps = Dict{String,Vector{String}}( "line" => ["f_connections", "t_connections", "source_id", "dss"], "transformer" => ["f_connections", "t_connections", "source_id", "dss"], "switch" => ["status", "f_connections", "t_connections", "source_id", "dss"], - "line_reactor" => ["f_connections", "t_connections", "source_id", "dss"], "shunt" => ["status", "dispatchable", "gs", "bs", "connections", "source_id", "dss"], "load" => ["model", "configuration", "connections", "dispatchable", "status", "source_id", "dss"], "generator" => ["pg", "qg", "vg", "configuration", "connections", "source_id", "dss"], @@ -16,13 +15,23 @@ const _1to1_maps = Dict{String,Vector{String}}( ) "list of nodal type elements in the engineering model" -const _node_elements = Vector{String}([ +const _eng_node_elements = Vector{String}([ "load", "shunt", "generator", "solar", "storage", "voltage_source" ]) "list of edge type elements in the engineering model" -const _edge_elements = Vector{String}([ - "line", "switch", "transformer", "line_reactor", "series_capacitor" +const _eng_edge_elements = Vector{String}([ + "line", "switch", "transformer" +]) + +"list of nodal type elements in the engineering model" +const _math_node_elements = Vector{String}([ + "load", "shunt", "gen", "storage" +]) + +"list of edge type elements in the engineering model" +const _math_edge_elements = Vector{String}([ + "branch", "switch", "transformer", "dcline" ]) "list of multinetwork keys that belong at the root level" @@ -264,8 +273,8 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic _apply_xfmrcode!(eng_obj, data_eng) - vnom = eng_obj["vnom"] * data_eng["settings"]["voltage_scale_factor"] - snom = eng_obj["snom"] * data_eng["settings"]["power_scale_factor"] + vnom = eng_obj["vm_nom"] * data_eng["settings"]["voltage_scale_factor"] + snom = eng_obj["sm_nom"] * data_eng["settings"]["power_scale_factor"] nrw = length(eng_obj["bus"]) @@ -299,7 +308,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic for w in 1:nrw # 2-WINDING TRANSFORMER # make virtual bus and mark it for reduction - tm_nom = eng_obj["configuration"][w]==DELTA ? eng_obj["vnom"][w]*sqrt(3) : eng_obj["vnom"][w] + tm_nom = eng_obj["configuration"][w]==DELTA ? eng_obj["vm_nom"][w]*sqrt(3) : eng_obj["vm_nom"][w] transformer_2wa_obj = Dict{String,Any}( "name" => "_virtual_transformer.$name.$w", "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", @@ -510,7 +519,7 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any math_obj["connections"] = connections end - math_obj["vnom_kv"] = eng_obj["vnom"] + math_obj["vnom_kv"] = eng_obj["vm_nom"] data_math["load"]["$(math_obj["index"])"] = math_obj diff --git a/src/data_model/transformations.jl b/src/data_model/transformations.jl index 952630120..9a929f4c6 100644 --- a/src/data_model/transformations.jl +++ b/src/data_model/transformations.jl @@ -9,6 +9,8 @@ const _loss_model_objects = Dict{String,Vector{String}}( "remove parameters from objects with loss models to make them lossless" function make_lossless!(data_eng::Dict{String,<:Any}) + @assert data_eng["data_model"] == ENGINEERING "incorrect data model type" + for (object_type, parameters) in _loss_model_objects if haskey(data_eng, object_type) for (id, eng_obj) in data_eng[object_type] @@ -25,3 +27,21 @@ function make_lossless!(data_eng::Dict{String,<:Any}) end end end + + +"add voltage bounds" +function apply_voltage_bounds!(data_eng::Dict{String,<:Any}; vm_lb::Union{Real,Missing}=0.9, vm_ub::Union{Real,Missing}=1.1) + @assert data_eng["data_model"] == ENGINEERING "incorrect data model type" + + (bus_vbases, edge_vbases) = calc_voltage_bases(data_eng, data_eng["settings"]["vbases_default"]) + for (id,bus) in get(data_eng, "bus", Dict{Any,Dict{String,Any}}()) + vbase = bus_vbases[id] + if !ismissing(vm_lb) && !haskey(bus, "vm_lb") + bus["vm_lb"] = vbase .* fill(vm_lb, length(bus["terminals"])) + end + + if !ismissing(vm_ub) && !haskey(bus, "vm_ub") + bus["vm_ub"] = vbase .* fill(vm_ub, length(bus["terminals"])) + end + end +end diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 55b7ea37d..61b910cc3 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -52,11 +52,14 @@ end "finds voltage zones" -function _find_zones(data_model::Dict{String,<:Any})::Dict{Int,Set{String}} - unused_components = Set("$comp_type.$id" for comp_type in ["branch", "switch"] for id in keys(data_model[comp_type])) - bus_connectors = Dict([(id,Set()) for id in keys(data_model["bus"])]) - for comp_type in ["branch", "switch"] - for (id,obj) in data_model[comp_type] +function discover_voltage_zones(data_model::Dict{String,<:Any})::Dict{Int,Set{Any}} + @assert data_model["data_model"] in [MATHEMATICAL, ENGINEERING] "unsupported data model" + edge_elements = data_model["data_model"] == MATHEMATICAL ? _math_edge_elements : _eng_edge_elements + + unused_components = Set("$comp_type.$id" for comp_type in edge_elements[edge_elements .!= "transformer"] for id in keys(get(data_model, comp_type, Dict()))) + bus_connectors = Dict([(id,Set()) for id in keys(get(data_model, "bus", Dict()))]) + for comp_type in edge_elements[edge_elements .!= "transformer"] + for (id,obj) in get(data_model, comp_type, Dict()) f_bus = string(obj["f_bus"]) t_bus = string(obj["t_bus"]) push!(bus_connectors[f_bus], ("$comp_type.$id",t_bus)) @@ -65,10 +68,10 @@ function _find_zones(data_model::Dict{String,<:Any})::Dict{Int,Set{String}} end zones = [] - buses = Set(keys(data_model["bus"])) + buses = Set(keys(get(data_model, "bus", Dict()))) while !isempty(buses) stack = [pop!(buses)] - zone = Set{String}() + zone = Set{Any}() while !isempty(stack) bus = pop!(stack) delete!(buses, bus) @@ -82,16 +85,16 @@ function _find_zones(data_model::Dict{String,<:Any})::Dict{Int,Set{String}} end append!(zones, [zone]) end - zones = Dict{Int,Set{String}}(enumerate(zones)) + zones = Dict{Int,Set{Any}}(enumerate(zones)) return zones end "calculates voltage bases for each voltage zone" -function _calc_vbase(data_model::Dict{String,<:Any}, vbase_sources::Dict{String,<:Real})::Tuple{Dict,Dict} +function calc_voltage_bases(data_model::Dict{String,<:Any}, vbase_sources::Dict{<:Any,<:Real})::Tuple{Dict,Dict} # find zones of buses connected by lines - zones = _find_zones(data_model) + zones = discover_voltage_zones(data_model) bus_to_zone = Dict([(bus,zone) for (zone, buses) in zones for bus in buses]) # assign specified vbase to corresponding zones @@ -106,11 +109,32 @@ function _calc_vbase(data_model::Dict{String,<:Any}, vbase_sources::Dict{String, # transformers form the edges between these zones zone_edges = Dict{Int,Vector{Tuple{Int,Real}}}([(zone,[]) for zone in keys(zones)]) for (_,transformer) in get(data_model, "transformer", Dict{Any,Dict{String,Any}}()) - f_zone = bus_to_zone["$(transformer["f_bus"])"] - t_zone = bus_to_zone["$(transformer["t_bus"])"] - tm_nom = transformer["configuration"]==DELTA ? transformer["tm_nom"]/sqrt(3) : transformer["tm_nom"] - push!(zone_edges[f_zone], (t_zone, 1/tm_nom)) - push!(zone_edges[t_zone], (f_zone, tm_nom)) + if data_model["data_model"] == MATHEMATICAL + f_zone = bus_to_zone["$(transformer["f_bus"])"] + t_zone = bus_to_zone["$(transformer["t_bus"])"] + tm_nom = transformer["configuration"]==DELTA ? transformer["tm_nom"]/sqrt(3) : transformer["tm_nom"] + push!(zone_edges[f_zone], (t_zone, 1/tm_nom)) + push!(zone_edges[t_zone], (f_zone, tm_nom)) + else + if haskey(transformer, "f_bus") + f_zone = bus_to_zone["$(transformer["f_bus"])"] + t_zone = bus_to_zone["$(transformer["f_bus"])"] + tm_nom = transformer["configuration"] == DELTA ? transformer["tm_nom"]/sqrt(3) : transformer["tm_nom"] + push!(zone_edges[f_zone], (t_zone, 1/tm_nom)) + push!(zone_edges[t_zone], (f_zone, tm_nom)) + else + nrw = length(transformer["bus"]) + f_zone = bus_to_zone[transformer["bus"][1]] + f_vnom = transformer["configuration"][1] == DELTA ? transformer["vm_nom"][1]/sqrt(3) : transformer["vm_nom"][1] + for w in 2:nrw + t_zone = bus_to_zone[transformer["bus"][w]] + t_vnom = transformer["configuration"][1] == DELTA ? transformer["vm_nom"][w]/sqrt(3) : transformer["vm_nom"][w] + tm_nom = f_vnom / t_vnom + push!(zone_edges[f_zone], (t_zone, 1/tm_nom)) + push!(zone_edges[t_zone], (f_zone, tm_nom)) + end + end + end end # initialize the stack with all specified zones @@ -125,9 +149,11 @@ function _calc_vbase(data_model::Dict{String,<:Any}, vbase_sources::Dict{String, end end + edge_elements = data_model["data_model"] == MATHEMATICAL ? _math_edge_elements : _eng_edge_elements + bus_vbase = Dict([(bus,zone_vbase[zone]) for (bus,zone) in bus_to_zone]) - line_vbase = Dict([(id, bus_vbase[string(line["f_bus"])]) for (id,line) in data_model["branch"]]) - return (bus_vbase, line_vbase) + edge_vbase = Dict([("$edge_type.$id", bus_vbase["$(obj["f_bus"])"]) for edge_type in edge_elements[edge_elements .!= "transformer"] for (id,obj) in data_model[edge_type]]) + return (bus_vbase, edge_vbase) end @@ -161,7 +187,7 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math::Dict{St end end - bus_vbase, line_vbase = _calc_vbase(data_model, vbases) + bus_vbase, line_vbase = calc_voltage_bases(data_model, vbases) voltage_scale_factor = data_math["settings"]["voltage_scale_factor"] for (id, bus) in data_model["bus"] @@ -169,8 +195,8 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math::Dict{St end for (id, line) in data_model["branch"] - vbase = line_vbase[id] - _rebase_pu_branch!(line, line_vbase[id], sbase, sbase_old, voltage_scale_factor) + vbase = line_vbase["branch.$id"] + _rebase_pu_branch!(line, vbase, sbase, sbase_old, voltage_scale_factor) end for (id, shunt) in data_model["shunt"] @@ -214,16 +240,16 @@ function _rebase_pu_bus!(bus::Dict{String,<:Any}, vbase::Real, sbase::Real, sbas if !haskey(bus, "vbase") - # if haskey(bus, "vnom") - # vnom = bus["vnom"] - # _scale_props!(bus, ["vnom"], 1/vbase) + # if haskey(bus, "vm_nom") + # vnom = bus["vm_nom"] + # _scale_props!(bus, ["vm_nom"], 1/vbase) # end _scale_props!(bus, prop_vnom, 1/vbase) z_old = 1.0 else vbase_old = bus["vbase"] - _scale_props!(bus, [prop_vnom..., "vnom"], vbase_old/vbase) + _scale_props!(bus, [prop_vnom..., "vm_nom"], vbase_old/vbase) z_old = vbase_old^2*sbase_old*voltage_scale_factor end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index c14ed6f73..014782940 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -481,7 +481,7 @@ function _apply_xfmrcode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:A for (k, v) in xfmrcode if !haskey(eng_obj, k) eng_obj[k] = v - elseif haskey(eng_obj, k) && k in ["vnom", "snom", "tm_set", "rs"] + elseif haskey(eng_obj, k) && k in ["vm_nom", "sm_nom", "tm_set", "rs"] for (w, vw) in enumerate(eng_obj[k]) if ismissing(vw) eng_obj[k][w] = v[w] @@ -671,3 +671,4 @@ function _build_eng_multinetwork(data_eng::Dict{String,<:Any})::Dict{String,Any} return _IM.make_multinetwork(_pre_mn_data, _pmd_eng_global_keys) end end + diff --git a/src/io/opendss.jl b/src/io/opendss.jl index c8717ed34..eb643fd5d 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -118,7 +118,7 @@ function _dss2eng_load!(data_eng::Dict{String,<:Any}, data_dss::Dict{String,<:An kv = kv/sqrt(3) end - eng_obj["vnom"] = kv + eng_obj["vm_nom"] = kv eng_obj["pd_nom"] = fill(defaults["kw"]/nphases, nphases) eng_obj["qd_nom"] = fill(defaults["kvar"]/nphases, nphases) @@ -519,8 +519,8 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, "tm_ub" => Vector{Vector{Float64}}(fill(fill(defaults["maxtap"], nphases), nrw)), "tm_fix" => Vector{Vector{Bool}}(fill(ones(Bool, nphases), nrw)), "tm_step" => Vector{Vector{Float64}}(fill(fill(1/32, nphases), nrw)), - "vnom" => Vector{Float64}(defaults["kvs"]), - "snom" => Vector{Float64}(defaults["kvas"]), + "vm_nom" => Vector{Float64}(defaults["kvs"]), + "sm_nom" => Vector{Float64}(defaults["kvas"]), "configuration" => Vector{ConnConfig}(defaults["conns"]), "rs" => Vector{Float64}(defaults["%rs"] ./ 100), "noloadloss" => defaults["%noloadloss"] / 100, @@ -596,7 +596,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end # kvs, kvas - for (fr_key, to_key) in zip(["kv", "kva"], ["vnom", "snom"]) + for (fr_key, to_key) in zip(["kv", "kva"], ["vm_nom", "sm_nom"]) if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, "$(fr_key)s") && _is_after_xfmrcode(dss_obj["prop_order"], "$(fr_key)s")) || all(haskey(dss_obj, "$(fr_key)$(key_suffix)") && _is_after_xfmrcode(dss_obj["prop_order"], "$(fr_key)$(key_suffix)") for key_suffix in ["", "_2", "_3"]) eng_obj[to_key] = defaults["$(fr_key)s"] else diff --git a/src/io/utils.jl b/src/io/utils.jl index df984fa8e..ba86ca986 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -322,7 +322,7 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) _apply_xfmrcode!(tr, data_eng) end # across-phase properties should be the same to be eligible for banking - props = ["bus", "noloadloss", "xsc", "rs", "imag", "vnom", "snom", "polarity", "configuration"] + props = ["bus", "noloadloss", "xsc", "rs", "imag", "vm_nom", "sm_nom", "polarity", "configuration"] btrans = Dict{String, Any}(prop=>trs[1][prop] for prop in props) if !all(tr[prop]==btrans[prop] for tr in trs, prop in props) Memento.warn(_LOGGER, "Not all across-phase properties match among transfomers identified by bank='$bank', aborting attempt to bank") From 3ea057fdcf9f65ac16dd32722001bd2b65c25c37 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 07:59:34 -0600 Subject: [PATCH 198/224] ADD: support for AL2W transformer definition in eng2math --- src/data_model/eng2math.jl | 161 +++++++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 59 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index e3b75a43f..b93bd8f1e 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -273,77 +273,120 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic _apply_xfmrcode!(eng_obj, data_eng) - vnom = eng_obj["vm_nom"] * data_eng["settings"]["voltage_scale_factor"] - snom = eng_obj["sm_nom"] * data_eng["settings"]["power_scale_factor"] - - nrw = length(eng_obj["bus"]) - - # calculate zbase in which the data is specified, and convert to SI - zbase = (vnom.^2) ./ snom - - # x_sc is specified with respect to first winding - x_sc = eng_obj["xsc"] .* zbase[1] - - # rs is specified with respect to each winding - r_s = eng_obj["rs"] .* zbase - - g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 - b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 - - # data is measured externally, but we now refer it to the internal side - ratios = vnom/data_eng["settings"]["voltage_scale_factor"] - x_sc = x_sc./ratios[1]^2 - r_s = r_s./ratios.^2 - g_sh = g_sh*ratios[1]^2 - b_sh = b_sh*ratios[1]^2 - - # convert x_sc from list of upper triangle elements to an explicit dict - y_sh = g_sh + im*b_sh - z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) - - #TODO remove once moving out kron-reduction - dims = kron_reduced ? 3 : length(eng_obj["tm_set"][1]) - transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh, nphases=dims) - - for w in 1:nrw - # 2-WINDING TRANSFORMER - # make virtual bus and mark it for reduction - tm_nom = eng_obj["configuration"][w]==DELTA ? eng_obj["vm_nom"][w]*sqrt(3) : eng_obj["vm_nom"][w] - transformer_2wa_obj = Dict{String,Any}( - "name" => "_virtual_transformer.$name.$w", - "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", - "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], - "t_bus" => transformer_t_bus_w[w], - "tm_nom" => tm_nom, - "f_connections" => eng_obj["connections"][w], - "t_connections" => collect(1:dims+1), - "configuration" => eng_obj["configuration"][w], - "polarity" => eng_obj["polarity"][w], - "tm_set" => eng_obj["tm_set"][w], - "tm_fix" => eng_obj["tm_fix"][w], - "index" => length(data_math["transformer"])+1 + if haskey(eng_obj, "f_bus") && haskey(eng_obj, "t_bus") + @assert all(haskey(eng_obj, k) for k in ["f_bus", "t_bus", "f_connections", "t_connections"]) + + nphases = length(eng_obj["f_connections"]) + + math_obj = Dict{String,Any}( + "name" => name, + "source_id" => eng_obj["source_id"], + "f_bus" => data_math["bus_lookup"][eng_obj["f_bus"]], + "t_bus" => data_math["bus_lookup"][eng_obj["t_bus"]], + "f_connections" => eng_obj["f_connections"], + "t_connections" => eng_obj["t_connections"], + "configuration" => get(eng_obj, "configuration", WYE), + "tm_nom" => get(eng_obj, "tm_nom", 1.0), + "tm_set" => get(eng_obj, "tm_set", fill(1.0, nphases)), + "tm_fix" => get(eng_obj, "tm_fix", fill(true, nphases)), + "status" => Int(get(eng_obj, "status", ENABLED)), + "index" => length(data_math["transformer"])+1 ) - for prop in ["tm_lb", "tm_ub", "tm_step"] - if haskey(eng_obj, prop) - transformer_2wa_obj[prop] = eng_obj[prop][w] + for k in ["tm_lb", "tm_ub"] + if haskey(eng_obj, k) + math_obj[k] = eng_obj[k] end end if kron_reduced # TODO fix how padding works, this is a workaround to get bank working - if all(conf==WYE for conf in eng_obj["configuration"]) - f_connections = transformer_2wa_obj["f_connections"] - _pad_properties!(transformer_2wa_obj, ["tm_lb", "tm_ub", "tm_set"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) - _pad_properties!(transformer_2wa_obj, ["tm_fix"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=false) + if eng_obj["configuration"] == WYE + f_connections = math_obj["f_connections"] + _pad_properties!(math_obj, ["tm_lb", "tm_ub", "tm_set"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) + _pad_properties!(math_obj, ["tm_fix"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=false) - transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] + math_obj["f_connections"] = math_obj["t_connections"] end end - data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj + data_math["transformer"]["$(math_obj["index"])"] = math_obj + + push!(to_map, "transformer.$(math_obj["index"])") + else + vnom = eng_obj["vm_nom"] * data_eng["settings"]["voltage_scale_factor"] + snom = eng_obj["sm_nom"] * data_eng["settings"]["power_scale_factor"] + + nrw = length(eng_obj["bus"]) + + # calculate zbase in which the data is specified, and convert to SI + zbase = (vnom.^2) ./ snom + + # x_sc is specified with respect to first winding + x_sc = eng_obj["xsc"] .* zbase[1] + + # rs is specified with respect to each winding + r_s = eng_obj["rs"] .* zbase + + g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 + b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 + + # data is measured externally, but we now refer it to the internal side + ratios = vnom/data_eng["settings"]["voltage_scale_factor"] + x_sc = x_sc./ratios[1]^2 + r_s = r_s./ratios.^2 + g_sh = g_sh*ratios[1]^2 + b_sh = b_sh*ratios[1]^2 + + # convert x_sc from list of upper triangle elements to an explicit dict + y_sh = g_sh + im*b_sh + z_sc = Dict([(key, im*x_sc[i]) for (i,key) in enumerate([(i,j) for i in 1:nrw for j in i+1:nrw])]) + + #TODO remove once moving out kron-reduction + dims = kron_reduced ? 3 : length(eng_obj["tm_set"][1]) + transformer_t_bus_w = _build_loss_model!(data_math, name, to_map, r_s, z_sc, y_sh, nphases=dims) + + for w in 1:nrw + # 2-WINDING TRANSFORMER + # make virtual bus and mark it for reduction + tm_nom = eng_obj["configuration"][w]==DELTA ? eng_obj["vm_nom"][w]*sqrt(3) : eng_obj["vm_nom"][w] + transformer_2wa_obj = Dict{String,Any}( + "name" => "_virtual_transformer.$name.$w", + "source_id" => "_virtual_transformer.$(eng_obj["source_id"]).$w", + "f_bus" => data_math["bus_lookup"][eng_obj["bus"][w]], + "t_bus" => transformer_t_bus_w[w], + "tm_nom" => tm_nom, + "f_connections" => eng_obj["connections"][w], + "t_connections" => collect(1:dims+1), + "configuration" => eng_obj["configuration"][w], + "polarity" => eng_obj["polarity"][w], + "tm_set" => eng_obj["tm_set"][w], + "tm_fix" => eng_obj["tm_fix"][w], + "status" => Int(get(eng_obj, "status", ENABLED)), + "index" => length(data_math["transformer"])+1 + ) + + for prop in ["tm_lb", "tm_ub", "tm_step"] + if haskey(eng_obj, prop) + transformer_2wa_obj[prop] = eng_obj[prop][w] + end + end + + if kron_reduced + # TODO fix how padding works, this is a workaround to get bank working + if all(conf==WYE for conf in eng_obj["configuration"]) + f_connections = transformer_2wa_obj["f_connections"] + _pad_properties!(transformer_2wa_obj, ["tm_lb", "tm_ub", "tm_set"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=1.0) + _pad_properties!(transformer_2wa_obj, ["tm_fix"], f_connections[f_connections.!=kr_neutral], kr_phases; pad_value=false) - push!(to_map, "transformer.$(transformer_2wa_obj["index"])") + transformer_2wa_obj["f_connections"] = transformer_2wa_obj["t_connections"] + end + end + + data_math["transformer"]["$(transformer_2wa_obj["index"])"] = transformer_2wa_obj + + push!(to_map, "transformer.$(transformer_2wa_obj["index"])") + end end end end From 7f306575916b68b8ecda9b3e2554268c02040a56 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 09:36:49 -0600 Subject: [PATCH 199/224] ADD: al2w transformer component to create_transformer --- src/data_model/components.jl | 112 +++++++++++++++++++++++++++++++---- src/data_model/eng2math.jl | 6 +- 2 files changed, 105 insertions(+), 13 deletions(-) diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 2e6b64714..37dab727b 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -36,9 +36,19 @@ function add_object!(data_eng::Dict{String,<:Any}, obj_type::String, obj_id::Any end if obj_type == "transformer" - for (wdg, bus_id) in enumerate(object["bus"]) - if !haskey(data_eng["bus"], bus_id) - data_eng["bus"][bus_id] = create_bus(; terminals=object["connections"][wdg]) + if haskey(object, "f_bus") && haskey(object, "t_bus") + if !haskey(data_eng["bus"], object["f_bus"]) + data_eng["bus"][object["f_bus"]] = create_bus(; terminals=object["f_connections"]) + end + + if !haskey(data_eng["bus"], object["t_bus"]) + data_eng["bus"][object["t_bus"]] = create_bus(; terminals=object["t_connections"]) + end + else + for (wdg, bus_id) in enumerate(object["bus"]) + if !haskey(data_eng["bus"], bus_id) + data_eng["bus"][bus_id] = create_bus(; terminals=object["connections"][wdg]) + end end end else @@ -54,9 +64,7 @@ end "Instantiates a PowerModelsDistribution data model" -function Model(model_type::String=ENGINEERING; kwargs...)::Dict{String,Any} - kwargs = Dict{Symbol,Any}(kwargs) - +function Model(model_type::DataModel=ENGINEERING; kwargs...)::Dict{String,Any} if model_type == ENGINEERING data_model = Dict{String,Any}( "data_model" => model_type, @@ -64,7 +72,7 @@ function Model(model_type::String=ENGINEERING; kwargs...)::Dict{String,Any} "settings" => Dict{String,Any}( "voltage_scale_factor" => get(kwargs, :voltage_scale_factor, 1e3), "power_scale_factor" => get(kwargs, :power_scale_factor, 1e3), - "vbases_default" => get(kwargs, :vbases_default, Dict{<:Any,<:Real}()), + "vbases_default" => get(kwargs, :vbases_default, Dict{Any,Real}()), "sbase_default" => get(kwargs, :sbase_default, 1.0), "base_frequency" => get(kwargs, :basefreq, 60.0), ) @@ -265,12 +273,14 @@ function create_bus(; kwargs... )::Dict{String,Any} + # grounded = Vector{Bool}([terminal in grounded for terminal in terminals]) + bus = Dict{String,Any}( "status" => status, "terminals" => terminals, "grounded" => grounded, - "rg" => rg, - "xg" => xg, + "rg" => isempty(rg) ? fill(0.0, length(grounded)) : rg, + "xg" => isempty(xg) ? fill(0.0, length(grounded)) : xg, ) _add_unused_kwargs!(bus, kwargs) @@ -342,7 +352,7 @@ function create_generator(bus::Any, connections::Union{Vector{Int},Vector{String "status" => status, ) - for v in [("pg", pg), ("qg", qg), ("vg", vg), ("pg_lb", pg_lb), ("pg_ub", pg_ub), ("qg_lb", qg_lb), ("qg_ub", qg_ub)] + for (k,v) in [("pg", pg), ("qg", qg), ("vg", vg), ("pg_lb", pg_lb), ("pg_ub", pg_ub), ("qg_lb", qg_lb), ("qg_ub", qg_ub)] if !ismissing(v) @assert length(v) == n_conductors generator[k] = v @@ -368,8 +378,33 @@ function create_xfmrcode(; kwargs... )::Dict{String,Any} + n_windings = 0 + for v in [configurations, rw, tm_nom, tm_lb, tm_ub, tm_set, tm_fix] + if !ismissing(v) + n_windings = length(v) + break + end + end + + @assert n_windings >= 2 "Cannot determine valid number of windings" + @assert all(length(v) == n_windings for v in [configurations, rw, tm_nom, tm_lb, tm_ub, tm_set, tm_fix]) "Number of windings inconsistent between parameters" + + n_phases = 0 + for v in [tm_lb, tm_ub, tm_set, tm_fix] + if !ismissing(v) + n_windings = length(v[1]) + break + end + end + + @assert n_phases >= 1 "Cannot determine valid number of phases" + eng_obj = Dict{String,Any}( - # TODO + "configurations" => !ismissing(configurations) ? configurations : fill(WYE, n_windings), + "xsc" => !ismissing(xsc) ? xsc : zeros(Int(n_windings * (n_windings-1)//2)), + "rw" => !ismissing(rw) ? rw : zeros(n_windings), + "tm_nom" => !ismissing(tm_nom) ? tm_nom : ones(n_windings), + "tm_set" => !ismissing(tm_set) ? tm_set : fill(fill(1.0, )) ) return eng_obj @@ -426,6 +461,45 @@ function create_transformer(buses::Vector{Any}, connections::Vector{Union{Vector end +"creates a aysmmetric lossless 2-winding transformer object with some defaults" +function create_al2w_transformer(f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Vector{String}}, t_connections::Union{Vector{Int},Vector{String}}; + configuration::ConnConfig=WYE, + tm_nom::Real=1.0, + tm_lb::Union{Vector{<:Real},Missing}=missing, + tm_ub::Union{Vector{<:Real},Missing}=missing, + tm_set::Union{Vector{<:Real},Missing}=missing, + tm_fix::Union{Vector{Bool},Missing}=missing, + status::Status=ENABLED, + kwargs... + )::Dict{String,Any} + + n_conductors = length(f_connections) + n_conductors = configuration == WYE ? n_conductors-1 : n_conductors + + transformer = Dict{String,Any}( + "f_bus" => f_bus, + "t_bus" => t_bus, + "f_connections" => f_connections, + "t_connections" => t_connections, + "configuration" => configuration, + "tm_nom" => tm_nom, + "tm_set" => !ismissing(tm_set) ? tm_set : fill(1.0, n_conductors), + "tm_fix" => !ismissing(tm_fix) ? tm_fix : fill(true, n_conductors), + "status" => status, + ) + + for (k,v) in [("tm_lb", tm_lb), ("tm_ub", tm_ub)] + if !ismissing(v) + transformer[k] = v + end + end + + _add_unused_kwargs!(transformer, kwargs) + + return transformer +end + + "creates a generic shunt with some defaults" function create_shunt(bus, connections; gs::Union{Vector{<:Real},Missing}=missing, @@ -566,6 +640,21 @@ function delete_component!(data_eng::Dict{String,<:Any}, component_type::String, end end + +"Function to add default vbase for a bus" +function add_vbase_default!(data_eng::Dict{String,<:Any}, bus::Any, vbase::Real) + if !haskey(data_eng, "settings") + data_eng["settings"] = Dict{String,Any}() + end + + if !haskey(data_eng["settings"], "vbases_default") + data_eng["settings"]["vbases_default"] = Dict{Any,Real}() + end + + data_eng["settings"]["vbases_default"][bus] = vbase +end + + # Data objects add_bus!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "bus", id, create_bus(; kwargs...)) add_linecode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "linecode", id, create_linecode(; kwargs...)) @@ -575,6 +664,7 @@ add_xfmrcode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(da # Edge objects add_line!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Vector{String}}, t_connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "line", id, create_line(f_bus, t_bus, f_connections, t_connections; kwargs...)) add_transformer!(data_eng::Dict{String,<:Any}, id::Any, buses::Vector{<:Any}, connections::Vector{Union{Vector{Int},Vector{String}}}; kwargs...) = add_object!(data_eng, "transformer", id, create_transformer(buses, connections; kwargs...)) +add_transformer!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Vector{String}}, t_connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "transformer", id, create_al2w_transformer(f_bus, t_bus, f_connections, t_connections; kwargs...)) add_switch!(data_eng::Dict{String,<:Any}, id::Any, f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Vector{String}}, t_connections::Union{Vector{Int},Vector{String}}; kwargs...) = add_object!(data_eng, "switch", id, create_switch(f_bus, t_bus, f_connections, t_connections; kwargs...)) # Node objects diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index b93bd8f1e..921482e8c 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -274,7 +274,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic _apply_xfmrcode!(eng_obj, data_eng) if haskey(eng_obj, "f_bus") && haskey(eng_obj, "t_bus") - @assert all(haskey(eng_obj, k) for k in ["f_bus", "t_bus", "f_connections", "t_connections"]) + @assert all(haskey(eng_obj, k) for k in ["f_bus", "t_bus", "f_connections", "t_connections"]) "Incomplete definition of AL2W tranformer $name, aborting eng2math conversion" nphases = length(eng_obj["f_connections"]) @@ -289,6 +289,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic "tm_nom" => get(eng_obj, "tm_nom", 1.0), "tm_set" => get(eng_obj, "tm_set", fill(1.0, nphases)), "tm_fix" => get(eng_obj, "tm_fix", fill(true, nphases)), + "polarity" => fill(1, nphases), "status" => Int(get(eng_obj, "status", ENABLED)), "index" => length(data_math["transformer"])+1 ) @@ -586,8 +587,9 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["gen_status"] = Int(eng_obj["status"]) math_obj["control_mode"] = get(eng_obj, "control_mode", DROOP) + math_obj["pmax"] = get(eng_obj, "pg_ub", fill(Inf, nconductors)) - for (f_key, t_key) in [("qg_lb", "qmin"), ("qg_ub", "qmax"), ("pg_lb", "pmin"), ("pg_ub", "pmax")] + for (f_key, t_key) in [("qg_lb", "qmin"), ("qg_ub", "qmax"), ("pg_lb", "pmin")] if haskey(eng_obj, f_key) math_obj[t_key] = eng_obj[f_key] end From cea11bddf0baa80dd8de113e7ed360fd083b72a4 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 10:17:38 -0600 Subject: [PATCH 200/224] UPD: assertion failure text --- src/data_model/eng2math.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 921482e8c..06c8ed293 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -221,7 +221,7 @@ function _map_eng2math_line!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any t_bus = data_eng["bus"][eng_obj["t_bus"]] if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") + @assert all(eng_obj["f_connections"].==eng_obj["t_connections"]) "Kron reduction is only supported if f_connections == t_connections" filter = _kron_reduce_branch!(math_obj, ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], eng_obj["f_connections"], kr_neutral @@ -475,7 +475,7 @@ function _map_eng2math_switch!(data_math::Dict{String,<:Any}, data_eng::Dict{<:A merge!(branch_obj, _branch_obj) if kron_reduced - @assert(all(eng_obj["f_connections"].==eng_obj["t_connections"]), "Kron reduction is only supported if f_connections is the same as t_connections.") + @assert all(eng_obj["f_connections"].==eng_obj["t_connections"]) "Kron reduction is only supported if f_connections == t_connections" filter = _kron_reduce_branch!(branch_obj, ["br_r", "br_x"], ["g_fr", "b_fr", "g_to", "b_to"], eng_obj["f_connections"], kr_neutral @@ -554,7 +554,7 @@ function _map_eng2math_load!(data_math::Dict{String,<:Any}, data_eng::Dict{<:Any if kron_reduced if math_obj["configuration"]==WYE - @assert(connections[end]==kr_neutral) + @assert connections[end]==kr_neutral "for wye-connected loads, if kron_reduced the connections list should end with a neutral" _pad_properties!(math_obj, ["pd", "qd"], connections[connections.!=kr_neutral], kr_phases) else _pad_properties_delta!(math_obj, ["pd", "qd"], connections, kr_phases) @@ -601,7 +601,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ if kron_reduced if math_obj["configuration"]==WYE - @assert(connections[end]==kr_neutral) + @assert connections[end]==kr_neutral "For WYE connected generators, if kron_reduced the connections list should end with a neutral conductor" _pad_properties!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections[1:end-1], kr_phases) else _pad_properties_delta!(math_obj, ["pg", "qg", "vg", "pmin", "pmax", "qmin", "qmax"], connections, kr_phases) From 47a9e9a9f0f1fb2a74c0eb4cc0961f24c3edf734 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 10:41:29 -0600 Subject: [PATCH 201/224] UPD: transformations syntax in parse_file --- src/io/common.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/io/common.jl b/src/io/common.jl index b281253dd..8dc032da4 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -8,7 +8,7 @@ function parse_file( data_model::DataModel=ENGINEERING, import_all::Bool=false, bank_transformers::Bool=true, - transformations::Vector{<:Function}=Vector{Function}([]), + transforms::Union{Vector{<:Function},Vector{<:Tuple{<:Function, Vararg{Pair{String,<:Any}}}}}=Vector{Tuple{Function,Pair{String,Any}}}([]), build_multinetwork::Bool=false, kron_reduced::Bool=true, time_series::String="daily" @@ -21,8 +21,12 @@ function parse_file( time_series=time_series ) - for transformation in transformations - transformation(data_eng) + for transform in transforms + if isa(transform, Tuple) + transform[1](data_eng; [Symbol(k)=>v for (k,v) in transform[2:end]]...) + else + transform(data_eng) + end end if data_model == MATHEMATICAL From 4301d4b4ea9cd0c927c85afaa2587c62fdfdd7f4 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 11:54:45 -0600 Subject: [PATCH 202/224] DOC: Data model changes --- docs/make.jl | 4 +- docs/src/{data_model.md => eng-data-model.md} | 0 docs/src/eng2math.md | 24 ++- ...ta-formats.md => external-data-formats.md} | 46 +++-- docs/src/quickguide.md | 40 +++- docs/src/specifications.md | 172 +----------------- src/io/common.jl | 4 +- 7 files changed, 87 insertions(+), 203 deletions(-) rename docs/src/{data_model.md => eng-data-model.md} (100%) rename docs/src/{data-formats.md => external-data-formats.md} (59%) diff --git a/docs/make.jl b/docs/make.jl index fde2aa1e5..83d671730 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -10,7 +10,9 @@ makedocs( "Manual" => [ "Getting Started" => "quickguide.md", "Mathematical Model" => "math-model.md", - "Data Formats" => "data-formats.md", + "Engineering Data Model" => "eng-data-model.md", + "Conversion to Mathematical Model" => "eng2math.md", + "External Data Formats" => "external-data-formats.md", ], "Library" => [ "Network Formulations" => "formulations.md", diff --git a/docs/src/data_model.md b/docs/src/eng-data-model.md similarity index 100% rename from docs/src/data_model.md rename to docs/src/eng-data-model.md diff --git a/docs/src/eng2math.md b/docs/src/eng2math.md index 6b8474970..4a531cbb0 100644 --- a/docs/src/eng2math.md +++ b/docs/src/eng2math.md @@ -4,28 +4,36 @@ In this document we define the mapping from the engineering data model down to t ## `bus` +Buses are parsed into `bus` and potentially `shunt` objects + ## `line` +Lines are parsed into `branch` objects with `transformer=false` + ## `switch` -## `transformer` +Switches are parsed into `switch`. If there are loss parameters provided (_i.e._ `rs` and/or `xs`) then a virtual branch and virtual bus are created to model the impedance -## `line_reactor` +## `transformer` -## `series_capacitor` +Transformers are parsed into ayssmetric lossless 2-winding transformers. When parsing n-winding transformers with n>2 additionally virtual branches and buses are created to connect the new 2-winding transformers. Furthermore, if the loss parameters are non-zero, additional virtual buses and branches to model the transformer impedances ## `shunt` -### `shunt_capacitor` - -### `shunt_reactor` - -### `fault` +Shunts are parsed directly into `shunt` objects. ## `load` +Loads are parsed into `load` objects, with a specialized model that can be found in Load Model documentation on the sidebar + ## `generator` +Generators are parsed into `gen` objects + ## `solar` +Solar objects (photovoltaic systems) are parsed into `gen` objects + ## `voltage_source` + +Voltage sources are parsed into `gen` objects. If loss parameters are specified (_i.e._ `rs` and/or `xs`) then a virtual bus and branch are created to model the internal impedance diff --git a/docs/src/data-formats.md b/docs/src/external-data-formats.md similarity index 59% rename from docs/src/data-formats.md rename to docs/src/external-data-formats.md index f7b2f8b4a..f02bc50b3 100644 --- a/docs/src/data-formats.md +++ b/docs/src/external-data-formats.md @@ -1,4 +1,4 @@ -# Data Formats +# External Data Formats ## OpenDSS @@ -7,38 +7,46 @@ PowerModelsDistribution supports parsing OpenDSS format files. In particular, we - Line - Load - Generator -- Capactior +- Capactior (shunt capacitors only) - Reactor - Transformer - Linecode +- Xfmrcode +- Loadshape +- XYCurve - Circuit - VSource - PVSystem - Storage -Of those, a subset of configurations are converted into a PowerModelsDistribution internal data model, namely +Of those, a subset of configurations are converted into a PowerModelsDistribution internal data model, namely: -- Branch (from Lines (incl. Linecodes), Reactors) -- Transformer (arbitrary winding, all connections except zig-zag) -- Generator (from Generators, PVSystems) -- Load (incl. support for Const. Power, Const. Impedance, Const. Current models) -- Shunt (from Capacitors and Reactors) -- Storage +### Edge Elements + +- line (from lines and line reactors) +- transformer (arbitrary winding, all connections except zig-zag) +- switch (from lines w/ switch=y) + +### Node Elements + +- generator +- voltage_source +- solar (from PVSystem) +- load (incl. support for constant POWER, constant IMPEDANCE, constant CURRENT, and EXPONENTIAL models) +- shunt (from shunt capacitors and shunt reactors) +- storage -Except for a small subset, in general, commands are not support, e.g. `solve` or `calcvoltagebases` (this is done automatically on parse in PowerModelsDistribution). We support the following commands +### Data Elements -- `clear` -- `redirect` -- `compile` -- `set` -- `buscoords` -- `new` +- linecode +- xfmrcode +- time_series (from loadshapes) Several notes about the specific design choices w.r.t. OpenDSS are explained below. ### Circuit -The default connection to the transmission system is modeled as an ideal voltage source in OpenDSS; we chosen to model the trunk connection as a loosely bounded generator at a reference bus which is connected to the distribution network via a branch in order to model the inherent impedance of the voltage source. +The default connection to the transmission system is modeled as an ideal voltage source named "source" in OpenDSS, which is connected by default to a node named "sourcebus", but this can be changed. ### Lines @@ -52,6 +60,6 @@ Unfortunately, in the OpenDSS format, multi-phase transformers with different ta Capacitors and reactors are supported as shunts, although shunts to ground via delta connections are not yet supported. Furthermore, generic reactors are not supported, only those whose second terminal is wye connected to ground (default for unspecified second terminal). Reactors are also supported as a resistanceless line if their second terminal is connected, but only for topological continuity of the network. -## Matlab +## PowerModelsDistribution JSON -We also include a matlab-base format similar in conception to Matpower. This format is in development and details will come later. +You can export a PowerModelsDistribution data structure to a JSON file using the `print_file` command and parse one in using the `parse_file` command diff --git a/docs/src/quickguide.md b/docs/src/quickguide.md index 7e03117a8..723e0dbf7 100644 --- a/docs/src/quickguide.md +++ b/docs/src/quickguide.md @@ -1,5 +1,6 @@ # Quick Start Guide -Once PowerModelsDistribution is installed, Ipopt is installed, and a network data file (e.g. `"case5_c_r_a.m"` or `"case3_unbalanced.dss"` in the package folder under `./test/data`) has been acquired, an unbalanced AC Optimal Power Flow can be executed with, + +Once PowerModelsDistribution is installed, Ipopt is installed, and a network data file (e.g. `"case3_unbalanced.dss"` in the package folder under `./test/data`) has been acquired, an unbalanced AC Optimal Power Flow can be executed with, ```julia using PowerModelsDistribution @@ -8,9 +9,29 @@ using Ipopt run_ac_mc_opf("case3_unbalanced.dss", with_optimizer(Ipopt.Optimizer)) ``` +## Parsing files + +To parse an OpenDSS file into PowerModelsDistribution's default `ENGINEERING` format, use the `parse_file` command + +```julia +eng = parse_file("case3_unbalanced.dss") +``` + +To examine the `MATHEMATICAL` model it is possible to transform the data model using the `transform_data_model` command, but this step is not necessary to run a problem. + +```julia +math = transform_data_model(eng) +``` + ## Getting Results -The run commands in PowerModelsDistribution return detailed results data in the form of a dictionary. Results dictionaries from either Matpower-style `.m` or OpenDSS' `.dss` files will be identical in format. This dictionary can be saved for further processing as follows, +The run commands in PowerModelsDistribution return detailed results data in the form of a dictionary. This dictionary can be saved for further processing as follows, + +```julia +result = run_ac_mc_opf(eng, with_optimizer(Ipopt.Optimizer)) +``` + +Alternatively, you can pass the file path string directly: ```julia result = run_ac_mc_opf("case3_unbalanced.dss", with_optimizer(Ipopt.Optimizer)) @@ -18,30 +39,29 @@ result = run_ac_mc_opf("case3_unbalanced.dss", with_optimizer(Ipopt.Optimizer)) ## Accessing Different Formulations -The function "run_ac_mc_opf" is a shorthands for a more general formulation-independent OPF execution, "run_mc_opf". +The function "run_ac_mc_opf" is a short-hand for a more general formulation-independent OPF execution, "run_mc_opf". For example, `run_ac_mc_opf` is equivalent to, ```julia -using PowerModels -run_mc_opf("case3_unbalanced.dss", ACPPowerModel, with_optimizer(Ipopt.Optimizer)) +run_mc_opf(eng, ACPPowerModel, with_optimizer(Ipopt.Optimizer)) ``` -Note that PowerModels needs to be loaded to access formulations which are extended by PowerModelsDistribution, here "ACPPowerModel". The PowerModel "ACPPowerModel" indicates an AC formulation in polar coordinates. This more generic `run_mc_opf()` allows one to solve an OPF problem with any power network formulation implemented in PowerModels or PowerModelsDistribution. For example, the SDP relaxation of unbalanced Optimal Power Flow (branch flow model) can be run with, +`ACPPowerModel` indicates an AC formulation in polar coordinates. This more generic `run_mc_opf()` allows one to solve an OPF problem with any power network formulation implemented in PowerModels or PowerModelsDistribution. For example, the SDPUBFPowerModel relaxation of unbalanced Optimal Power Flow (branch flow model) can be run with, ```julia using SCS -run_mc_opf_bf("case3_unbalanced.dss", SDPUBFPowerModel, with_optimizer(SCS.Optimizer)) +run_mc_opf(eng, SDPUBFPowerModel, with_optimizer(SCS.Optimizer)) ``` Note that you have to use a SDP-capable solver, e.g. the open-source solver SCS, to solve SDP models. ## Inspecting the Formulation -The following example demonstrates how to break a `run_mc_opf` call into seperate model building and solving steps. This allows inspection of the JuMP model created by PowerModelsDistribution for the AC-OPF problem, +The following example demonstrates how to break a `run_mc_opf` call into seperate model building and solving steps. This allows inspection of the JuMP model created by PowerModelsDistribution for the AC-OPF problem. Note that the `MATHEMATICAL` model must be passed to `instantiate_model`, so the data model must either be transformed with `transform_data_model` or parsed directly to a `MATHEMATICAL` model using the `data_model` keyword argument: ```julia -data = PowerModelsDistribution.parse_file("case3_unbalanced.dss") -pm = PowerModels.instantiate_model(data, ACPPowerModel, PowerModelsDistribution.build_mc_opf; multiconductor=true, ref_extensions=[ref_add_arcs_trans!]) +math = parse_file("case3_unbalanced.dss"; data_model=MATHEMATICAL) +pm = instantiate_model(math, ACPPowerModel, build_mc_opf; ref_extensions=[ref_add_arcs_trans!]) print(pm.model) optimize_model!(pm, optimizer=with_optimizer(Ipopt.Optimizer)) ``` diff --git a/docs/src/specifications.md b/docs/src/specifications.md index 8d64946b3..d8ebf799d 100644 --- a/docs/src/specifications.md +++ b/docs/src/specifications.md @@ -1,16 +1,18 @@ # Problem Specifications +In addition to the standard power flow `run_mc_pf`, and optimal power flow `run_mc_opf`, there are several notable problem specifications included in PowerModelsDistribution + ## Optimal Power Flow (OPF) with On-Load Tap Changers (OLTC) This problem is identical to `mc_opf`, except that all transformers are now modelled as on-load tap changers (OLTCs). Each phase has an individual tap ratio, which can be either variable or fixed, as specified in the data model. -### Objective +### OLTC Objective ```julia objective_min_fuel_cost(pm) ``` -### Variables +### OLTC Variables ```julia variable_mc_voltage(pm) @@ -24,7 +26,7 @@ variable_mc_transformer_flow(pm) variable_mc_oltc_tap(pm) ``` -### Constraints +### OLTC Constraints ```julia constraint_mc_model_voltage(pm) @@ -58,168 +60,11 @@ for i in PMs.ids(pm, :transformer) end ``` -## Optimal Power Flow (OPF) with Load Models (LM) - -Unlike `mc_opf`, which models all loads as constant power loads, this problem specification additionally supports loads proportional to the voltage magnitude (a.k.a. constant current) and the square of the voltage magnitude (a.k.a. constant impedance). Each load now has associated active and reactive power variables. In `mc_opf`, loads are directly added as parameters in KCL. - -### Objective - -```julia -objective_min_fuel_cost(pm) -``` - -### Variables - -```julia -variable_mc_voltage(pm) -variable_mc_branch_flow(pm) - -for c in PMs.conductor_ids(pm) - PMs.variable_generation(pm, cnd=c) - PMs.variable_dcline_flow(pm, cnd=c) -end -variable_mc_transformer_flow(pm) -variable_mc_oltc_tap(pm) -``` - -### Constraints - -```julia -constraint_mc_model_voltage(pm) - -for i in PMs.ids(pm, :ref_buses) - constraint_mc_theta_ref(pm, i) -end - -for i in PMs.ids(pm, :bus) - constraint_mc_power_balance_load(pm, i) -end - -for id in PMs.ids(pm, :load) - model = PMs.ref(pm, pm.cnw, :load, id, "model") - if model=="constant_power" - constraint_mc_load_power_setpoint(pm, id) - elseif model=="proportional_vm" - constraint_mc_load_power_prop_vm(pm, id) - elseif model=="proportional_vmsqr" - constraint_mc_load_power_prop_vmsqr(pm, id) - else - Memento.@error(LOGGER, "Unknown model $model for load $id.") - end -end - -for i in PMs.ids(pm, :branch) - constraint_mc_ohms_yt_from(pm, i) - constraint_mc_ohms_yt_to(pm, i) - - for c in PMs.conductor_ids(pm) - PMs.constraint_voltage_angle_difference(pm, i, cnd=c) - - PMs.constraint_thermal_limit_from(pm, i, cnd=c) - PMs.constraint_thermal_limit_to(pm, i, cnd=c) - end -end - -for i in PMs.ids(pm, :dcline), c in PMs.conductor_ids(pm) - PMs.constraint_dcline(pm, i, cnd=c) -end - -for i in PMs.ids(pm, :transformer) - constraint_mc_transformer(pm, i) -end -``` - -## Power Flow (PF) with Load Models (LM) - -Unlike `mc_pf`, which models all loads as constant power loads, this problem specification additionally supports loads proportional to the voltage magnitude (a.k.a. constant current) and the square of the voltage magnitude (a.k.a. constant impedance). Each load now has associated active and reactive power variables. In `mc_pf`, loads are directly added as parameters in KCL. - -### Variables - -```julia -variable_mc_voltage(pm, bounded=false) -variable_mc_branch_flow(pm, bounded=false) -variable_mc_transformer_flow(pm, bounded=false) -variable_mc_load(pm) - -for c in PMs.conductor_ids(pm) - PMs.variable_generation(pm, bounded=false, cnd=c) - PMs.variable_dcline_flow(pm, bounded=false, cnd=c) -end -``` - -### Constraints - -```julia -constraint_mc_model_voltage(pm, bounded=false) - -for (i,bus) in PMs.ref(pm, :ref_buses) - constraint_mc_theta_ref(pm, i) - - for c in PMs.conductor_ids(pm) - @assert bus["bus_type"] == 3 - PMs.constraint_voltage_magnitude_setpoint(pm, i, cnd=c) - end -end - -for (i,bus) in PMs.ref(pm, :bus) - constraint_mc_power_balance_load(pm, i) - - for c in PM.conductor_ids(pm) - # PV Bus Constraints - if length(PMs.ref(pm, :bus_gens, i)) > 0 && !(i in PMs.ids(pm,:ref_buses)) - # this assumes inactive generators are filtered out of bus_gens - @assert bus["bus_type"] == 2 - - PMs.constraint_voltage_magnitude_setpoint(pm, i, cnd=c) - for j in PMs.ref(pm, :bus_gens, i) - PMs.constraint_active_gen_setpoint(pm, j, cnd=c) - end - end - end -end - -for id in PMs.ids(pm, :load) - model = PMs.ref(pm, pm.cnw, :load, id, "model") - if model=="constant_power" - constraint_mc_load_power_setpoint(pm, id) - elseif model=="proportional_vm" - constraint_mc_load_power_prop_vm(pm, id) - elseif model=="proportional_vmsqr" - constraint_mc_load_power_prop_vmsqr(pm, id) - else - Memento.@error(LOGGER, "Unknown model $model for load $id.") - end -end - -for i in PMs.ids(pm, :branch) - constraint_mc_ohms_yt_from(pm, i) - constraint_mc_ohms_yt_to(pm, i) -end - -for (i,dcline) in PMs.ref(pm, :dcline), c in PMs.conductor_ids(pm) - PMs.constraint_active_dcline_setpoint(pm, i, cnd=c) - - f_bus = PMs.ref(pm, :bus)[dcline["f_bus"]] - if f_bus["bus_type"] == 1 - PMs.constraint_voltage_magnitude_setpoint(pm, f_bus["index"], cnd=c) - end - - t_bus = PMs.ref(pm, :bus)[dcline["t_bus"]] - if t_bus["bus_type"] == 1 - PMs.constraint_voltage_magnitude_setpoint(pm, t_bus["index"], cnd=c) - end -end - -for i in PMs.ids(pm, :transformer) - constraint_mc_transformer(pm, i) -end -``` - ## Minimal Load Delta (MLD) Problem Specification Load shed (continuous) problem. See "Relaxations of AC Maximal Load Delivery for Severe Contingency Analysis" by C. Coffrin _et al._ (DOI: [10.1109/TPWRS.2018.2876507](https://ieeexplore.ieee.org/document/8494809)) for single-phase case. -### Variables +### MLD Variables ```math \begin{align} @@ -232,7 +77,7 @@ Load shed (continuous) problem. See "Relaxations of AC Maximal Load Delivery for \end{align} ``` -### Objective +### MLD Objective ```math \begin{align} @@ -240,6 +85,7 @@ Load shed (continuous) problem. See "Relaxations of AC Maximal Load Delivery for \sum_{\substack{i\in N,c\in C}}{10 \left (1-z^v_i \right )} + \sum_{\substack{i\in L,c\in C}}{10 \omega_{i,c}\left |\Re{\left (S^d_i\right )}\right |\left ( 1-z^d_i \right ) } + \sum_{\substack{i\in H,c\in C}}{\left | \Re{\left (S^s_i \right )}\right | \left (1-z^s_i \right ) } + \sum_{\substack{i\in G,c\in C}}{\Delta^g_i } + \sum_{\substack{i\in B,c\in C}}{\Delta^b_i} \right ) \end{align} ``` + where ```math @@ -251,7 +97,7 @@ where \end{align} ``` -### Constraints +### MLD Constraints ```math \begin{align} diff --git a/src/io/common.jl b/src/io/common.jl index 8dc032da4..542d20da9 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -8,7 +8,7 @@ function parse_file( data_model::DataModel=ENGINEERING, import_all::Bool=false, bank_transformers::Bool=true, - transforms::Union{Vector{<:Function},Vector{<:Tuple{<:Function, Vararg{Pair{String,<:Any}}}}}=Vector{Tuple{Function,Pair{String,Any}}}([]), + transformations::Union{Vector{<:Function},Vector{<:Tuple{<:Function, Vararg{Pair{String,<:Any}}}}}=Vector{Tuple{Function,Pair{String,Any}}}([]), build_multinetwork::Bool=false, kron_reduced::Bool=true, time_series::String="daily" @@ -21,7 +21,7 @@ function parse_file( time_series=time_series ) - for transform in transforms + for transform in transformations if isa(transform, Tuple) transform[1](data_eng; [Symbol(k)=>v for (k,v) in transform[2:end]]...) else From ed3418d138d00cd99c8cfef24ba57bae15c83089 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 11:56:34 -0600 Subject: [PATCH 203/224] DOC: data model page title --- docs/src/eng-data-model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/eng-data-model.md b/docs/src/eng-data-model.md index e4e28f8db..d70999606 100644 --- a/docs/src/eng-data-model.md +++ b/docs/src/eng-data-model.md @@ -1,4 +1,4 @@ -# Data model +# Engineering Data Model This document describes the `ENGINEERING` data model type in PowerModelsDistribution, which is transformed at runtime, or at the user's direction into a `MATHEMATICAL` data model for optimization. From 9c9fe900e867f59fb245e53e7ffbdac401cb17ef Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 13:38:12 -0600 Subject: [PATCH 204/224] DOC: update type hierarchies documentation --- docs/src/formulations.md | 93 +++++++++++++++++++++------------------- src/core/types.jl | 6 ++- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/docs/src/formulations.md b/docs/src/formulations.md index 6761db8b0..84c29c6c2 100644 --- a/docs/src/formulations.md +++ b/docs/src/formulations.md @@ -1,73 +1,80 @@ # Network Formulations ## Type Hierarchy -We begin with the top of the hierarchy, where we can distinguish between conic and non-conic power flow models. + +PowerModelsDistribution shares a rich model type hierarchy with PowerModels. The relevant abstract models from PowerModels are documented here for context. At the top of the type hierarchy, starting in PowerModels, we can distinguish between conic, active power only, and branch flow models: + +```julia +abstract type PowerModels.AbstractConicModel <: PowerModels.AbstractPowerModel end +abstract type PowerModels.AbstractActivePowerModel <: PowerModels.AbstractPowerModel end +abstract type PowerModels.AbstractBFModel <: PowerModels.AbstractPowerModel end +abstract type PowerModels.AbstractBFQPModel <: PowerModels.AbstractBFModel end +abstract type PowerModels.AbstractBFConicModel <: PowerModels.AbstractBFModel end +const PowerModels.AbstractConicModels = Union{PowerModels.AbstractConicModel, PowerModels.AbstractBFConicModel} +``` + +Several nonlinear (non-convex) models are available at the top level: + ```julia -PowerModels.AbstractConicModels = Union{PowerModels.AbstractConicModel, PowerModels.AbstractBFConicModel} -PowerModels.AbstractConicModel <: PowerModels.AbstractPowerModel -PowerModels.AbstractBFModel <: PowerModels.AbstractPowerModel -PowerModels.AbstractBFQPModel <: PowerModels.AbstractBFModel -PowerModels.AbstractBFConicModel <: PowerModels.AbstractBFModel +abstract type PowerModels.AbstractACPModel <: PowerModels.AbstractPowerModel end +abstract type PowerModels.AbstractACRModel <: PowerModels.AbstractPowerModel end +abstract type PowerModels.AbstractIVRModel <: PowerModels.AbstractACRModel end ``` -We begin with the top of the hierarchy, where we can distinguish between AC and DC power flow models. +In PowerModelsDistribution, the following relaxations are available under these hierarchies: + ```julia -PowerModels.AbstractACPModel <: PowerModels.AbstractPowerModel -PowerModels.AbstractDCPModel <: PowerModels.AbstractPowerModel -PowerModelsDistribution.AbstractNLPUBFModel <: PowerModels.AbstractBFQPModel -PowerModelsDistribution.AbstractConicUBFModel <: PowerModels.AbstractBFConicModel -PowerModelsDistribution.AbstractLPUBFModel <: PowerModelsDistribution.AbstractNLPUBFModel +abstract type PowerModelsDistribution.AbstractNLPUBFModel <: PowerModels.AbstractBFQPModel end +abstract type PowerModelsDistribution.AbstractConicUBFModel <: PowerModels.AbstractBFConicModel end +const PowerModelsDistribution.AbstractUBFModels = Union{PowerModelsDistribution.AbstractNLPUBFModel, PowerModelsDistribution.AbstractConicUBFModel} + +abstract type PowerModelsDistribution.SDPUBFModel <: PowerModelsDistribution.AbstractConicUBFModel end +abstract type PowerModelsDistribution.SDPUBFKCLMXModel <: PowerModelsDistribution.SDPUBFModel end +abstract type PowerModelsDistribution.SOCNLPUBFModel <: PowerModelsDistribution.AbstractNLPUBFModel end +abstract type PowerModelsDistribution.SOCConicUBFModel <: PowerModelsDistribution.AbstractConicUBFModel end +const PowerModelsDistribution.SOCUBFModels = Union{PowerModelsDistribution.SOCNLPUBFModel, PowerModelsDistribution.SOCConicUBFModel} ``` -From there, different Models are possible: +where `UBF` is an unbalanced variant of the __Branch Flow__ models from PowerModels. Models which do not contain `UBF` in their name are __Bus Injection__ Models _e.g._ `AbstractACPModel`. Finally, some linear unbalanced power flow models are available under the following hierarchy: + ```julia -#Bus injection models: -PowerModels.AbstractACPModel <: PowerModels.AbstractPowerModel -PowerModels.AbstractDCPModel <: PowerModels.AbstractPowerModel - -#Branch flow models: -PowerModelsDistribution.SDPUBFModel <: PowerModelsDistribution.AbstractConicUBFModel -PowerModelsDistribution.SOCNLPUBFModel <: PowerModelsDistribution.AbstractNLPUBFModel -PowerModelsDistribution.SOCConicUBFModel <: PowerModelsDistribution.AbstractConicUBFModel - -PowerModelsDistribution.LPLinUBFModel <: PowerModels.AbstractBFModel -PowerModelsDistribution.LPUBFFullModel <: PowerModelsDistribution.AbstractLPUBFModel -PowerModelsDistribution.LPUBFDiagModel <: PowerModelsDistribution.AbstractLPUBFModel +abstract type PowerModels.AbstractDCPModel <: PowerModels.AbstractActivePowerModel end +abstract type PowerModels.AbstractNFAModel <: PowerModels.AbstractDCPModel end +abstract type PowerModelsDistribution.AbstractLPUBFModel <: PowerModelsDistribution.AbstractNLPUBFModel end +abstract type PowerModelsDistribution.LPUBFDiagModel <: PowerModelsDistribution.AbstractLPUBFModel end +const PowerModelsDistribution.LinDist3FlowModel = PowerModelsDistribution.LPUBFDiagModel ``` ## Power Models + Each of these Models can be used as the type parameter for a PowerModel: + ```julia mutable struct PowerModels.ACPPowerModel <: PowerModels.AbstractACPModel PowerModels.@pm_fields end +mutable struct PowerModels.ACRPowerModel <: PowerModels.AbstractACRModel PowerModels.@pm_fields end mutable struct PowerModels.DCPPowerModel <: PowerModels.AbstractDCPModel PowerModels.@pm_fields end - -mutable struct PowerModels.SOCWRPowerModel <: PowerModels.SOCWRModel PowerModels.@pm_fields end +mutable struct PowerModels.NFAPowerModel <: PowerModels.AbstractNFAModel PowerModels.@pm_fields end mutable struct PowerModelsDistribution.SDPUBFPowerModel <: PowerModelsDistribution.SDPUBFModel PowerModels.@pm_fields end +mutable struct PowerModelsDistribution.SDPUBFKCLMXPowerModel <: PowerModelsDistribution.SDPUBFKCLMXModel PowerModels.@pm_fields end + mutable struct PowerModelsDistribution.SOCNLPUBFPowerModel <: PowerModelsDistribution.SOCNLPUBFModel PowerModels.@pm_fields end mutable struct PowerModelsDistribution.SOCConicUBFPowerModel <: PowerModelsDistribution.SOCConicUBFModel PowerModels.@pm_fields end -mutable struct PowerModelsDistribution.LPUBFFullPowerModel <: PowerModelsDistribution.LPUBFFullModel PowerModels.@pm_fields end mutable struct PowerModelsDistribution.LPUBFDiagPowerModel <: PowerModelsDistribution.LPUBFDiagModel PowerModels.@pm_fields end -mutable struct PowerModelsDistribution.LPLinUBFPowerModel <: PowerModelsDistribution.LPLinUBFModel PowerModels.@pm_fields end -``` - -## Union Types - -To support both conic and quadratically-constrained formulation variants for the unbalanced branch flow model, the union type `AbstractUBFModels` is defined. These formulations extend `AbstractBFModel` and are therefore also `AbstractWModels` (as defined in PowerModels proper). - -```julia -AbstractUBFModels = Union{AbstractNLPUBFModel, AbstractConicUBFModel} +const PowerModelsDistribution.LinDist3FlowPowerModel = PowerModelsDistribution.LPUBFDiagPowerModel ``` ## Optimization problem classes -- NLP (nonconvex): ACPPowerModel -- SDP: SDPUBFPowerModel -- SOC(-representable): SOCWRPowerModel, SOCNLPUBFPowerModel, SOCConicUBFPowerModel -- Linear: LPUBFFullPowerModel, LPUBFDiagPowerModel, LPLinUBFPowerModel, DCPPowerModel +- NLP (nonconvex): ACPPowerModel, ACRPowerModel, IVRPowerModel +- SDP: SDPUBFPowerModel, SDPUBFKCLMXPowerModel +- SOC(-representable): SOCNLPUBFPowerModel, SOCConicUBFPowerModel +- Linear: LPUBFDiagPowerModel (LinDist3FlowPowerModel), DCPPowerModel, NFAPowerModel ## Matrix equations versus scalar equations + JuMP supports vectorized syntax, but not for nonlinear constraints. Therefore, certain formulations must be implemented in a scalar fashion. Other formulations can be written as matrix (in)equalities. The current implementations are categorized as follows: -- Scalar: ACPPowerModel, DCPPowerModel, LPLinUBFPowerModel, SOCWRPowerModel -- Matrix: SDPUBFPowerModel, SOCNLPUBFPowerModel, SOCConicUBFPowerModel, LPUBFFullPowerModel, LPUBFDiagPowerModel + +- Scalar: ACPPowerModel, ACRPowerModel, IVRPowerModel, DCPPowerModel, NFAPowerMoel +- Matrix: SDPUBFPowerModel, SDPUBFKCLMXPowerModel, SOCNLPUBFPowerModel, SOCConicUBFPowerModel, LPUBFDiagPowerModel diff --git a/src/core/types.jl b/src/core/types.jl index 8bcf2bd82..a5234cb1c 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -64,7 +64,11 @@ SOCUBFModels = Union{SOCNLPUBFModel, SOCConicUBFModel} abstract type AbstractLPUBFModel <: AbstractNLPUBFModel end -"LinDist3Flow per Sankur et al 2016, using vector variables for power, voltage and current" +""" +LinDist3Flow per Arnold et al. (2016), using vector variables for power, voltage and current + +D. B. Arnold, M. Sankur, R. Dobbe, K. Brady, D. S. Callaway and A. Von Meier, "Optimal dispatch of reactive power for voltage regulation and balancing in unbalanced distribution systems," 2016 IEEE Power and Energy Society General Meeting (PESGM), Boston, MA, 2016, pp. 1-5, doi: 10.1109/PESGM.2016.7741261. +""" abstract type LPUBFDiagModel <: AbstractLPUBFModel end const LinDist3FlowModel = LPUBFDiagModel # more popular name for it From 8cc066e80dca3e3dd5dab95d0a466cb21fefc563 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 13:55:30 -0600 Subject: [PATCH 205/224] ADD: Enums documentation --- docs/make.jl | 1 + docs/src/enums.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 docs/src/enums.md diff --git a/docs/make.jl b/docs/make.jl index 83d671730..a83127670 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -11,6 +11,7 @@ makedocs( "Getting Started" => "quickguide.md", "Mathematical Model" => "math-model.md", "Engineering Data Model" => "eng-data-model.md", + "Enums in Engineering Model" => "enums.md", "Conversion to Mathematical Model" => "eng2math.md", "External Data Formats" => "external-data-formats.md", ], diff --git a/docs/src/enums.md b/docs/src/enums.md new file mode 100644 index 000000000..b86a27565 --- /dev/null +++ b/docs/src/enums.md @@ -0,0 +1,71 @@ +# PowerModelsDistribution Enum Types + +Within the PowerModelsDistribution Engineering Model we have included the use of Enums. Here we document the fields for which Enums are expected and the possible Enums available + +## Data Model + +Any place in PowerModelsDistribution that calls for specifying the `data_model`, either in function calls or the `"data_model"` field inside the data structure itself, will expect a `DataModel` type + +```julia +@enum DataModel ENGINEERING MATHEMATICAL DSS +``` + +The `DSS` data model is an output of `parse_dss`, and is an untranslated raw parse of a DSS file. This Enum exists for use by `count_nodes`, where the method to count the number of active nodes is different between all three models + +## Component statuses + +All `"status"` fields in the `ENGINEERING` model expect a `Status` type: + +```julia +@enum Status ENABLED DISABLED +``` + +## Connection Configurations + +All `"configuration"` fields in the `ENGINEERING` model expect a `ConnConfig` type: + +```julia +@enum ConnConfig WYE DELTA +``` + +## Load Models + +For `load` objects, the `"model"` field expects a `LoadModel` type to specify the type of load model to use: + +```julia +@enum LoadModel POWER CURRENT IMPEDANCE EXPONENTIAL ZIP +``` + +where `POWER` indicates constant power, `CURRENT` indicates constant current, `IMPEDANCE` indicates constant impedance, `EXPONENTIAL` indicates an exponential load model, and `ZIP` indicates a ZIP model + +## Shunt Models + +For `shunt` objects, the `"model"` field expects a `ShuntModel` type to specify the origin of the shunt object, which is important for transient analysis: + +```julia +@enum ShuntModel GENERIC CAPACITOR REACTOR +``` + +## Switch States + +For `switch` objects, the `"state"` field expects a `SwitchState` type to specify whether the switch is currently open or closed: + +```julia +@enum SwitchState OPEN CLOSED +``` + +## Dispatchable Components + +Some components can be dispatchable, _e.g._ if a switch is dispatchable that means it is free to open or close, but if not then it is fixed in place, or if a load is dispatchable it implies that it can be shed in a `run_mc_mld` problem: + +```julia +@enum Dispatchable NO YES +``` + +## Generator Control Modes + +For generator objects, the `"control_mode"` field expects a `ControlMode` type to specify whether the generator is operating in an isochronous mode (_i.e._ is frequency forming) or droop mode (_i.e._ is frequency following): + +```julia +@enum ControlMode DROOP ISOCHRONOUS +``` From 9f2c6c62143cd42056568a859496a5c48747c331 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 13:59:03 -0600 Subject: [PATCH 206/224] ADD: reference to examples --- README.md | 5 +++++ docs/src/quickguide.md | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/README.md b/README.md index 860620d79..bfef1c1ff 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ This enables the definition of a wide variety of power network formulations and - Continuous load shed, minimum load delta (mld) - ACP, LinDist3Flow, NFA - Optimal Power Flow with on-load tap-changer (opf_oltc) + - ACP **Note: The documentation is somewhat lagging behind development and the parings of network features with problem specifications with formulations has not been enumerated. We are working to correct this for you.** @@ -43,6 +44,10 @@ This enables the definition of a wide variety of power network formulations and - OpenDSS ".dss" files +## Examples + +Examples of how to use PowerModelsDistribution can be found in the main documentation and in Jupyter Notebooks inside the `/examples` directory + ## Development Community-driven development and enhancement of PowerModelsDistribution are welcome and encouraged. Please fork this repository and share your contributions to the master with pull requests. diff --git a/docs/src/quickguide.md b/docs/src/quickguide.md index 723e0dbf7..9edff17a4 100644 --- a/docs/src/quickguide.md +++ b/docs/src/quickguide.md @@ -65,3 +65,7 @@ pm = instantiate_model(math, ACPPowerModel, build_mc_opf; ref_extensions=[ref_ad print(pm.model) optimize_model!(pm, optimizer=with_optimizer(Ipopt.Optimizer)) ``` + +## Examples + +More examples of working with the engineering data model can be found in the `/examples` folder of the PowerModelsDistribution.jl repository. From 1cc773e0a060c9c934f8e637182d2b160a496764 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 16:00:12 -0600 Subject: [PATCH 207/224] FIX: "rs" -> "rw" for transformers --- src/data_model/checks.jl | 2 +- src/data_model/components.jl | 2 +- src/data_model/eng2math.jl | 2 +- src/data_model/transformations.jl | 18 ++++++++++-------- src/data_model/utils.jl | 2 +- src/io/opendss.jl | 10 +++++----- src/io/utils.jl | 2 +- 7 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/data_model/checks.jl b/src/data_model/checks.jl index 8af46c50b..77ec3d711 100644 --- a/src/data_model/checks.jl +++ b/src/data_model/checks.jl @@ -510,7 +510,7 @@ function _check_transformer(data_eng::Dict{String,<:Any}, name::Any) transformer = data_eng["transformer"][name] nrw = length(transformer["bus"]) - _check_has_size(transformer, ["bus", "connections", "vm_nom", "sm_nom", "configuration", "polarity", "rs", "tm_fix", "tm_set", "tm_lb", "tm_ub", "tm_step"], nrw, context="trans $name") + _check_has_size(transformer, ["bus", "connections", "vm_nom", "sm_nom", "configuration", "polarity", "rw", "tm_fix", "tm_set", "tm_lb", "tm_ub", "tm_step"], nrw, context="trans $name") @assert length(transformer["xsc"])==(nrw^2-nrw)/2 diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 37dab727b..4fa5901fa 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -235,7 +235,7 @@ function create_switch(f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int}, linecode::Any=missing, rs::Union{Matrix{<:Real},Missing}=missing, xs::Union{Matrix{<:Real},Missing}=missing, - dipatchable::Dispatchable=NO, + dispatchable::Dispatchable=NO, state::SwitchState=CLOSED, status::Status=ENABLED, kwargs... diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 06c8ed293..007f2f190 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -327,7 +327,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic x_sc = eng_obj["xsc"] .* zbase[1] # rs is specified with respect to each winding - r_s = eng_obj["rs"] .* zbase + r_s = eng_obj["rw"] .* zbase g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 diff --git a/src/data_model/transformations.jl b/src/data_model/transformations.jl index 9a929f4c6..bc9c4bc0d 100644 --- a/src/data_model/transformations.jl +++ b/src/data_model/transformations.jl @@ -3,7 +3,7 @@ const _loss_model_objects = Dict{String,Vector{String}}( "switch" => Vector{String}(["linecode", "rs", "xs"]), "voltage_source" => Vector{String}(["rs", "xs"]), - "transformer" => Vector{String}(["rs", "xsc", "imag", "noloadloss"]) + "transformer" => Vector{String}(["rw", "xsc", "imag", "noloadloss"]) ) @@ -34,14 +34,16 @@ function apply_voltage_bounds!(data_eng::Dict{String,<:Any}; vm_lb::Union{Real,M @assert data_eng["data_model"] == ENGINEERING "incorrect data model type" (bus_vbases, edge_vbases) = calc_voltage_bases(data_eng, data_eng["settings"]["vbases_default"]) - for (id,bus) in get(data_eng, "bus", Dict{Any,Dict{String,Any}}()) - vbase = bus_vbases[id] - if !ismissing(vm_lb) && !haskey(bus, "vm_lb") - bus["vm_lb"] = vbase .* fill(vm_lb, length(bus["terminals"])) - end + if haskey(data_eng, "bus") + for (id,bus) in data_eng["bus"] + vbase = bus_vbases[id] + if !ismissing(vm_lb) && !haskey(bus, "vm_lb") + bus["vm_lb"] = vbase .* fill(vm_lb, length(bus["terminals"])) + end - if !ismissing(vm_ub) && !haskey(bus, "vm_ub") - bus["vm_ub"] = vbase .* fill(vm_ub, length(bus["terminals"])) + if !ismissing(vm_ub) && !haskey(bus, "vm_ub") + bus["vm_ub"] = vbase .* fill(vm_ub, length(bus["terminals"])) + end end end end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index 014782940..ff7692783 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -481,7 +481,7 @@ function _apply_xfmrcode!(eng_obj::Dict{String,<:Any}, data_eng::Dict{String,<:A for (k, v) in xfmrcode if !haskey(eng_obj, k) eng_obj[k] = v - elseif haskey(eng_obj, k) && k in ["vm_nom", "sm_nom", "tm_set", "rs"] + elseif haskey(eng_obj, k) && k in ["vm_nom", "sm_nom", "tm_set", "rw"] for (w, vw) in enumerate(eng_obj[k]) if ismissing(vw) eng_obj[k][w] = v[w] diff --git a/src/io/opendss.jl b/src/io/opendss.jl index eb643fd5d..485b1f2b5 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -522,7 +522,7 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, "vm_nom" => Vector{Float64}(defaults["kvs"]), "sm_nom" => Vector{Float64}(defaults["kvas"]), "configuration" => Vector{ConnConfig}(defaults["conns"]), - "rs" => Vector{Float64}(defaults["%rs"] ./ 100), + "rw" => Vector{Float64}(defaults["%rs"] ./ 100), "noloadloss" => defaults["%noloadloss"] / 100, "imag" => defaults["%imag"] / 100, "xsc" => nrw == 2 ? [defaults["xhl"] / 100] : [defaults["xhl"], defaults["xht"], defaults["xlt"]] ./ 100, @@ -627,14 +627,14 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri # %rs if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, "%rs") && _is_after_xfmrcode(dss_obj["prop_order"], "%rs")) || all(haskey(dss_obj, k) && _is_after_xfmrcode(dss_obj["prop_order"], k) for k in ["%r", "%r_2", "%r_3"]) - eng_obj["rs"] = defaults["%rs"] / 100 + eng_obj["rw"] = defaults["%rs"] / 100 else for (w, key_suffix) in enumerate(["", "_2", "_3"]) if haskey(dss_obj, "%r$(key_suffix)") && _is_after_xfmrcode(dss_obj["prop_order"], "%r$(key_suffix)") - if !haskey(eng_obj, "rs") - eng_obj["rs"] = Vector{Any}(missing, nrw) + if !haskey(eng_obj, "rw") + eng_obj["rw"] = Vector{Any}(missing, nrw) end - eng_obj["rs"][w] = defaults["%rs"][defaults["wdg$(key_suffix)"]] / 100 + eng_obj["rw"][w] = defaults["%rs"][defaults["wdg$(key_suffix)"]] / 100 end end end diff --git a/src/io/utils.jl b/src/io/utils.jl index ba86ca986..a7712daf2 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -322,7 +322,7 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) _apply_xfmrcode!(tr, data_eng) end # across-phase properties should be the same to be eligible for banking - props = ["bus", "noloadloss", "xsc", "rs", "imag", "vm_nom", "sm_nom", "polarity", "configuration"] + props = ["bus", "noloadloss", "xsc", "rw", "imag", "vm_nom", "sm_nom", "polarity", "configuration"] btrans = Dict{String, Any}(prop=>trs[1][prop] for prop in props) if !all(tr[prop]==btrans[prop] for tr in trs, prop in props) Memento.warn(_LOGGER, "Not all across-phase properties match among transfomers identified by bank='$bank', aborting attempt to bank") From 478319536c49b46b28f63ccc0aa4bad25343ea56 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 16:00:34 -0600 Subject: [PATCH 208/224] FIX: transformations interface in parse_file --- src/io/common.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/io/common.jl b/src/io/common.jl index 542d20da9..d5eb7f5ee 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -8,7 +8,7 @@ function parse_file( data_model::DataModel=ENGINEERING, import_all::Bool=false, bank_transformers::Bool=true, - transformations::Union{Vector{<:Function},Vector{<:Tuple{<:Function, Vararg{Pair{String,<:Any}}}}}=Vector{Tuple{Function,Pair{String,Any}}}([]), + transformations::Vector{Any}=[], build_multinetwork::Bool=false, kron_reduced::Bool=true, time_series::String="daily" @@ -22,6 +22,8 @@ function parse_file( ) for transform in transformations + @assert isa(transform, Function) || isa(transform, Tuple{<:Function,Vararg{Pair{String,<:Any}}}) + if isa(transform, Tuple) transform[1](data_eng; [Symbol(k)=>v for (k,v) in transform[2:end]]...) else From c7eb1c9b4802791ab23395e63af8c00358d6d18f Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 16:00:54 -0600 Subject: [PATCH 209/224] FIX: calc_voltage_bases for eng model --- src/data_model/units.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data_model/units.jl b/src/data_model/units.jl index 61b910cc3..a7cce115f 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -152,7 +152,7 @@ function calc_voltage_bases(data_model::Dict{String,<:Any}, vbase_sources::Dict{ edge_elements = data_model["data_model"] == MATHEMATICAL ? _math_edge_elements : _eng_edge_elements bus_vbase = Dict([(bus,zone_vbase[zone]) for (bus,zone) in bus_to_zone]) - edge_vbase = Dict([("$edge_type.$id", bus_vbase["$(obj["f_bus"])"]) for edge_type in edge_elements[edge_elements .!= "transformer"] for (id,obj) in data_model[edge_type]]) + edge_vbase = Dict([("$edge_type.$id", bus_vbase["$(obj["f_bus"])"]) for edge_type in edge_elements[edge_elements .!= "transformer"] if haskey(data_model, edge_type) for (id,obj) in data_model[edge_type]]) return (bus_vbase, edge_vbase) end From 0400545851608530fdd45d32215a0b379819236c Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 16:12:45 -0600 Subject: [PATCH 210/224] DOC: example notebook --- docs/make.jl | 1 + docs/src/engineering_model.md | 728 +++++++++++++++++ examples/engineering_model.ipynb | 1248 ++++++++++++++---------------- src/io/common.jl | 2 +- 4 files changed, 1319 insertions(+), 660 deletions(-) create mode 100644 docs/src/engineering_model.md diff --git a/docs/make.jl b/docs/make.jl index a83127670..1c0bc33d6 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -14,6 +14,7 @@ makedocs( "Enums in Engineering Model" => "enums.md", "Conversion to Mathematical Model" => "eng2math.md", "External Data Formats" => "external-data-formats.md", + "Examples" => "engineering_model.md", ], "Library" => [ "Network Formulations" => "formulations.md", diff --git a/docs/src/engineering_model.md b/docs/src/engineering_model.md new file mode 100644 index 000000000..43952ad7f --- /dev/null +++ b/docs/src/engineering_model.md @@ -0,0 +1,728 @@ + +# Introduction to the PowerModelsDistribution Data Models + +In this notebook we introduce the engineering data model added to PowerModelsDistribution in version v0.9.0. We will give serveral examples of how to use this new data model directly, including new transformations that have become easier with it's introduction, how to convert it to the the lower-level mathematical model that was previously the only user interface we offered, and how to get various types of results using this new model. + +## Imports + +All commands in this document with no package namespace specified are directly exported by PowerModelsDistribution or already available in Julia base. Any commands that are only avaiable via an external package will be specified by including by using `import`, which will require specifying the originating package before the command, _e.g._ `Ipopt.Optimizer` as you will see below. + + +```julia +using PowerModelsDistribution +``` + + ┌ Info: Precompiling PowerModelsDistribution [d7431456-977f-11e9-2de3-97ff7677985e] + └ @ Base loading.jl:1260 + + +In these examples we will use the following optimization solvers, specified using `optimizer_with_attributes` from JuMP v0.21 + + +```julia +import Ipopt + +ipopt_solver = optimizer_with_attributes(Ipopt.Optimizer, "tol"=>1e-6, "print_level"=>0) +``` + + + + + MathOptInterface.OptimizerWithAttributes(Ipopt.Optimizer, Pair{MathOptInterface.AbstractOptimizerAttribute,Any}[MathOptInterface.RawParameter("tol") => 1.0e-6, MathOptInterface.RawParameter("print_level") => 0]) + + + +## Parsing Data + +Here we give the first example of how to parse data into the `ENGINEERING` data model structure, which is the default data structure type that the user will see without passing additional arguments, as we demonstrate later. + +We start with a 3 bus unbalanced load case provided as a dss file in the `test` folder of the PowerModelsDistribution.jl repository + + +```julia +eng = parse_file("../test/data/opendss/case3_unbalanced.dss") +``` + + [info | PowerModels]: Circuit has been reset with the "clear" on line 1 in "case3_unbalanced.dss" + [warn | PowerModels]: Command "calcvoltagebases" on line 35 in "case3_unbalanced.dss" is not supported, skipping. + [warn | PowerModels]: Command "solve" on line 37 in "case3_unbalanced.dss" is not supported, skipping. + + + + + + Dict{String,Any} with 9 entries: + "voltage_source" => Dict{Any,Any}("source"=>Dict{String,Any}("source_id"=>"vs… + "name" => "3bus_example" + "line" => Dict{Any,Any}("quad"=>Dict{String,Any}("cm_ub"=>[400.0, 4… + "settings" => Dict{String,Any}("sbase_default"=>500.0,"vbases_default"=… + "files" => ["case3_unbalanced.dss"] + "load" => Dict{Any,Any}("l2"=>Dict{String,Any}("source_id"=>"load.l… + "bus" => Dict{Any,Any}("primary"=>Dict{String,Any}("rg"=>Float64[]… + "linecode" => Dict{Any,Any}("556mcm"=>Dict{String,Any}("b_fr"=>[25.4648… + "data_model" => ENGINEERING + + + +Different information and warning messages will be given depending on the input file. In the case above, these messages all related to various parse notifications that arise during a parse of a dss file, and can be safely ignored + +The resulting data structure is a Julia dictionary. The first notable field is `"data_model"` which specifies which data model this data structure corresponds to, in this case `ENGINEERING`. This value is expected to be an `Enum` of type `DataModel` + +The next notable field is `"settings"`, which contains some important default/starting values for the distribution network + + +```julia +eng["settings"] +``` + + + + + Dict{String,Any} with 5 entries: + "sbase_default" => 500.0 + "vbases_default" => Dict("sourcebus"=>0.23094) + "voltage_scale_factor" => 1000.0 + "power_scale_factor" => 1000.0 + "base_frequency" => 50.0 + + + +- `"sbase_default"` is the starting value for the power base, +- `"vbases_default"` is the starting voltage base for the case, and multiple voltage bases can be specified, which would be useful in cases where there are multiple isolated islands with their own generation, +- `"voltage_scale_factor"` is a scaling factor for all voltage values, which in the case of OpenDSS is in kV by default +- `"power_scale_factor"` is a scaling factor for all power values +- `"base_frequency"` is the base frequency of the network in Hz, which is useful to know for mixed frequency networks + +Next we look at the `"bus"` components + + +```julia +eng["bus"] +``` + + + + + Dict{Any,Any} with 3 entries: + "primary" => Dict{String,Any}("rg"=>Float64[],"grounded"=>Int64[],"status"=… + "sourcebus" => Dict{String,Any}("rg"=>Float64[],"grounded"=>Int64[],"status"=… + "loadbus" => Dict{String,Any}("rg"=>[0.0],"grounded"=>[4],"status"=>ENABLED… + + + +We can see there are three buses in this system, identified by ids `"primary"`, `"sourcebus"`, and `"loadbus"`. + +__NOTE__: In Julia, order of Dictionary keys is not fixed, nor does it retain the order in which it was parsed like _e.g._ `Vectors`. + +Identifying components by non-integer names is a new feature of the `ENGINEERING` model, and makes network debugging more straightforward. + +__NOTE__: all names are converted to lowercase on parse from the originating dss file. + +Each bus component has the following properties in the `ENGINEERING` model + + +```julia +eng["bus"]["sourcebus"] +``` + + + + + Dict{String,Any} with 5 entries: + "rg" => Float64[] + "grounded" => Int64[] + "status" => ENABLED + "terminals" => [1, 2, 3] + "xg" => Float64[] + + + +- `"terminals"` indicates which terminals on the bus have active connections +- `"grounded"` indicates which terminals are grounded +- `"rg"` and `"xg"` indicate the grounding resistance and reactance of the ground +- `"status"` indicates whether a bus is `ENABLED` or `DISABLED`, and is specified for every component in the engineering model + +Next, we look at the `"line"` components, which is a generic name for both overhead lines and underground cables, which we do not differentiate between in the nomenclature + + +```julia +eng["line"] +``` + + + + + Dict{Any,Any} with 2 entries: + "quad" => Dict{String,Any}("cm_ub"=>[400.0, 400.0, 400.0],"cm_ub_c"=>[600.0… + "ohline" => Dict{String,Any}("cm_ub"=>[400.0, 400.0, 400.0],"cm_ub_c"=>[600.0… + + + + +```julia +eng["line"]["quad"] +``` + + + + + Dict{String,Any} with 11 entries: + "cm_ub" => [400.0, 400.0, 400.0] + "cm_ub_c" => [600.0, 600.0, 600.0] + "f_connections" => [1, 2, 3] + "length" => 1.0 + "status" => ENABLED + "source_id" => "line.quad" + "t_connections" => [1, 2, 3] + "f_bus" => "primary" + "t_bus" => "loadbus" + "cm_ub_b" => [600.0, 600.0, 600.0] + "linecode" => "4/0quad" + + + +Again, we see components identified by their OpenDSS names. A `"line"` is an edge object, which will always have the following properties: + +- `"f_bus"` +- `"t_bus"` +- `"f_connections"` - list of terminals to which the line is connected on the from-side +- `"t_connections"` - list of terminals to which the line is connected on the to-side + +Here we are also introduced to two important concepts, the `"source_id"`, which is an easy way to identify from where an object originates in the dss file, and a data type element, pointed to by `"linecode"` in this case. + +A data type element is an element that does not represent a real engineering object, but only contains data that one of those real objects can refer to, in this case a linecode, which contains information like line resistance/reactance and conductance/susceptance. + + +```julia +eng["linecode"]["4/0quad"] +``` + + + + + Dict{String,Any} with 6 entries: + "b_fr" => [25.4648 -0.0 -0.0; -0.0 25.4648 -0.0; -0.0 -0.0 25.4648] + "rs" => [0.1167 0.0467 0.0467; 0.0467 0.1167 0.0467; 0.0467 0.0467 0.1167] + "xs" => [0.0667 0.0267 0.0267; 0.0267 0.0667 0.0267; 0.0267 0.0267 0.0667] + "b_to" => [25.4648 -0.0 -0.0; -0.0 25.4648 -0.0; -0.0 -0.0 25.4648] + "g_to" => [0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0] + "g_fr" => [0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0] + + + +Next, we introduce a node element, the `"load"` object, where we also see the first example of a specification of less than three phases at a time + + +```julia +eng["load"]["l1"] +``` + + + + + Dict{String,Any} with 10 entries: + "source_id" => "load.l1" + "qd_nom" => [3.0] + "status" => ENABLED + "model" => POWER + "connections" => [1, 4] + "vm_nom" => 0.23094 + "pd_nom" => [9.0] + "dispatchable" => NO + "bus" => "loadbus" + "configuration" => WYE + + + +We can see that the length of the Vectors for `"pd_nom"` and `"qd_nom"` are only one, although the number of terminals listed in `"connections"` is two. This is because the connection is WYE, and therefore the final connection is a grounded neutral + +Here we are also introduced to two new Enums, `WYE`, which gives the connection configuration, and `NO` under dispatchable, which indicates that if this case were used in an MLD problem, _i.e._ with `run_mc_mld` that this load would not be sheddable. + +Finally, we show the generation source for this case, which in opendss is a voltage source named `"source"` + + +```julia +eng["voltage_source"]["source"] +``` + + + + + Dict{String,Any} with 8 entries: + "source_id" => "vsource.source" + "rs" => [4.27691e-8 3.96342e-9 3.96342e-9; 3.96342e-9 4.27691e-8 3.9… + "va" => [0.0, -120.0, 120.0] + "status" => ENABLED + "connections" => [1, 2, 3] + "vm" => [0.229993, 0.229993, 0.229993] + "xs" => [1.54178e-7 -1.04497e-9 -1.04497e-9; -1.04497e-9 1.54178e-7 … + "bus" => "sourcebus" + + + +- `"vm"` - specifies the fixed voltage magnitudes per phase at the bus +- `"va"` - specifies the fixed reference angles per phases at the bus +- `"rs"` and `"xs"` specifies internal impedances of the voltage source + +### Importing raw dss properties + +In case there are additional properties that you want to use from dss, it is possible to import those directly into the `ENGINEERING` (and `MATHEMATICAL`) data structure with the `import_all` keyword argument + + +```julia +eng_all = parse_file("../test/data/opendss/case3_unbalanced.dss"; import_all=true) + +eng_all["line"] +``` + + [info | PowerModels]: Circuit has been reset with the "clear" on line 1 in "case3_unbalanced.dss" + [warn | PowerModels]: Command "calcvoltagebases" on line 35 in "case3_unbalanced.dss" is not supported, skipping. + [warn | PowerModels]: Command "solve" on line 37 in "case3_unbalanced.dss" is not supported, skipping. + + + + + + Dict{Any,Any} with 2 entries: + "quad" => Dict{String,Any}("cm_ub"=>[400.0, 400.0, 400.0],"cm_ub_c"=>[600.0… + "ohline" => Dict{String,Any}("cm_ub"=>[400.0, 400.0, 400.0],"cm_ub_c"=>[600.0… + + + +You will note the presence of `"dss"` dictionaries under components, and `"dss_options"` at the root level + +## Running Optimal Power Flow + +In this section we introduce how to run an optimal power flow (opf) in PowerModelsDistribution on an engineering data model + +In order to run an OPF problem you will need + +1. a data model +2. a formulation +3. a solver + +In these examples we will use the `eng` model we worked with above, the `ACPPowerModel`, which is a AC power flow formulation in polar coordinates, and the `ipopt_solver` we already defined above + + +```julia +result = run_mc_opf(eng, ACPPowerModel, ipopt_solver) +``` + + [warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0] + + ****************************************************************************** + This program contains Ipopt, a library for large-scale nonlinear optimization. + Ipopt is released as open source code under the Eclipse Public License (EPL). + For more information visit http://projects.coin-or.org/Ipopt + ****************************************************************************** + + + + + + + Dict{String,Any} with 8 entries: + "solve_time" => 3.9884 + "optimizer" => "Ipopt" + "termination_status" => LOCALLY_SOLVED + "dual_status" => FEASIBLE_POINT + "primal_status" => FEASIBLE_POINT + "objective" => 0.0214812 + "solution" => Dict{String,Any}("voltage_source"=>Dict{Any,Any}("sou… + "objective_lb" => -Inf + + + +The result of `run_mc_opf` will be very familiar to those who are already familiar with PowerModels and PowerModelsDistribution. The notable difference will be in the `"solution"` dictionary + + +```julia +result["solution"] +``` + + + + + Dict{String,Any} with 6 entries: + "voltage_source" => Dict{Any,Any}("source"=>Dict{String,Any}("qg_bus"=>[3.177… + "line" => Dict{Any,Any}("quad"=>Dict{String,Any}("qf"=>[3.09488, 3.… + "settings" => Dict{String,Any}("sbase"=>0.5) + "load" => Dict{Any,Any}("l2"=>Dict{String,Any}("qd_bus"=>[0.0, 3.0,… + "bus" => Dict{Any,Any}("primary"=>Dict{String,Any}("va"=>[-0.22425… + "per_unit" => false + + + +Here you can see that the solution comes back out by default into the same data model as is provided by the user to the `run_` command, as well as being in SI units, as opposed to per unit, which is used during the solve. For example, + + +```julia +result["solution"]["bus"]["loadbus"] +``` + + + + + Dict{String,Any} with 2 entries: + "va" => [-0.484238, -120.243, 120.274] + "vm" => [0.222521, 0.226727, 0.225577] + + + +If for some reason you want to return the result in per-unit rather than SI, you can specify this in the `run_` command by + + +```julia +result_pu = run_mc_opf(eng, ACPPowerModel, ipopt_solver; make_si=false) + +result_pu["solution"]["bus"]["loadbus"] +``` + + [warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0] + + + + + + Dict{String,Any} with 2 entries: + "va" => [-0.484238, -120.243, 120.274] + "vm" => [0.963546, 0.981757, 0.976779] + + + +### Branch Flow formulations + +Previously, to use a branch flow formulation, such as `SOCNLPUBFPowerModel`, it was required to use a different `run_` command, but now, by using multiple dispatch we have simplified this for the user + + +```julia +result_bf = run_mc_opf(eng, SOCNLPUBFPowerModel, ipopt_solver) +``` + + [warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0] + + + + + + Dict{String,Any} with 8 entries: + "solve_time" => 0.190441 + "optimizer" => "Ipopt" + "termination_status" => LOCALLY_SOLVED + "dual_status" => FEASIBLE_POINT + "primal_status" => FEASIBLE_POINT + "objective" => 0.0211303 + "solution" => Dict{String,Any}("voltage_source"=>Dict{Any,Any}("sou… + "objective_lb" => -Inf + + + +## Engineering Model Transformations + +One of the power things about the engineering model is that data transformations are much more simple. Here we illustrate two examples that are currently included in PowerModelsDistribution, but writing your own data transformation functions will be trivial, as we will show + +First, there are several objects that have loss models by default when parsing from dss files, such as voltage sources, transformers, and switches. To remove these loss models, therefore making these components lossless, we can use the included `make_lossess!` function. Here we use a basic 2-winding wye-wye connected transformer case from `test` to illustrate this + + +```julia +eng_ut = parse_file("../test/data/opendss/ut_trans_2w_yy.dss") + +eng_ut["transformer"]["tx1"] +``` + + [info | PowerModels]: Circuit has been reset with the "clear" on line 1 in "ut_trans_2w_yy.dss" + [warn | PowerModels]: Command "calcvoltagebases" on line 35 in "ut_trans_2w_yy.dss" is not supported, skipping. + [warn | PowerModels]: Command "solve" on line 38 in "ut_trans_2w_yy.dss" is not supported, skipping. + + + + + + Dict{String,Any} with 16 entries: + "polarity" => [1, 1] + "sm_nom" => [500.0, 500.0] + "tm_lb" => [[0.9, 0.9, 0.9], [0.9, 0.9, 0.9]] + "connections" => [[1, 2, 3, 4], [1, 2, 3, 4]] + "tm_set" => [[1.02, 1.02, 1.02], [0.97, 0.97, 0.97]] + "tm_step" => [[0.03125, 0.03125, 0.03125], [0.03125, 0.03125, 0.03125]] + "bus" => ["1", "2"] + "configuration" => ConnConfig[WYE, WYE] + "noloadloss" => 0.05 + "xsc" => [0.05] + "source_id" => "transformer.tx1" + "rw" => [0.01, 0.02] + "tm_fix" => Array{Bool,1}[[1, 1, 1], [1, 1, 1]] + "vm_nom" => [11.0, 4.0] + "tm_ub" => [[1.1, 1.1, 1.1], [1.1, 1.1, 1.1]] + "imag" => 0.11 + + + +We can see that `"noloadloss"`, `"rw"`, and `"imag"` are non-zero, but if we apply the `make_lossless!` function we can see these parameters are set to zero, effectively eliminating the losses + + +```julia +make_lossless!(eng_ut) + +eng_ut["transformer"]["tx1"] +``` + + + + + Dict{String,Any} with 16 entries: + "polarity" => [1, 1] + "sm_nom" => [500.0, 500.0] + "tm_lb" => [[0.9, 0.9, 0.9], [0.9, 0.9, 0.9]] + "connections" => [[1, 2, 3, 4], [1, 2, 3, 4]] + "tm_set" => [[1.02, 1.02, 1.02], [0.97, 0.97, 0.97]] + "tm_step" => [[0.03125, 0.03125, 0.03125], [0.03125, 0.03125, 0.03125]] + "bus" => ["1", "2"] + "configuration" => ConnConfig[WYE, WYE] + "noloadloss" => 0.0 + "xsc" => [0.0] + "source_id" => "transformer.tx1" + "rw" => [0.0, 0.0] + "tm_fix" => Array{Bool,1}[[1, 1, 1], [1, 1, 1]] + "vm_nom" => [11.0, 4.0] + "tm_ub" => [[1.1, 1.1, 1.1], [1.1, 1.1, 1.1]] + "imag" => 0.0 + + + +Alternatively, we can apply this function at parse + + +```julia +eng_ut = parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; transformations=[make_lossless!]) + +eng_ut["transformer"]["tx1"] +``` + + [info | PowerModels]: Circuit has been reset with the "clear" on line 1 in "ut_trans_2w_yy.dss" + [warn | PowerModels]: Command "calcvoltagebases" on line 35 in "ut_trans_2w_yy.dss" is not supported, skipping. + [warn | PowerModels]: Command "solve" on line 38 in "ut_trans_2w_yy.dss" is not supported, skipping. + + + + + + Dict{String,Any} with 16 entries: + "polarity" => [1, 1] + "sm_nom" => [500.0, 500.0] + "tm_lb" => [[0.9, 0.9, 0.9], [0.9, 0.9, 0.9]] + "connections" => [[1, 2, 3, 4], [1, 2, 3, 4]] + "tm_set" => [[1.02, 1.02, 1.02], [0.97, 0.97, 0.97]] + "tm_step" => [[0.03125, 0.03125, 0.03125], [0.03125, 0.03125, 0.03125]] + "bus" => ["1", "2"] + "configuration" => ConnConfig[WYE, WYE] + "noloadloss" => 0.0 + "xsc" => [0.0] + "source_id" => "transformer.tx1" + "rw" => [0.0, 0.0] + "tm_fix" => Array{Bool,1}[[1, 1, 1], [1, 1, 1]] + "vm_nom" => [11.0, 4.0] + "tm_ub" => [[1.1, 1.1, 1.1], [1.1, 1.1, 1.1]] + "imag" => 0.0 + + + +Another transformation function included in PowerModelsDistribution is the `apply_voltage_bounds!` function, which will apply some voltage bounds in SI units, given some percent value, _e.g._ if we want the lower bound on voltage to be `0.9` and upper bound `1.1` after per-unit conversion + + +```julia +apply_voltage_bounds!(eng_ut; vm_lb=0.9, vm_ub=1.1) + +eng_ut["bus"]["2"] +``` + + + + + Dict{String,Any} with 7 entries: + "rg" => [0.0] + "grounded" => [4] + "status" => ENABLED + "terminals" => [1, 2, 3, 4] + "vm_ub" => [2.54034, 2.54034, 2.54034, 2.54034] + "vm_lb" => [2.07846, 2.07846, 2.07846, 2.07846] + "xg" => [0.0] + + + +Alternatively, this can be specified at parse by + + +```julia +eng_ut = parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; transformations=[make_lossless!, (apply_voltage_bounds!, "vm_lb"=>0.9, "vm_ub"=>1.1)]) + +eng_ut["bus"]["2"] +``` + + [info | PowerModels]: Circuit has been reset with the "clear" on line 1 in "ut_trans_2w_yy.dss" + [warn | PowerModels]: Command "calcvoltagebases" on line 35 in "ut_trans_2w_yy.dss" is not supported, skipping. + [warn | PowerModels]: Command "solve" on line 38 in "ut_trans_2w_yy.dss" is not supported, skipping. + + + + + + Dict{String,Any} with 7 entries: + "rg" => [0.0] + "grounded" => [4] + "status" => ENABLED + "terminals" => [1, 2, 3, 4] + "vm_ub" => [2.54034, 2.54034, 2.54034, 2.54034] + "vm_lb" => [2.07846, 2.07846, 2.07846, 2.07846] + "xg" => [0.0] + + + +## Mathematical Model + +In this section we introduce the mathematical model, which was the previous user-facing model in PowerModelsDistribution, explain how conversions between the model happen in practice, and give an example of how to do this conversion manually + +In practice, unless the user is interested, the conversion between the `ENGINEERING` and `MATHEMATICAL` models should be seemless and invisible to the user. By providing an `ENGINEERING` model to a `run_` command the `run_mc_model` command will know to convert the model to `MATHEMATICAL`, which will be used to the generate the JuMP model that will actually be optimized. Similarly, the solution generated by this optimization will be automatically converted back to the format of the `ENGINEERING` model. + +Let's first take a look at how to convert to the `MATHEMATICAL` model + + +```julia +math = transform_data_model(eng) +``` + + [warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0] + + + + + + Dict{String,Any} with 18 entries: + "bus" => Dict{String,Any}("4"=>Dict{String,Any}("grounded"=>Bool[0, 0… + "name" => "3bus_example" + "dcline" => Dict{String,Any}() + "map" => Dict{String,Any}[Dict("unmap_function"=>"_map_math2eng_root!… + "settings" => Dict{String,Any}("sbase_default"=>500.0,"vbases_default"=>Di… + "gen" => Dict{String,Any}("1"=>Dict{String,Any}("pg"=>[0.0, 0.0, 0.0]… + "branch" => Dict{String,Any}("1"=>Dict{String,Any}("br_r"=>[1.09406 0.43… + "storage" => Dict{String,Any}() + "switch" => Dict{String,Any}() + "basekv" => 0.23094 + "baseMVA" => 0.5 + "conductors" => 3 + "per_unit" => true + "data_model" => MATHEMATICAL + "shunt" => Dict{String,Any}() + "transformer" => Dict{String,Any}() + "bus_lookup" => Dict{Any,Int64}("primary"=>1,"sourcebus"=>2,"loadbus"=>3) + "load" => Dict{String,Any}("1"=>Dict{String,Any}("model"=>POWER,"conne… + + + +There are a couple of things to notice right away. First, the data model transform automatically converts the model to per-unit. Second, there are a lot of empty component sets, whereas in the `ENGINEERING` model, only component types that had components in them were listed. In the `MATHEMATICAL` model certain component dictionaries are always expected to exist, and the `eng2math` conversion functions will automatically populate these. + +Next, there are a few unusal fields, such as `"settings"`, which previously didn't exist in the `MATHEMATICAL` model. This is used for the per-unit conversion specifically in PowerModelsDistribution. Also, is the `"map"` field, which is a `Vector` of Dictionaries that enable the conversion back to `ENGINEERING` from `MATHEMATICAL`. Without this it would be impossible to convert back, and in fact only the solution can be converted, because some properties are combined destructively during the conversion to the `MATHEMATICAL` model, and therefore cannot be reverse engineered. However, since the conversion to `MATHEMATICAL` is not in-place, you will always have a copy of `eng` alongside `math`. + +Here is an example of one of the `"map"` entries + + +```julia +math["map"][end] +``` + + + + + Dict{String,Any} with 3 entries: + "to" => ["gen.1", "bus.4", "branch.3"] + "from" => "source" + "unmap_function" => "_map_math2eng_voltage_source!" + + + +Alternatively, the `MATHEMATICAL` model can be returned directly from the `parse_file` command with the `data_model` keyword argument + + +```julia +math = parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model=MATHEMATICAL) +``` + + [info | PowerModels]: Circuit has been reset with the "clear" on line 1 in "case3_unbalanced.dss" + [warn | PowerModels]: Command "calcvoltagebases" on line 35 in "case3_unbalanced.dss" is not supported, skipping. + [warn | PowerModels]: Command "solve" on line 37 in "case3_unbalanced.dss" is not supported, skipping. + [warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0] + + + + + + Dict{String,Any} with 18 entries: + "bus" => Dict{String,Any}("4"=>Dict{String,Any}("grounded"=>Bool[0, 0… + "name" => "3bus_example" + "dcline" => Dict{String,Any}() + "map" => Dict{String,Any}[Dict("unmap_function"=>"_map_math2eng_root!… + "settings" => Dict{String,Any}("sbase_default"=>500.0,"vbases_default"=>Di… + "gen" => Dict{String,Any}("1"=>Dict{String,Any}("pg"=>[0.0, 0.0, 0.0]… + "branch" => Dict{String,Any}("1"=>Dict{String,Any}("br_r"=>[1.09406 0.43… + "storage" => Dict{String,Any}() + "switch" => Dict{String,Any}() + "basekv" => 0.23094 + "baseMVA" => 0.5 + "conductors" => 3 + "per_unit" => true + "data_model" => MATHEMATICAL + "shunt" => Dict{String,Any}() + "transformer" => Dict{String,Any}() + "bus_lookup" => Dict{Any,Int64}("primary"=>1,"sourcebus"=>2,"loadbus"=>3) + "load" => Dict{String,Any}("1"=>Dict{String,Any}("model"=>POWER,"conne… + + + +### Running `MATHEMATICAL` models + +There is very little difference from the user point-of-view in running `MATHEMATICAL` models other than the results will not be automatically converted back to the the format of the `ENGINEERING` model + + +```julia +result_math = run_mc_opf(math, ACPPowerModel, ipopt_solver) + +result_math["solution"] +``` + + + + + Dict{String,Any} with 7 entries: + "baseMVA" => 0.5 + "branch" => Dict{String,Any}("1"=>Dict{String,Any}("qf"=>[0.00618975, 0.0… + "gen" => Dict{String,Any}("1"=>Dict{String,Any}("qg_bus"=>[0.00635515,… + "load" => Dict{String,Any}("1"=>Dict{String,Any}("qd_bus"=>[0.0, 0.006,… + "conductors" => 3 + "bus" => Dict{String,Any}("4"=>Dict{String,Any}("va"=>[0.0, -2.0944, 2… + "per_unit" => true + + + +It is also possible to manually convert the solution back to the `ENGINEERING` format, provided you have the __map__ + + +```julia +result_eng = transform_solution(result_math["solution"], math) +``` + + + + + Dict{String,Any} with 6 entries: + "voltage_source" => Dict{Any,Any}("source"=>Dict{String,Any}("qg_bus"=>[3.177… + "line" => Dict{Any,Any}("quad"=>Dict{String,Any}("qf"=>[3.09488, 3.… + "settings" => Dict{String,Any}("sbase"=>0.5) + "load" => Dict{Any,Any}("l2"=>Dict{String,Any}("qd_bus"=>[0.0, 3.0,… + "bus" => Dict{Any,Any}("primary"=>Dict{String,Any}("va"=>[-0.22425… + "per_unit" => false + + + +## Conclusion + +This concludes the introduction to the `ENGINEERING` data model and conversion to the `MATHEMATICAL` model. We hope that you will find this new data model abstraction easy to use and simple to understand diff --git a/examples/engineering_model.ipynb b/examples/engineering_model.ipynb index 42a8a8224..cf62e57eb 100644 --- a/examples/engineering_model.ipynb +++ b/examples/engineering_model.ipynb @@ -1,8 +1,51 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction to the PowerModelsDistribution Data Models\n", + "\n", + "In this notebook we introduce the engineering data model added to PowerModelsDistribution in version v0.9.0. We will give serveral examples of how to use this new data model directly, including new transformations that have become easier with it's introduction, how to convert it to the the lower-level mathematical model that was previously the only user interface we offered, and how to get various types of results using this new model." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports\n", + "\n", + "All commands in this document with no package namespace specified are directly exported by PowerModelsDistribution or already available in Julia base. Any commands that are only avaiable via an external package will be specified by including by using `import`, which will require specifying the originating package before the command, _e.g._ `Ipopt.Optimizer` as you will see below." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "┌ Info: Precompiling PowerModelsDistribution [d7431456-977f-11e9-2de3-97ff7677985e]\n", + "└ @ Base loading.jl:1260\n" + ] + } + ], + "source": [ + "using PowerModelsDistribution" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In these examples we will use the following optimization solvers, specified using `optimizer_with_attributes` from JuMP v0.21" + ] + }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -11,99 +54,75 @@ "MathOptInterface.OptimizerWithAttributes(Ipopt.Optimizer, Pair{MathOptInterface.AbstractOptimizerAttribute,Any}[MathOptInterface.RawParameter(\"tol\") => 1.0e-6, MathOptInterface.RawParameter(\"print_level\") => 0])" ] }, - "execution_count": 36, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "using PowerModelsDistribution\n", - "using Ipopt\n", + "import Ipopt\n", "\n", "ipopt_solver = optimizer_with_attributes(Ipopt.Optimizer, \"tol\"=>1e-6, \"print_level\"=>0)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Parsing Data\n", + "\n", + "Here we give the first example of how to parse data into the `ENGINEERING` data model structure, which is the default data structure type that the user will see without passing additional arguments, as we demonstrate later.\n", + "\n", + "We start with a 3 bus unbalanced load case provided as a dss file in the `test` folder of the PowerModelsDistribution.jl repository" + ] + }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Cannot find load object s844.\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Reading Buscoords in \"test2_Buscoords.dss\" on line 67 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b3-1; |2|!=3.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b4; |2|!=3.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Rg,Xg are not fully supported\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor1 like line\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor2 like line\u001b[39m\n" + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"case3_unbalanced.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 35 in \"case3_unbalanced.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 37 in \"case3_unbalanced.dss\" is not supported, skipping.\u001b[39m\n" ] }, { "data": { "text/plain": [ - "Dict{String,Any} with 18 entries:\n", - " \"xfmrcode\" => Dict{Any,Any}(\"t1\"=>Dict{String,Any}(\"tm_min\"=>Array{Flo…\n", - " \"shunt_reactor\" => Dict{Any,Any}(\"reactor3\"=>Dict{String,Any}(\"source_id\"=>…\n", - " \"bus\" => Dict{String,Any}(\"b3-1\"=>Dict{String,Any}(\"lat\"=>12.8477…\n", - " \"name\" => \"test2\"\n", - " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>100000.…\n", - " \"files\" => Set([\"test2_Linecodes.dss\", \"test2_master.dss\", \"test2_L…\n", - " \"switch\" => Dict{Any,Any}(\"_l4\"=>Dict{String,Any}(\"length\"=>0.001,\"t…\n", - " \"generator\" => Dict{Any,Any}(\"g2\"=>Dict{String,Any}(\"pg\"=>[1.0],\"connec…\n", - " \"loadshape\" => Dict{Any,Any}(\"default\"=>Dict{String,Any}(\"pmult\"=>[0.5,…\n", - " \"shunt_capacitor\" => Dict{Any,Any}(\"c2\"=>Dict{String,Any}(\"source_id\"=>\"capac…\n", - " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"v…\n", - " \"line\" => Dict{Any,Any}(\"l7\"=>Dict{String,Any}(\"length\"=>0.0135168…\n", - " \"line_reactor\" => Dict{Any,Any}(\"reactor1\"=>Dict{String,Any}(\"xs\"=>[0.0 0.…\n", - " \"data_model\" => \"engineering\"\n", - " \"xycurve\" => Dict{Any,Any}(\"test_curve3\"=>Dict{String,Any}(\"source_id…\n", - " \"transformer\" => Dict{String,Any}(\"t3a\"=>Dict{String,Any}(\"source_id\"=>\"t…\n", - " \"load\" => Dict{String,Any}(\"ld1\"=>Dict{String,Any}(\"source_id\"=>\"l…\n", - " \"linecode\" => Dict{Any,Any}(\"lc7\"=>Dict{String,Any}(\"b_fr\"=>[0.0082021…" + "Dict{String,Any} with 9 entries:\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"vs…\n", + " \"name\" => \"3bus_example\"\n", + " \"line\" => Dict{Any,Any}(\"quad\"=>Dict{String,Any}(\"cm_ub\"=>[400.0, 4…\n", + " \"settings\" => Dict{String,Any}(\"sbase_default\"=>500.0,\"vbases_default\"=…\n", + " \"files\" => [\"case3_unbalanced.dss\"]\n", + " \"load\" => Dict{Any,Any}(\"l2\"=>Dict{String,Any}(\"source_id\"=>\"load.l…\n", + " \"bus\" => Dict{Any,Any}(\"primary\"=>Dict{String,Any}(\"rg\"=>Float64[]…\n", + " \"linecode\" => Dict{Any,Any}(\"556mcm\"=>Dict{String,Any}(\"b_fr\"=>[25.4648…\n", + " \"data_model\" => ENGINEERING" ] }, - "execution_count": 37, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "eng_ex = parse_file(\"../test/data/opendss/test2_master.dss\"; data_model=\"engineering\")" + "eng = parse_file(\"../test/data/opendss/case3_unbalanced.dss\")" ] }, { - "cell_type": "code", - "execution_count": 3, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dict{String,Any} with 5 entries:\n", - " \"v_var_scalar\" => 1000.0\n", - " \"sbase\" => 100000.0\n", - " \"base_bus\" => \"testsource\"\n", - " \"vbase\" => 66.3953\n", - " \"base_frequency\" => 60.0" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "eng_ex[\"settings\"]" + "Different information and warning messages will be given depending on the input file. In the case above, these messages all related to various parse notifications that arise during a parse of a dss file, and can be safely ignored\n", + "\n", + "The resulting data structure is a Julia dictionary. The first notable field is `\"data_model\"` which specifies which data model this data structure corresponds to, in this case `ENGINEERING`. This value is expected to be an `Enum` of type `DataModel`\n", + "\n", + "The next notable field is `\"settings\"`, which contains some important default/starting values for the distribution network" ] }, { @@ -114,19 +133,12 @@ { "data": { "text/plain": [ - "Dict{String,Any} with 12 entries:\n", - " \"b3-1\" => Dict{String,Any}(\"lat\"=>12.8477,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", - " \"b8\" => Dict{String,Any}(\"lat\"=>12.6979,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", - " \"b11\" => Dict{String,Any}(\"bus_type\"=>1,\"rg\"=>Float64[],\"grounded\"…\n", - " \"b7\" => Dict{String,Any}(\"lat\"=>12.9187,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", - " \"b9\" => Dict{String,Any}(\"lat\"=>12.757,\"bus_type\"=>1,\"rg\"=>[0.0],…\n", - " \"testsource\" => Dict{String,Any}(\"bus_type\"=>1,\"rg\"=>[0.0],\"grounded\"=>[4…\n", - " \"_b2\" => Dict{String,Any}(\"lat\"=>12.283,\"bus_type\"=>1,\"rg\"=>[0.0],…\n", - " \"b5\" => Dict{String,Any}(\"lat\"=>12.7396,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", - " \"b10\" => Dict{String,Any}(\"lat\"=>12.0982,\"bus_type\"=>1,\"rg\"=>[0.0]…\n", - " \"b6_check-chars\" => Dict{String,Any}(\"lat\"=>12.0391,\"bus_type\"=>1,\"rg\"=>Float…\n", - " \"b4\" => Dict{String,Any}(\"lat\"=>12.371,\"bus_type\"=>1,\"rg\"=>Float6…\n", - " \"b1\" => Dict{String,Any}(\"lat\"=>12.0004,\"bus_type\"=>1,\"rg\"=>[0.0]…" + "Dict{String,Any} with 5 entries:\n", + " \"sbase_default\" => 500.0\n", + " \"vbases_default\" => Dict(\"sourcebus\"=>0.23094)\n", + " \"voltage_scale_factor\" => 1000.0\n", + " \"power_scale_factor\" => 1000.0\n", + " \"base_frequency\" => 50.0" ] }, "execution_count": 4, @@ -135,7 +147,20 @@ } ], "source": [ - "eng_ex[\"bus\"]" + "eng[\"settings\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- `\"sbase_default\"` is the starting value for the power base,\n", + "- `\"vbases_default\"` is the starting voltage base for the case, and multiple voltage bases can be specified, which would be useful in cases where there are multiple isolated islands with their own generation,\n", + "- `\"voltage_scale_factor\"` is a scaling factor for all voltage values, which in the case of OpenDSS is in kV by default\n", + "- `\"power_scale_factor\"` is a scaling factor for all power values\n", + "- `\"base_frequency\"` is the base frequency of the network in Hz, which is useful to know for mixed frequency networks\n", + "\n", + "Next we look at the `\"bus\"` components" ] }, { @@ -146,13 +171,10 @@ { "data": { "text/plain": [ - "Dict{String,Any} with 6 entries:\n", - " \"b_fr\" => [0.003125]\n", - " \"rs\" => [0.0015]\n", - " \"xs\" => [0.0]\n", - " \"b_to\" => [0.003125]\n", - " \"g_to\" => [0.0]\n", - " \"g_fr\" => [0.0]" + "Dict{Any,Any} with 3 entries:\n", + " \"primary\" => Dict{String,Any}(\"rg\"=>Float64[],\"grounded\"=>Int64[],\"status\"=…\n", + " \"sourcebus\" => Dict{String,Any}(\"rg\"=>Float64[],\"grounded\"=>Int64[],\"status\"=…\n", + " \"loadbus\" => Dict{String,Any}(\"rg\"=>[0.0],\"grounded\"=>[4],\"status\"=>ENABLED…" ] }, "execution_count": 5, @@ -161,7 +183,22 @@ } ], "source": [ - "eng_ex[\"linecode\"][\"lc1\"]" + "eng[\"bus\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see there are three buses in this system, identified by ids `\"primary\"`, `\"sourcebus\"`, and `\"loadbus\"`. \n", + "\n", + "__NOTE__: In Julia, order of Dictionary keys is not fixed, nor does it retain the order in which it was parsed like _e.g._ `Vectors`. \n", + "\n", + "Identifying components by non-integer names is a new feature of the `ENGINEERING` model, and makes network debugging more straightforward. \n", + "\n", + "__NOTE__: all names are converted to lowercase on parse from the originating dss file.\n", + "\n", + "Each bus component has the following properties in the `ENGINEERING` model" ] }, { @@ -172,13 +209,12 @@ { "data": { "text/plain": [ - "Dict{String,Any} with 6 entries:\n", - " \"b_fr\" => [0.0082021 -0.00410105; -0.00410105 0.0082021]\n", - " \"rs\" => [0.00164042 0.000410105; 0.000410105 0.00164042]\n", - " \"xs\" => [0.00082021 0.0; 0.0 0.0]\n", - " \"b_to\" => [0.0082021 -0.00410105; -0.00410105 0.0082021]\n", - " \"g_to\" => [0.0 0.0; 0.0 0.0]\n", - " \"g_fr\" => [0.0 0.0; 0.0 0.0]" + "Dict{String,Any} with 5 entries:\n", + " \"rg\" => Float64[]\n", + " \"grounded\" => Int64[]\n", + " \"status\" => ENABLED\n", + " \"terminals\" => [1, 2, 3]\n", + " \"xg\" => Float64[]" ] }, "execution_count": 6, @@ -187,7 +223,19 @@ } ], "source": [ - "eng_ex[\"linecode\"][\"lc7\"]" + "eng[\"bus\"][\"sourcebus\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- `\"terminals\"` indicates which terminals on the bus have active connections\n", + "- `\"grounded\"` indicates which terminals are grounded\n", + "- `\"rg\"` and `\"xg\"` indicate the grounding resistance and reactance of the ground\n", + "- `\"status\"` indicates whether a bus is `ENABLED` or `DISABLED`, and is specified for every component in the engineering model\n", + "\n", + "Next, we look at the `\"line\"` components, which is a generic name for both overhead lines and underground cables, which we do not differentiate between in the nomenclature" ] }, { @@ -198,17 +246,9 @@ { "data": { "text/plain": [ - "Dict{String,Any} with 10 entries:\n", - " \"source_id\" => \"load.ld1\"\n", - " \"name\" => \"ld1\"\n", - " \"vnom\" => 0.208\n", - " \"qd_nom\" => [0.974926]\n", - " \"status\" => 1\n", - " \"model\" => \"constant_power\"\n", - " \"connections\" => [1, 4]\n", - " \"pd_nom\" => [3.89]\n", - " \"bus\" => \"b7\"\n", - " \"configuration\" => \"wye\"" + "Dict{Any,Any} with 2 entries:\n", + " \"quad\" => Dict{String,Any}(\"cm_ub\"=>[400.0, 400.0, 400.0],\"cm_ub_c\"=>[600.0…\n", + " \"ohline\" => Dict{String,Any}(\"cm_ub\"=>[400.0, 400.0, 400.0],\"cm_ub_c\"=>[600.0…" ] }, "execution_count": 7, @@ -217,7 +257,7 @@ } ], "source": [ - "eng_ex[\"load\"][\"ld1\"]" + "eng[\"line\"]" ] }, { @@ -228,15 +268,18 @@ { "data": { "text/plain": [ - "Dict{String,Any} with 8 entries:\n", - " \"length\" => 0.001\n", + "Dict{String,Any} with 11 entries:\n", + " \"cm_ub\" => [400.0, 400.0, 400.0]\n", + " \"cm_ub_c\" => [600.0, 600.0, 600.0]\n", + " \"f_connections\" => [1, 2, 3]\n", + " \"length\" => 1.0\n", + " \"status\" => ENABLED\n", + " \"source_id\" => \"line.quad\"\n", " \"t_connections\" => [1, 2, 3]\n", - " \"f_bus\" => \"b8\"\n", - " \"source_id\" => \"line._l4\"\n", - " \"status\" => 1\n", - " \"t_bus\" => \"b10\"\n", - " \"linecode\" => \"300\"\n", - " \"f_connections\" => [1, 2, 3]" + " \"f_bus\" => \"primary\"\n", + " \"t_bus\" => \"loadbus\"\n", + " \"cm_ub_b\" => [600.0, 600.0, 600.0]\n", + " \"linecode\" => \"4/0quad\"" ] }, "execution_count": 8, @@ -245,7 +288,23 @@ } ], "source": [ - "eng_ex[\"switch\"][\"_l4\"]" + "eng[\"line\"][\"quad\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Again, we see components identified by their OpenDSS names. A `\"line\"` is an edge object, which will always have the following properties:\n", + "\n", + "- `\"f_bus\"`\n", + "- `\"t_bus\"`\n", + "- `\"f_connections\"` - list of terminals to which the line is connected on the from-side\n", + "- `\"t_connections\"` - list of terminals to which the line is connected on the to-side\n", + "\n", + "Here we are also introduced to two important concepts, the `\"source_id\"`, which is an easy way to identify from where an object originates in the dss file, and a data type element, pointed to by `\"linecode\"` in this case.\n", + "\n", + "A data type element is an element that does not represent a real engineering object, but only contains data that one of those real objects can refer to, in this case a linecode, which contains information like line resistance/reactance and conductance/susceptance." ] }, { @@ -256,15 +315,13 @@ { "data": { "text/plain": [ - "Dict{String,Any} with 8 entries:\n", - " \"source_id\" => \"vsource.source\"\n", - " \"rs\" => [0.001 0.0 0.0; 0.0 0.001 0.0; 0.0 0.0 0.001]\n", - " \"va\" => [30.0, -90.0, 150.0]\n", - " \"status\" => 1\n", - " \"connections\" => [1, 2, 3]\n", - " \"vm\" => [73.0348, 73.0348, 73.0348]\n", - " \"xs\" => [0.01 0.0 0.0; 0.0 0.01 0.0; 0.0 0.0 0.01]\n", - " \"bus\" => \"testsource\"" + "Dict{String,Any} with 6 entries:\n", + " \"b_fr\" => [25.4648 -0.0 -0.0; -0.0 25.4648 -0.0; -0.0 -0.0 25.4648]\n", + " \"rs\" => [0.1167 0.0467 0.0467; 0.0467 0.1167 0.0467; 0.0467 0.0467 0.1167]\n", + " \"xs\" => [0.0667 0.0267 0.0267; 0.0267 0.0667 0.0267; 0.0267 0.0267 0.0667]\n", + " \"b_to\" => [25.4648 -0.0 -0.0; -0.0 25.4648 -0.0; -0.0 -0.0 25.4648]\n", + " \"g_to\" => [0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0]\n", + " \"g_fr\" => [0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0]" ] }, "execution_count": 9, @@ -273,7 +330,14 @@ } ], "source": [ - "eng_ex[\"voltage_source\"][\"source\"]" + "eng[\"linecode\"][\"4/0quad\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we introduce a node element, the `\"load\"` object, where we also see the first example of a specification of less than three phases at a time" ] }, { @@ -281,57 +345,20 @@ "execution_count": 10, "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Cannot find load object s844.\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Reading Buscoords in \"test2_Buscoords.dss\" on line 67 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b3-1; |2|!=3.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b4; |2|!=3.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Rg,Xg are not fully supported\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor1 like line\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor2 like line\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: branch found with non-positive tap value of 0.0, setting a tap to 1.0 on conductor 1\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: branch found with non-positive tap value of 0.0, setting a tap to 1.0 on conductor 3\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: angmin and angmax values are 0, widening these values on branch 7, conductor 1 to +/- 60.0 deg.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: angmin and angmax values are 0, widening these values on branch 7, conductor 3 to +/- 60.0 deg.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Updated generator 4 cost function with order 3 to a function of order 2: [100.0, 0.0]\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [100.0, 0.0]\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Updated generator 2 cost function with order 3 to a function of order 2: [100.0, 0.0]\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Updated generator 3 cost function with order 3 to a function of order 2: [100.0, 0.0]\u001b[39m\n" - ] - }, { "data": { "text/plain": [ - "Dict{String,Any} with 19 entries:\n", - " \"bus\" => Dict{String,Any}(\"24\"=>Dict{String,Any}(\"grounded\"=>Bool[0, …\n", - " \"name\" => \"test2\"\n", - " \"dcline\" => Dict{String,Any}()\n", - " \"map\" => Dict(18=>Dict(:from=>\"l5\",:unmap_function=>:_map_math2eng_li…\n", - " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>100000.0,\"b…\n", - " \"gen\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"pg\"=>[0.0, 0.0, 0.0]…\n", - " \"branch\" => Dict{String,Any}(\"24\"=>Dict{String,Any}(\"source_id\"=>\"_virtu…\n", - " \"storage\" => Dict{String,Any}()\n", - " \"switch\" => Dict{String,Any}()\n", - " \"basekv\" => 66.3953\n", - " \"baseMVA\" => 100.0\n", - " \"sbase\" => 100000.0\n", - " \"conductors\" => 3\n", - " \"per_unit\" => true\n", - " \"data_model\" => \"mathematical\"\n", - " \"shunt\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"source_id\"=>\"reactor…\n", - " \"transformer\" => Dict{String,Any}(\"8\"=>Dict{String,Any}(\"polarity\"=>1,\"tm_min…\n", - " \"bus_lookup\" => Dict{Any,Int64}(\"b3-1\"=>1,\"b8\"=>2,\"b11\"=>3,\"b7\"=>4,\"b9\"=>5,\"…\n", - " \"load\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"model\"=>\"constant_cu…" + "Dict{String,Any} with 10 entries:\n", + " \"source_id\" => \"load.l1\"\n", + " \"qd_nom\" => [3.0]\n", + " \"status\" => ENABLED\n", + " \"model\" => POWER\n", + " \"connections\" => [1, 4]\n", + " \"vm_nom\" => 0.23094\n", + " \"pd_nom\" => [9.0]\n", + " \"dispatchable\" => NO\n", + " \"bus\" => \"loadbus\"\n", + " \"configuration\" => WYE" ] }, "execution_count": 10, @@ -340,7 +367,18 @@ } ], "source": [ - "math_ex = parse_file(\"../test/data/opendss/test2_master.dss\"; data_model=\"mathematical\")" + "eng[\"load\"][\"l1\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the length of the Vectors for `\"pd_nom\"` and `\"qd_nom\"` are only one, although the number of terminals listed in `\"connections\"` is two. This is because the connection is WYE, and therefore the final connection is a grounded neutral\n", + "\n", + "Here we are also introduced to two new Enums, `WYE`, which gives the connection configuration, and `NO` under dispatchable, which indicates that if this case were used in an MLD problem, _i.e._ with `run_mc_mld` that this load would not be sheddable.\n", + "\n", + "Finally, we show the generation source for this case, which in opendss is a voltage source named `\"source\"`" ] }, { @@ -351,33 +389,15 @@ { "data": { "text/plain": [ - "Dict{Int64,Dict{Symbol,Any}} with 42 entries:\n", - " 18 => Dict{Symbol,Any}(:from=>\"l5\",:unmap_function=>:_map_math2eng_line!,:to=…\n", - " 30 => Dict{Symbol,Any}(:from=>\"ld1\",:unmap_function=>:_map_math2eng_load!,:to…\n", - " 33 => Dict{Symbol,Any}(:from=>\"ld2\",:unmap_function=>:_map_math2eng_load!,:to…\n", - " 32 => Dict{Symbol,Any}(:from=>\"ld3\",:unmap_function=>:_map_math2eng_load!,:to…\n", - " 41 => Dict{Symbol,Any}(:from=>\"g3\",:unmap_function=>:_map_math2eng_generator!…\n", - " 2 => Dict{Symbol,Any}(:from=>\"b3-1\",:unmap_function=>:_map_math2eng_bus!,:to…\n", - " 40 => Dict{Symbol,Any}(:from=>\"g1\",:unmap_function=>:_map_math2eng_generator!…\n", - " 16 => Dict{Symbol,Any}(:from=>\"l8\",:unmap_function=>:_map_math2eng_line!,:to=…\n", - " 11 => Dict{Symbol,Any}(:from=>\"b6_check-chars\",:unmap_function=>:_map_math2en…\n", - " 21 => Dict{Symbol,Any}(:from=>\"l1\",:unmap_function=>:_map_math2eng_line!,:to=…\n", - " 39 => Dict{Symbol,Any}(:from=>\"g2\",:unmap_function=>:_map_math2eng_generator!…\n", - " 7 => Dict{Symbol,Any}(:from=>\"testsource\",:unmap_function=>:_map_math2eng_bu…\n", - " 9 => Dict{Symbol,Any}(:from=>\"b5\",:unmap_function=>:_map_math2eng_bus!,:to=>…\n", - " 25 => Dict{Symbol,Any}(:from=>\"t2\",:unmap_function=>:_map_math2eng_transforme…\n", - " 10 => Dict{Symbol,Any}(:from=>\"b10\",:unmap_function=>:_map_math2eng_bus!,:to=…\n", - " 26 => Dict{Symbol,Any}(:from=>\"t1\",:unmap_function=>:_map_math2eng_transforme…\n", - " 29 => Dict{Symbol,Any}(:from=>\"reactor2\",:unmap_function=>:_map_math2eng_line…\n", - " 34 => Dict{Symbol,Any}(:from=>\"c2\",:unmap_function=>:_map_math2eng_shunt_capa…\n", - " 35 => Dict{Symbol,Any}(:from=>\"c1\",:unmap_function=>:_map_math2eng_shunt_capa…\n", - " 19 => Dict{Symbol,Any}(:from=>\"l3\",:unmap_function=>:_map_math2eng_line!,:to=…\n", - " 17 => Dict{Symbol,Any}(:from=>\"l2\",:unmap_function=>:_map_math2eng_line!,:to=…\n", - " 42 => Dict{Symbol,Any}(:from=>\"source\",:unmap_function=>:_map_math2eng_voltag…\n", - " 8 => Dict{Symbol,Any}(:from=>\"_b2\",:unmap_function=>:_map_math2eng_bus!,:to=…\n", - " 22 => Dict{Symbol,Any}(:from=>\"_l4\",:unmap_function=>:_map_math2eng_switch!,:…\n", - " 6 => Dict{Symbol,Any}(:from=>\"b9\",:unmap_function=>:_map_math2eng_bus!,:to=>…\n", - " ⋮ => ⋮" + "Dict{String,Any} with 8 entries:\n", + " \"source_id\" => \"vsource.source\"\n", + " \"rs\" => [4.27691e-8 3.96342e-9 3.96342e-9; 3.96342e-9 4.27691e-8 3.9…\n", + " \"va\" => [0.0, -120.0, 120.0]\n", + " \"status\" => ENABLED\n", + " \"connections\" => [1, 2, 3]\n", + " \"vm\" => [0.229993, 0.229993, 0.229993]\n", + " \"xs\" => [1.54178e-7 -1.04497e-9 -1.04497e-9; -1.04497e-9 1.54178e-7 …\n", + " \"bus\" => \"sourcebus\"" ] }, "execution_count": 11, @@ -386,7 +406,25 @@ } ], "source": [ - "math_ex[\"map\"]" + "eng[\"voltage_source\"][\"source\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- `\"vm\"` - specifies the fixed voltage magnitudes per phase at the bus\n", + "- `\"va\"` - specifies the fixed reference angles per phases at the bus\n", + "- `\"rs\"` and `\"xs\"` specifies internal impedances of the voltage source" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Importing raw dss properties\n", + "\n", + "In case there are additional properties that you want to use from dss, it is possible to import those directly into the `ENGINEERING` (and `MATHEMATICAL`) data structure with the `import_all` keyword argument" ] }, { @@ -394,13 +432,21 @@ "execution_count": 12, "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"case3_unbalanced.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 35 in \"case3_unbalanced.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 37 in \"case3_unbalanced.dss\" is not supported, skipping.\u001b[39m\n" + ] + }, { "data": { "text/plain": [ - "Dict{Symbol,Any} with 3 entries:\n", - " :from => \"source\"\n", - " :unmap_function => :_map_math2eng_voltage_source!\n", - " :to => [\"gen.4\", \"bus.33\", \"branch.27\"]" + "Dict{Any,Any} with 2 entries:\n", + " \"quad\" => Dict{String,Any}(\"cm_ub\"=>[400.0, 400.0, 400.0],\"cm_ub_c\"=>[600.0…\n", + " \"ohline\" => Dict{String,Any}(\"cm_ub\"=>[400.0, 400.0, 400.0],\"cm_ub_c\"=>[600.0…" ] }, "execution_count": 12, @@ -409,7 +455,33 @@ } ], "source": [ - "math_ex[\"map\"][42]" + "eng_all = parse_file(\"../test/data/opendss/case3_unbalanced.dss\"; import_all=true)\n", + "\n", + "eng_all[\"line\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You will note the presence of `\"dss\"` dictionaries under components, and `\"dss_options\"` at the root level" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running Optimal Power Flow\n", + "\n", + "In this section we introduce how to run an optimal power flow (opf) in PowerModelsDistribution on an engineering data model\n", + "\n", + "In order to run an OPF problem you will need\n", + "\n", + "1. a data model\n", + "2. a formulation\n", + "3. a solver\n", + "\n", + "In these examples we will use the `eng` model we worked with above, the `ACPPowerModel`, which is a AC power flow formulation in polar coordinates, and the `ipopt_solver` we already defined above" ] }, { @@ -421,35 +493,28 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Cannot find load object s844.\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Reading Buscoords in \"test2_Buscoords.dss\" on line 67 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n" + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0]\u001b[39m\n", + "\n", + "******************************************************************************\n", + "This program contains Ipopt, a library for large-scale nonlinear optimization.\n", + " Ipopt is released as open source code under the Eclipse Public License (EPL).\n", + " For more information visit http://projects.coin-or.org/Ipopt\n", + "******************************************************************************\n", + "\n" ] }, { "data": { "text/plain": [ - "Dict{String,Any} with 16 entries:\n", - " \"xfmrcode\" => Dict{String,Any}(\"t1\"=>Dict{String,Any}(\"windings\"=>2,\"name\"…\n", - " \"reactor\" => Dict{String,Any}(\"reactor1\"=>Dict{String,Any}(\"name\"=>\"react…\n", - " \"circuit\" => \"test2\"\n", - " \"generator\" => Dict{String,Any}(\"g2\"=>Dict{String,Any}(\"name\"=>\"g2\",\"bus1\"=…\n", - " \"vsource\" => Dict{String,Any}(\"source\"=>Dict{String,Any}(\"name\"=>\"source\"…\n", - " \"loadshape\" => Dict{String,Any}(\"default\"=>Dict{String,Any}(\"pmult\"=>[0.5, …\n", - " \"line\" => Dict{String,Any}(\"_l4\"=>Dict{String,Any}(\"repair\"=>0.0,\"bus2…\n", - " \"buscoords\" => Dict{String,Any}(\"b10\"=>Dict{String,Any}(\"x\"=>49.2303,\"y\"=>1…\n", - " \"data_model\" => \"dss\"\n", - " \"options\" => Dict{String,Any}(\"voltagebases\"=>[115.0, 12.47, 0.48, 0.208])\n", - " \"xycurve\" => Dict{String,Any}(\"test_curve3\"=>Dict{String,Any}(\"name\"=>\"te…\n", - " \"transformer\" => Dict{String,Any}(\"t3a\"=>Dict{String,Any}(\"xfmrcode\"=>\"t1\",\"w…\n", - " \"filename\" => Set([\"test2_Linecodes.dss\", \"test2_master.dss\", \"test2_Loads…\n", - " \"load\" => Dict{String,Any}(\"ld1\"=>Dict{String,Any}(\"model\"=>1,\"kv\"=>0.…\n", - " \"linecode\" => Dict{String,Any}(\"lc7\"=>Dict{String,Any}(\"nphases\"=>2,\"name\"…\n", - " \"capacitor\" => Dict{String,Any}(\"c2\"=>Dict{String,Any}(\"name\"=>\"c2\",\"bus1\"=…" + "Dict{String,Any} with 8 entries:\n", + " \"solve_time\" => 3.9884\n", + " \"optimizer\" => \"Ipopt\"\n", + " \"termination_status\" => LOCALLY_SOLVED\n", + " \"dual_status\" => FEASIBLE_POINT\n", + " \"primal_status\" => FEASIBLE_POINT\n", + " \"objective\" => 0.0214812\n", + " \"solution\" => Dict{String,Any}(\"voltage_source\"=>Dict{Any,Any}(\"sou…\n", + " \"objective_lb\" => -Inf" ] }, "execution_count": 13, @@ -458,7 +523,14 @@ } ], "source": [ - "dss = parse_dss(\"../test/data/opendss/test2_master.dss\")" + "result = run_mc_opf(eng, ACPPowerModel, ipopt_solver)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The result of `run_mc_opf` will be very familiar to those who are already familiar with PowerModels and PowerModelsDistribution. The notable difference will be in the `\"solution\"` dictionary" ] }, { @@ -469,14 +541,13 @@ { "data": { "text/plain": [ - "Dict{String,Any} with 7 entries:\n", - " \"name\" => \"l7\"\n", - " \"bus1\" => \"_b2\"\n", - " \"test_param\" => 100.0\n", - " \"prop_order\" => [\"name\", \"like\", \"bus1\", \"bus2\", \"linecode\", \"test_param\"]\n", - " \"linecode\" => \"lc10\"\n", - " \"like\" => \"l2\"\n", - " \"bus2\" => \"b10\"" + "Dict{String,Any} with 6 entries:\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"qg_bus\"=>[3.177…\n", + " \"line\" => Dict{Any,Any}(\"quad\"=>Dict{String,Any}(\"qf\"=>[3.09488, 3.…\n", + " \"settings\" => Dict{String,Any}(\"sbase\"=>0.5)\n", + " \"load\" => Dict{Any,Any}(\"l2\"=>Dict{String,Any}(\"qd_bus\"=>[0.0, 3.0,…\n", + " \"bus\" => Dict{Any,Any}(\"primary\"=>Dict{String,Any}(\"va\"=>[-0.22425…\n", + " \"per_unit\" => false" ] }, "execution_count": 14, @@ -485,7 +556,14 @@ } ], "source": [ - "dss[\"line\"][\"l7\"]" + "result[\"solution\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here you can see that the solution comes back out by default into the same data model as is provided by the user to the `run_` command, as well as being in SI units, as opposed to per unit, which is used during the solve. For example," ] }, { @@ -493,36 +571,12 @@ "execution_count": 15, "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 2 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Linecodes.dss\" on line 9 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Redirecting to \"test2_Loadshape.dss\" on line 10 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Cannot find load object s844.\u001b[39m\n", - "\u001b[32m[info | PowerModels]: Reading Buscoords in \"test2_Buscoords.dss\" on line 67 in \"test2_master.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 69 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"show\" on line 71 in \"test2_master.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b3-1; |2|!=3.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: An incorrect number of nodes was specified on b4; |2|!=3.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: line.l1: like=something cannot be found\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Rg,Xg are not fully supported\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor1 like line\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: reactors as constant impedance elements is not yet supported, treating reactor.reactor2 like line\u001b[39m\n" - ] - }, { "data": { "text/plain": [ - "Dict{String,Any} with 6 entries:\n", - " \"length\" => 0.0321756\n", - " \"name\" => \"l1-2\"\n", - " \"bus1\" => \"b3-1.2\"\n", - " \"units\" => \"km\"\n", - " \"linecode\" => \"lc1\"\n", - " \"bus2\" => \"b4.2\"" + "Dict{String,Any} with 2 entries:\n", + " \"va\" => [-0.484238, -120.243, 120.274]\n", + " \"vm\" => [0.222521, 0.226727, 0.225577]" ] }, "execution_count": 15, @@ -531,9 +585,14 @@ } ], "source": [ - "eng_import_all = parse_file(\"../test/data/opendss/test2_master.dss\"; data_model=\"engineering\", import_all=true)\n", - "\n", - "eng_import_all[\"line\"][\"l1-2\"][\"dss\"]" + "result[\"solution\"][\"bus\"][\"loadbus\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If for some reason you want to return the result in per-unit rather than SI, you can specify this in the `run_` command by" ] }, { @@ -545,25 +604,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_2w_yy_bank.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 53 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 56 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n" + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0]\u001b[39m\n" ] }, { "data": { "text/plain": [ - "Dict{String,Any} with 10 entries:\n", - " \"xfmrcode\" => Dict{Any,Any}(\"tx\"=>Dict{String,Any}(\"tm_min\"=>Array{Floa…\n", - " \"name\" => \"ut_trans\"\n", - " \"line\" => Dict{Any,Any}(\"line2\"=>Dict{String,Any}(\"xs\"=>[0.3349 0.0…\n", - " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"vs…\n", - " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>1000.0,\"…\n", - " \"files\" => Set([\"ut_trans_2w_yy_bank.dss\"])\n", - " \"transformer\" => Dict{String,Any}(\"tx1\"=>Dict{String,Any}(\"polarity\"=>[1, …\n", - " \"load\" => Dict{String,Any}(\"load3\"=>Dict{String,Any}(\"source_id\"=>\"…\n", - " \"bus\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"bus_type\"=>1,\"rg\"…\n", - " \"data_model\" => \"engineering\"" + "Dict{String,Any} with 2 entries:\n", + " \"va\" => [-0.484238, -120.243, 120.274]\n", + " \"vm\" => [0.963546, 0.981757, 0.976779]" ] }, "execution_count": 16, @@ -572,7 +621,18 @@ } ], "source": [ - "eng = parse_file(\"../test/data/opendss/ut_trans_2w_yy_bank.dss\"; data_model=\"engineering\")" + "result_pu = run_mc_opf(eng, ACPPowerModel, ipopt_solver; make_si=false)\n", + "\n", + "result_pu[\"solution\"][\"bus\"][\"loadbus\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Branch Flow formulations\n", + "\n", + "Previously, to use a branch flow formulation, such as `SOCNLPUBFPowerModel`, it was required to use a different `run_` command, but now, by using multiple dispatch we have simplified this for the user" ] }, { @@ -584,29 +644,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [1.0, 0.0]\u001b[39m\n", - "\n", - "******************************************************************************\n", - "This program contains Ipopt, a library for large-scale nonlinear optimization.\n", - " Ipopt is released as open source code under the Eclipse Public License (EPL).\n", - " For more information visit http://projects.coin-or.org/Ipopt\n", - "******************************************************************************\n", - "\n" + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0]\u001b[39m\n" ] }, { "data": { "text/plain": [ - "Dict{String,Any} with 10 entries:\n", - " \"solve_time\" => 4.33379\n", + "Dict{String,Any} with 8 entries:\n", + " \"solve_time\" => 0.190441\n", " \"optimizer\" => \"Ipopt\"\n", " \"termination_status\" => LOCALLY_SOLVED\n", " \"dual_status\" => FEASIBLE_POINT\n", " \"primal_status\" => FEASIBLE_POINT\n", - " \"objective\" => 0.0\n", + " \"objective\" => 0.0211303\n", " \"solution\" => Dict{String,Any}(\"voltage_source\"=>Dict{Any,Any}(\"sou…\n", - " \"data\" => Dict{String,Any}(\"name\"=>\"ut_trans\")\n", - " \"machine\" => Dict(\"cpu\"=>\"Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GH…\n", " \"objective_lb\" => -Inf" ] }, @@ -616,607 +667,486 @@ } ], "source": [ - "result_eng = run_mc_pf(eng, ACPPowerModel, ipopt_solver; make_si=false)" + "result_bf = run_mc_opf(eng, SOCNLPUBFPowerModel, ipopt_solver)" ] }, { - "cell_type": "code", - "execution_count": 18, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [1.0, 0.0]\u001b[39m\n" - ] - }, - { - "data": { - "text/plain": [ - "Dict{String,Any} with 19 entries:\n", - " \"bus\" => Dict{String,Any}(\"8\"=>Dict{String,Any}(\"grounded\"=>Bool[0, 0…\n", - " \"name\" => \"ut_trans\"\n", - " \"dcline\" => Dict{String,Any}()\n", - " \"map\" => Dict(2=>Dict(:from=>\"1\",:unmap_function=>:_map_math2eng_bus!…\n", - " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>1000.0,\"bas…\n", - " \"gen\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"pg\"=>[0.0, 0.0, 0.0]…\n", - " \"branch\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"source_id\"=>\"_virtua…\n", - " \"storage\" => Dict{String,Any}()\n", - " \"switch\" => Dict{String,Any}()\n", - " \"basekv\" => 6.35085\n", - " \"baseMVA\" => 1.0\n", - " \"sbase\" => 1000.0\n", - " \"conductors\" => 3\n", - " \"per_unit\" => true\n", - " \"data_model\" => \"mathematical\"\n", - " \"shunt\" => Dict{String,Any}()\n", - " \"transformer\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"polarity\"=>1,\"tm_min…\n", - " \"bus_lookup\" => Dict{Any,Int64}(\"1\"=>1,\"sourcebus\"=>2,\"2\"=>3,\"3\"=>4)\n", - " \"load\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"model\"=>\"constant_po…" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "math = transform_data_model(eng)" + "## Engineering Model Transformations\n", + "\n", + "One of the power things about the engineering model is that data transformations are much more simple. Here we illustrate two examples that are currently included in PowerModelsDistribution, but writing your own data transformation functions will be trivial, as we will show\n", + "\n", + "First, there are several objects that have loss models by default when parsing from dss files, such as voltage sources, transformers, and switches. To remove these loss models, therefore making these components lossless, we can use the included `make_lossess!` function. Here we use a basic 2-winding wye-wye connected transformer case from `test` to illustrate this" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_2w_yy_bank.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 53 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 56 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [1.0, 0.0]\u001b[39m\n" + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_2w_yy.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 35 in \"ut_trans_2w_yy.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 38 in \"ut_trans_2w_yy.dss\" is not supported, skipping.\u001b[39m\n" ] }, { "data": { "text/plain": [ - "true" + "Dict{String,Any} with 16 entries:\n", + " \"polarity\" => [1, 1]\n", + " \"sm_nom\" => [500.0, 500.0]\n", + " \"tm_lb\" => [[0.9, 0.9, 0.9], [0.9, 0.9, 0.9]]\n", + " \"connections\" => [[1, 2, 3, 4], [1, 2, 3, 4]]\n", + " \"tm_set\" => [[1.02, 1.02, 1.02], [0.97, 0.97, 0.97]]\n", + " \"tm_step\" => [[0.03125, 0.03125, 0.03125], [0.03125, 0.03125, 0.03125]]\n", + " \"bus\" => [\"1\", \"2\"]\n", + " \"configuration\" => ConnConfig[WYE, WYE]\n", + " \"noloadloss\" => 0.05\n", + " \"xsc\" => [0.05]\n", + " \"source_id\" => \"transformer.tx1\"\n", + " \"rw\" => [0.01, 0.02]\n", + " \"tm_fix\" => Array{Bool,1}[[1, 1, 1], [1, 1, 1]]\n", + " \"vm_nom\" => [11.0, 4.0]\n", + " \"tm_ub\" => [[1.1, 1.1, 1.1], [1.1, 1.1, 1.1]]\n", + " \"imag\" => 0.11" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "math_from_parse = parse_file(\"../test/data/opendss/ut_trans_2w_yy_bank.dss\"; data_model=\"mathematical\")\n", + "eng_ut = parse_file(\"../test/data/opendss/ut_trans_2w_yy.dss\")\n", "\n", - "math_from_parse == math" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dict{String,Any} with 10 entries:\n", - " \"solve_time\" => 0.0145578\n", - " \"optimizer\" => \"Ipopt\"\n", - " \"termination_status\" => LOCALLY_SOLVED\n", - " \"dual_status\" => FEASIBLE_POINT\n", - " \"primal_status\" => FEASIBLE_POINT\n", - " \"objective\" => 0.0\n", - " \"solution\" => Dict{String,Any}(\"baseMVA\"=>1.0,\"branch\"=>Dict{String…\n", - " \"data\" => Dict{String,Any}(\"name\"=>\"ut_trans\")\n", - " \"machine\" => Dict(\"cpu\"=>\"Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GH…\n", - " \"objective_lb\" => -Inf" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result_math = run_mc_pf(math, ACPPowerModel, ipopt_solver)" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dict{Any,Any} with 4 entries:\n", - " \"sourcebus\" => Dict{String,Any}(\"va\"=>[-8.75226e-8, -120.0, 120.0],\"vm\"=>[6.3…\n", - " \"1\" => Dict{String,Any}(\"va\"=>[0.765392, -119.423, 120.857],\"vm\"=>[6.…\n", - " \"2\" => Dict{String,Any}(\"va\"=>[-0.249838, -120.484, 119.472],\"vm\"=>[1…\n", - " \"3\" => Dict{String,Any}(\"va\"=>[-0.11431, -120.435, 119.578],\"vm\"=>[1.…" - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result_eng[\"solution\"][\"bus\"]" + "eng_ut[\"transformer\"][\"tx1\"]" ] }, { - "cell_type": "code", - "execution_count": 22, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dict{String,Any} with 9 entries:\n", - " \"8\" => Dict{String,Any}(\"va\"=>[0.0133586, -2.08432, 2.10936],\"vm\"=>[0.92195, …\n", - " \"4\" => Dict{String,Any}(\"va\"=>[-0.00199508, -2.10198, 2.08703],\"vm\"=>[0.82944…\n", - " \"1\" => Dict{String,Any}(\"va\"=>[0.0133586, -2.08432, 2.10936],\"vm\"=>[0.968047,…\n", - " \"5\" => Dict{String,Any}(\"va\"=>[-0.0127852, -2.11267, 2.07185],\"vm\"=>[0.897401…\n", - " \"2\" => Dict{String,Any}(\"va\"=>[-1.52756e-9, -2.0944, 2.0944],\"vm\"=>[1.0, 1.0,…\n", - " \"7\" => Dict{String,Any}(\"va\"=>[0.0175895, -2.0794, 2.11601],\"vm\"=>[0.916491, …\n", - " \"6\" => Dict{String,Any}(\"va\"=>[-0.0043605, -2.10284, 2.08518],\"vm\"=>[0.886299…\n", - " \"9\" => Dict{String,Any}(\"va\"=>[0.0, -2.0944, 2.0944],\"vm\"=>[1.0, 1.0, 1.0])\n", - " \"3\" => Dict{String,Any}(\"va\"=>[-0.0043605, -2.10284, 2.08518],\"vm\"=>[0.841984…" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "result_math[\"solution\"][\"bus\"]" + "We can see that `\"noloadloss\"`, `\"rw\"`, and `\"imag\"` are non-zero, but if we apply the `make_lossless!` function we can see these parameters are set to zero, effectively eliminating the losses" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Dict{String,Any} with 4 entries:\n", - " \"qg_bus\" => [138.242, 162.053, 182.485]\n", - " \"qg\" => [138.242, 162.053, 182.485]\n", - " \"pg\" => [133.06, 155.561, 179.012]\n", - " \"pg_bus\" => [133.06, 155.561, 179.012]" + "Dict{String,Any} with 16 entries:\n", + " \"polarity\" => [1, 1]\n", + " \"sm_nom\" => [500.0, 500.0]\n", + " \"tm_lb\" => [[0.9, 0.9, 0.9], [0.9, 0.9, 0.9]]\n", + " \"connections\" => [[1, 2, 3, 4], [1, 2, 3, 4]]\n", + " \"tm_set\" => [[1.02, 1.02, 1.02], [0.97, 0.97, 0.97]]\n", + " \"tm_step\" => [[0.03125, 0.03125, 0.03125], [0.03125, 0.03125, 0.03125]]\n", + " \"bus\" => [\"1\", \"2\"]\n", + " \"configuration\" => ConnConfig[WYE, WYE]\n", + " \"noloadloss\" => 0.0\n", + " \"xsc\" => [0.0]\n", + " \"source_id\" => \"transformer.tx1\"\n", + " \"rw\" => [0.0, 0.0]\n", + " \"tm_fix\" => Array{Bool,1}[[1, 1, 1], [1, 1, 1]]\n", + " \"vm_nom\" => [11.0, 4.0]\n", + " \"tm_ub\" => [[1.1, 1.1, 1.1], [1.1, 1.1, 1.1]]\n", + " \"imag\" => 0.0" ] }, - "execution_count": 23, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "result_eng[\"solution\"][\"voltage_source\"][\"source\"]" + "make_lossless!(eng_ut)\n", + "\n", + "eng_ut[\"transformer\"][\"tx1\"]" ] }, { - "cell_type": "code", - "execution_count": 24, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dict{String,Any} with 4 entries:\n", - " \"qg_bus\" => [0.138242, 0.162053, 0.182485]\n", - " \"qg\" => [0.138242, 0.162053, 0.182485]\n", - " \"pg\" => [0.13306, 0.155561, 0.179012]\n", - " \"pg_bus\" => [0.13306, 0.155561, 0.179012]" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "result_math[\"solution\"][\"gen\"][\"1\"]" + "Alternatively, we can apply this function at parse" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_2w_yy_bank.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 53 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 56 in \"ut_trans_2w_yy_bank.dss\" is not supported, skipping.\u001b[39m\n" + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_2w_yy.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 35 in \"ut_trans_2w_yy.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 38 in \"ut_trans_2w_yy.dss\" is not supported, skipping.\u001b[39m\n" ] }, { "data": { "text/plain": [ - "Dict{String,Any} with 10 entries:\n", - " \"xfmrcode\" => Dict{Any,Any}(\"tx\"=>Dict{String,Any}(\"tm_min\"=>Array{Floa…\n", - " \"name\" => \"ut_trans\"\n", - " \"line\" => Dict{Any,Any}(\"line2\"=>Dict{String,Any}(\"xs\"=>[0.3349 0.0…\n", - " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"vs…\n", - " \"settings\" => Dict{String,Any}(\"v_var_scalar\"=>1000.0,\"sbase\"=>1000.0,\"…\n", - " \"files\" => Set([\"ut_trans_2w_yy_bank.dss\"])\n", - " \"transformer\" => Dict{String,Any}(\"tx1\"=>Dict{String,Any}(\"polarity\"=>[1, …\n", - " \"load\" => Dict{String,Any}(\"load3\"=>Dict{String,Any}(\"source_id\"=>\"…\n", - " \"bus\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"bus_type\"=>1,\"rg\"…\n", - " \"data_model\" => \"engineering\"" + "Dict{String,Any} with 16 entries:\n", + " \"polarity\" => [1, 1]\n", + " \"sm_nom\" => [500.0, 500.0]\n", + " \"tm_lb\" => [[0.9, 0.9, 0.9], [0.9, 0.9, 0.9]]\n", + " \"connections\" => [[1, 2, 3, 4], [1, 2, 3, 4]]\n", + " \"tm_set\" => [[1.02, 1.02, 1.02], [0.97, 0.97, 0.97]]\n", + " \"tm_step\" => [[0.03125, 0.03125, 0.03125], [0.03125, 0.03125, 0.03125]]\n", + " \"bus\" => [\"1\", \"2\"]\n", + " \"configuration\" => ConnConfig[WYE, WYE]\n", + " \"noloadloss\" => 0.0\n", + " \"xsc\" => [0.0]\n", + " \"source_id\" => \"transformer.tx1\"\n", + " \"rw\" => [0.0, 0.0]\n", + " \"tm_fix\" => Array{Bool,1}[[1, 1, 1], [1, 1, 1]]\n", + " \"vm_nom\" => [11.0, 4.0]\n", + " \"tm_ub\" => [[1.1, 1.1, 1.1], [1.1, 1.1, 1.1]]\n", + " \"imag\" => 0.0" ] }, - "execution_count": 25, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# make_lossless!(eng)\n", + "eng_ut = parse_file(\"../test/data/opendss/ut_trans_2w_yy.dss\"; transformations=[make_lossless!])\n", "\n", - "eng_lossless = parse_file(\"../test/data/opendss/ut_trans_2w_yy_bank.dss\"; data_model=\"engineering\", transformations=[make_lossless!])" + "eng_ut[\"transformer\"][\"tx1\"]" ] }, { - "cell_type": "code", - "execution_count": 26, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dict{String,Any} with 8 entries:\n", - " \"source_id\" => \"vsource.source\"\n", - " \"rs\" => [1.69763e-7 1.57319e-8 1.57319e-8; 1.57319e-8 1.69763e-7 1.5…\n", - " \"va\" => [0.0, -120.0, 120.0]\n", - " \"status\" => 1\n", - " \"connections\" => [1, 2, 3]\n", - " \"vm\" => [6.35085, 6.35085, 6.35085]\n", - " \"xs\" => [6.11975e-7 -4.14779e-9 -4.14779e-9; -4.14779e-9 6.11975e-7 …\n", - " \"bus\" => \"sourcebus\"" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "eng[\"voltage_source\"][\"source\"]" + "Another transformation function included in PowerModelsDistribution is the `apply_voltage_bounds!` function, which will apply some voltage bounds in SI units, given some percent value, _e.g._ if we want the lower bound on voltage to be `0.9` and upper bound `1.1` after per-unit conversion" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Dict{String,Any} with 6 entries:\n", - " \"source_id\" => \"vsource.source\"\n", - " \"va\" => [0.0, -120.0, 120.0]\n", - " \"status\" => 1\n", - " \"connections\" => [1, 2, 3]\n", - " \"vm\" => [6.35085, 6.35085, 6.35085]\n", - " \"bus\" => \"sourcebus\"" + "Dict{String,Any} with 7 entries:\n", + " \"rg\" => [0.0]\n", + " \"grounded\" => [4]\n", + " \"status\" => ENABLED\n", + " \"terminals\" => [1, 2, 3, 4]\n", + " \"vm_ub\" => [2.54034, 2.54034, 2.54034, 2.54034]\n", + " \"vm_lb\" => [2.07846, 2.07846, 2.07846, 2.07846]\n", + " \"xg\" => [0.0]" ] }, - "execution_count": 27, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "eng_lossless[\"voltage_source\"][\"source\"]" + "apply_voltage_bounds!(eng_ut; vm_lb=0.9, vm_ub=1.1)\n", + "\n", + "eng_ut[\"bus\"][\"2\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, this can be specified at parse by" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_3w_dyy_1.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 36 in \"ut_trans_3w_dyy_1.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 39 in \"ut_trans_3w_dyy_1.dss\" is not supported, skipping.\u001b[39m\n" + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_2w_yy.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 35 in \"ut_trans_2w_yy.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 38 in \"ut_trans_2w_yy.dss\" is not supported, skipping.\u001b[39m\n" ] }, { "data": { "text/plain": [ - "Dict{String,Any} with 17 entries:\n", - " \"polarity\" => [1, 1, 1]\n", - " \"connections\" => Array{Int64,1}[[1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4]]\n", - " \"tm_min\" => Array{Float64,1}[[0.9, 0.9, 0.9], [0.9, 0.9, 0.9], [0.9, 0…\n", - " \"tm_step\" => Array{Float64,1}[[0.03125, 0.03125, 0.03125], [0.03125, 0.…\n", - " \"bus\" => [\"1\", \"2\", \"3\"]\n", - " \"configuration\" => [\"delta\", \"wye\", \"wye\"]\n", - " \"noloadloss\" => 0.05\n", - " \"xsc\" => Any[0.05, 0.04, 0.03]\n", - " \"source_id\" => \"transformer.tx1\"\n", - " \"snom\" => [500.0, 500.0, 500.0]\n", - " \"tm_fix\" => Array{Int64,1}[[1, 1, 1], [1, 1, 1], [1, 1, 1]]\n", - " \"tm\" => Array{Float64,1}[[1.01, 1.01, 1.01], [1.02, 1.02, 1.02], […\n", - " \"tm_max\" => Array{Float64,1}[[1.1, 1.1, 1.1], [1.1, 1.1, 1.1], [1.1, 1…\n", - " \"rs\" => [0.01, 0.02, 0.03]\n", - " \"fixed\" => Array{Bool,1}[[1, 1, 1], [1, 1, 1], [1, 1, 1]]\n", - " \"imag\" => 0.11\n", - " \"vnom\" => [11.0, 4.0, 0.4]" + "Dict{String,Any} with 7 entries:\n", + " \"rg\" => [0.0]\n", + " \"grounded\" => [4]\n", + " \"status\" => ENABLED\n", + " \"terminals\" => [1, 2, 3, 4]\n", + " \"vm_ub\" => [2.54034, 2.54034, 2.54034, 2.54034]\n", + " \"vm_lb\" => [2.07846, 2.07846, 2.07846, 2.07846]\n", + " \"xg\" => [0.0]" ] }, - "execution_count": 28, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "eng_tr = parse_file(\"../test/data/opendss/ut_trans_3w_dyy_1.dss\"; data_model=\"engineering\")\n", + "eng_ut = parse_file(\"../test/data/opendss/ut_trans_2w_yy.dss\"; transformations=[make_lossless!, (apply_voltage_bounds!, \"vm_lb\"=>0.9, \"vm_ub\"=>1.1)])\n", "\n", - "eng_tr[\"transformer\"][\"tx1\"]" + "eng_ut[\"bus\"][\"2\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mathematical Model\n", + "\n", + "In this section we introduce the mathematical model, which was the previous user-facing model in PowerModelsDistribution, explain how conversions between the model happen in practice, and give an example of how to do this conversion manually\n", + "\n", + "In practice, unless the user is interested, the conversion between the `ENGINEERING` and `MATHEMATICAL` models should be seemless and invisible to the user. By providing an `ENGINEERING` model to a `run_` command the `run_mc_model` command will know to convert the model to `MATHEMATICAL`, which will be used to the generate the JuMP model that will actually be optimized. Similarly, the solution generated by this optimization will be automatically converted back to the format of the `ENGINEERING` model.\n", + "\n", + "Let's first take a look at how to convert to the `MATHEMATICAL` model" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"ut_trans_3w_dyy_1.dss\"\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 36 in \"ut_trans_3w_dyy_1.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 39 in \"ut_trans_3w_dyy_1.dss\" is not supported, skipping.\u001b[39m\n", - "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.1, 0.0]\u001b[39m\n" + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0]\u001b[39m\n" ] }, { "data": { "text/plain": [ - "Dict{String,Any} with 3 entries:\n", - " \"1\" => Dict{String,Any}(\"polarity\"=>1,\"tm_min\"=>[0.9, 0.9, 0.9],\"t_vbase\"=>0.…\n", - " \"2\" => Dict{String,Any}(\"polarity\"=>1,\"tm_min\"=>[0.9, 0.9, 0.9],\"t_vbase\"=>0.…\n", - " \"3\" => Dict{String,Any}(\"polarity\"=>1,\"tm_min\"=>[0.9, 0.9, 0.9],\"t_vbase\"=>0.…" + "Dict{String,Any} with 18 entries:\n", + " \"bus\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"grounded\"=>Bool[0, 0…\n", + " \"name\" => \"3bus_example\"\n", + " \"dcline\" => Dict{String,Any}()\n", + " \"map\" => Dict{String,Any}[Dict(\"unmap_function\"=>\"_map_math2eng_root!…\n", + " \"settings\" => Dict{String,Any}(\"sbase_default\"=>500.0,\"vbases_default\"=>Di…\n", + " \"gen\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"pg\"=>[0.0, 0.0, 0.0]…\n", + " \"branch\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"br_r\"=>[1.09406 0.43…\n", + " \"storage\" => Dict{String,Any}()\n", + " \"switch\" => Dict{String,Any}()\n", + " \"basekv\" => 0.23094\n", + " \"baseMVA\" => 0.5\n", + " \"conductors\" => 3\n", + " \"per_unit\" => true\n", + " \"data_model\" => MATHEMATICAL\n", + " \"shunt\" => Dict{String,Any}()\n", + " \"transformer\" => Dict{String,Any}()\n", + " \"bus_lookup\" => Dict{Any,Int64}(\"primary\"=>1,\"sourcebus\"=>2,\"loadbus\"=>3)\n", + " \"load\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"model\"=>POWER,\"conne…" ] }, - "execution_count": 29, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "math_tr = parse_file(\"../test/data/opendss/ut_trans_3w_dyy_1.dss\"; data_model=\"mathematical\")\n", - "\n", - "math_tr[\"transformer\"]" + "math = transform_data_model(eng)" ] }, { - "cell_type": "code", - "execution_count": 30, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dict{String,Any} with 17 entries:\n", - " \"polarity\" => 1\n", - " \"tm_min\" => [0.9, 0.9, 0.9]\n", - " \"t_vbase\" => 0.57735\n", - " \"tm_step\" => [0.03125, 0.03125, 0.03125]\n", - " \"f_connections\" => [1, 2, 3]\n", - " \"configuration\" => \"delta\"\n", - " \"name\" => \"_virtual_transformer.tx1.1\"\n", - " \"tm_nom\" => 1.73205\n", - " \"source_id\" => \"_virtual_transformer.transformer.tx1.1\"\n", - " \"t_connections\" => [1, 2, 3, 4]\n", - " \"f_bus\" => 1\n", - " \"tm\" => [1.01, 1.01, 1.01]\n", - " \"t_bus\" => 10\n", - " \"tm_max\" => [1.1, 1.1, 1.1]\n", - " \"index\" => 1\n", - " \"fixed\" => Bool[1, 1, 1]\n", - " \"f_vbase\" => 6.35085" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "math_tr[\"transformer\"][\"1\"]" + "There are a couple of things to notice right away. First, the data model transform automatically converts the model to per-unit. Second, there are a lot of empty component sets, whereas in the `ENGINEERING` model, only component types that had components in them were listed. In the `MATHEMATICAL` model certain component dictionaries are always expected to exist, and the `eng2math` conversion functions will automatically populate these.\n", + "\n", + "Next, there are a few unusal fields, such as `\"settings\"`, which previously didn't exist in the `MATHEMATICAL` model. This is used for the per-unit conversion specifically in PowerModelsDistribution. Also, is the `\"map\"` field, which is a `Vector` of Dictionaries that enable the conversion back to `ENGINEERING` from `MATHEMATICAL`. Without this it would be impossible to convert back, and in fact only the solution can be converted, because some properties are combined destructively during the conversion to the `MATHEMATICAL` model, and therefore cannot be reverse engineered. However, since the conversion to `MATHEMATICAL` is not in-place, you will always have a copy of `eng` alongside `math`.\n", + "\n", + "Here is an example of one of the `\"map\"` entries" ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Dict{String,Any} with 17 entries:\n", - " \"polarity\" => 1\n", - " \"tm_min\" => [0.9, 0.9, 0.9]\n", - " \"t_vbase\" => 0.57735\n", - " \"tm_step\" => [0.03125, 0.03125, 0.03125]\n", - " \"f_connections\" => [1, 2, 3, 4]\n", - " \"configuration\" => \"wye\"\n", - " \"name\" => \"_virtual_transformer.tx1.2\"\n", - " \"tm_nom\" => 1.0\n", - " \"source_id\" => \"_virtual_transformer.transformer.tx1.2\"\n", - " \"t_connections\" => [1, 2, 3, 4]\n", - " \"f_bus\" => 3\n", - " \"tm\" => [1.02, 1.02, 1.02]\n", - " \"t_bus\" => 6\n", - " \"tm_max\" => [1.1, 1.1, 1.1]\n", - " \"index\" => 2\n", - " \"fixed\" => Bool[1, 1, 1]\n", - " \"f_vbase\" => 2.3094" + "Dict{String,Any} with 3 entries:\n", + " \"to\" => [\"gen.1\", \"bus.4\", \"branch.3\"]\n", + " \"from\" => \"source\"\n", + " \"unmap_function\" => \"_map_math2eng_voltage_source!\"" ] }, - "execution_count": 31, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "math_tr[\"transformer\"][\"2\"]" + "math[\"map\"][end]" ] }, { - "cell_type": "code", - "execution_count": 32, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dict{String,Any} with 17 entries:\n", - " \"polarity\" => 1\n", - " \"tm_min\" => [0.9, 0.9, 0.9]\n", - " \"t_vbase\" => 0.57735\n", - " \"tm_step\" => [0.03125, 0.03125, 0.03125]\n", - " \"f_connections\" => [1, 2, 3, 4]\n", - " \"configuration\" => \"wye\"\n", - " \"name\" => \"_virtual_transformer.tx1.3\"\n", - " \"tm_nom\" => 1.0\n", - " \"source_id\" => \"_virtual_transformer.transformer.tx1.3\"\n", - " \"t_connections\" => [1, 2, 3, 4]\n", - " \"f_bus\" => 4\n", - " \"tm\" => [1.03, 1.03, 1.03]\n", - " \"t_bus\" => 7\n", - " \"tm_max\" => [1.1, 1.1, 1.1]\n", - " \"index\" => 3\n", - " \"fixed\" => Bool[1, 1, 1]\n", - " \"f_vbase\" => 0.23094" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "math_tr[\"transformer\"][\"3\"]" + "Alternatively, the `MATHEMATICAL` model can be returned directly from the `parse_file` command with the `data_model` keyword argument" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [1.0, 0.0]\u001b[39m\n" + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"case3_unbalanced.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 35 in \"case3_unbalanced.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 37 in \"case3_unbalanced.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0]\u001b[39m\n" ] }, { "data": { "text/plain": [ - "Dict{String,Any} with 10 entries:\n", - " \"solve_time\" => 0.010365\n", - " \"optimizer\" => \"Ipopt\"\n", - " \"termination_status\" => LOCALLY_SOLVED\n", - " \"dual_status\" => FEASIBLE_POINT\n", - " \"primal_status\" => FEASIBLE_POINT\n", - " \"objective\" => 0.0\n", - " \"solution\" => Dict{String,Any}(\"voltage_source\"=>Dict{Any,Any}(\"sou…\n", - " \"data\" => Dict{String,Any}(\"name\"=>\"ut_trans\")\n", - " \"machine\" => Dict(\"cpu\"=>\"Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GH…\n", - " \"objective_lb\" => -Inf" + "Dict{String,Any} with 18 entries:\n", + " \"bus\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"grounded\"=>Bool[0, 0…\n", + " \"name\" => \"3bus_example\"\n", + " \"dcline\" => Dict{String,Any}()\n", + " \"map\" => Dict{String,Any}[Dict(\"unmap_function\"=>\"_map_math2eng_root!…\n", + " \"settings\" => Dict{String,Any}(\"sbase_default\"=>500.0,\"vbases_default\"=>Di…\n", + " \"gen\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"pg\"=>[0.0, 0.0, 0.0]…\n", + " \"branch\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"br_r\"=>[1.09406 0.43…\n", + " \"storage\" => Dict{String,Any}()\n", + " \"switch\" => Dict{String,Any}()\n", + " \"basekv\" => 0.23094\n", + " \"baseMVA\" => 0.5\n", + " \"conductors\" => 3\n", + " \"per_unit\" => true\n", + " \"data_model\" => MATHEMATICAL\n", + " \"shunt\" => Dict{String,Any}()\n", + " \"transformer\" => Dict{String,Any}()\n", + " \"bus_lookup\" => Dict{Any,Int64}(\"primary\"=>1,\"sourcebus\"=>2,\"loadbus\"=>3)\n", + " \"load\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"model\"=>POWER,\"conne…" ] }, - "execution_count": 33, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "result_eng_pu = run_mc_pf(eng, ACPPowerModel, ipopt_solver; make_si=false)" + "math = parse_file(\"../test/data/opendss/case3_unbalanced.dss\"; data_model=MATHEMATICAL)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Running `MATHEMATICAL` models\n", + "\n", + "There is very little difference from the user point-of-view in running `MATHEMATICAL` models other than the results will not be automatically converted back to the the format of the `ENGINEERING` model" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Dict{String,Any} with 2 entries:\n", - " \"va\" => [-8.75226e-8, -120.0, 120.0]\n", - " \"vm\" => [6.35085, 6.35085, 6.35085]" + "Dict{String,Any} with 7 entries:\n", + " \"baseMVA\" => 0.5\n", + " \"branch\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"qf\"=>[0.00618975, 0.0…\n", + " \"gen\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"qg_bus\"=>[0.00635515,…\n", + " \"load\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"qd_bus\"=>[0.0, 0.006,…\n", + " \"conductors\" => 3\n", + " \"bus\" => Dict{String,Any}(\"4\"=>Dict{String,Any}(\"va\"=>[0.0, -2.0944, 2…\n", + " \"per_unit\" => true" ] }, - "execution_count": 34, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "result_eng[\"solution\"][\"bus\"][\"sourcebus\"]" + "result_math = run_mc_opf(math, ACPPowerModel, ipopt_solver)\n", + "\n", + "result_math[\"solution\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also possible to manually convert the solution back to the `ENGINEERING` format, provided you have the __map__" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Dict{String,Any} with 2 entries:\n", - " \"va\" => [-8.75226e-8, -120.0, 120.0]\n", - " \"vm\" => [1.0, 1.0, 1.0]" + "Dict{String,Any} with 6 entries:\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"qg_bus\"=>[3.177…\n", + " \"line\" => Dict{Any,Any}(\"quad\"=>Dict{String,Any}(\"qf\"=>[3.09488, 3.…\n", + " \"settings\" => Dict{String,Any}(\"sbase\"=>0.5)\n", + " \"load\" => Dict{Any,Any}(\"l2\"=>Dict{String,Any}(\"qd_bus\"=>[0.0, 3.0,…\n", + " \"bus\" => Dict{Any,Any}(\"primary\"=>Dict{String,Any}(\"va\"=>[-0.22425…\n", + " \"per_unit\" => false" ] }, - "execution_count": 35, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "result_eng_pu[\"solution\"][\"bus\"][\"sourcebus\"]" + "result_eng = transform_solution(result_math[\"solution\"], math)" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "## Conclusion\n", + "\n", + "This concludes the introduction to the `ENGINEERING` data model and conversion to the `MATHEMATICAL` model. We hope that you will find this new data model abstraction easy to use and simple to understand" + ] } ], "metadata": { "kernelspec": { - "display_name": "Julia 1.3.1", + "display_name": "Julia 1.4.1", "language": "julia", - "name": "julia-1.3" + "name": "julia-1.4" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.3.1" + "version": "1.4.1" }, "varInspector": { "cols": { diff --git a/src/io/common.jl b/src/io/common.jl index d5eb7f5ee..5ecd8587c 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -8,7 +8,7 @@ function parse_file( data_model::DataModel=ENGINEERING, import_all::Bool=false, bank_transformers::Bool=true, - transformations::Vector{Any}=[], + transformations::Vector{<:Any}=[], build_multinetwork::Bool=false, kron_reduced::Bool=true, time_series::String="daily" From 7354c3ba5a6b09e4ded0ff29ba75415bce365f17 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 6 May 2020 16:20:52 -0600 Subject: [PATCH 211/224] FIX: Changelog --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f61c652c6..0dc9d40e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,10 +15,10 @@ - Adds Dierckx dependency for creation of 1d splines for xycurve object - Adds some commonly used InfrastructureModels and PowerModels functions as exports - Adds model building functions `add_{component}!` to aid in building simple models for testing (experimental) -- Add run_mc_model (adds ref_add_arcs_transformer! to ref_extensions, and sets multiconductor=true by default) (breaking) -- Rename ref_add_arcs_trans -> ref_add_arcs_transformer (breaking) -- Update `count_nodes`, now counts source nodes as well, excludes _virtual objects -- Change _PMs and _IMs to _PM, _IM, respectively +- Add run_mc_model (adds `ref_add_arcs_transformer!` to ref_extensions, and sets `multiconductor=true` by default) (breaking) +- Rename `ref_add_arcs_trans` -> `ref_add_arcs_transformer` (breaking) +- Update `count_nodes`, now counts source nodes as well, excludes \_virtual objects +- Change \_PMs and \_IMs to \_PM, \_IM, respectively - Add example for PMD usage (see Jupyter notebooks in `/examples`) - Update transformer mathematical model - Introduce new data models: ENGINEERING, MATHEMATICAL (see data model documentation) (breaking) @@ -26,7 +26,7 @@ - Updates DSS paser to parse more options/commands, moves these into `"options"` dict (breaking) - Updates how dss `like` is applied to better match opendss (almost all properties are copied with like) (breaking) - Add support for new OpenDSS components (loadshape, xfmrcode, xycurve) -- Add support for JuMP v0.22 (exports optimizer_with_attributtes by default) +- Add support for JuMP v0.22 (exports `optimizer_with_attributtes` by default) - Add support for PowerModels v0.16 (breaking) - Add support for Memento v0.13, v1.0 From 3801d7e3aef2592b476a9cdbcf978c1e689b9390 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 7 May 2020 10:08:38 -0600 Subject: [PATCH 212/224] ADD: examples of multinetworks fixes bugs in multinetwork processing and running ADD: multinetwork problem example --- docs/src/engineering_model.md | 208 +++++++++++++- examples/engineering_model.ipynb | 392 ++++++++++++++++++++++++--- src/core/constraint_template.jl | 39 +++ src/data_model/eng2math.jl | 2 +- src/data_model/math2eng.jl | 88 +++--- src/data_model/units.jl | 119 +++++--- src/data_model/utils.jl | 2 +- src/form/apo.jl | 11 + src/io/common.jl | 8 +- src/prob/common.jl | 11 +- src/prob/debug.jl | 4 +- src/prob/mld.jl | 6 +- src/prob/opf.jl | 2 +- src/prob/opf_oltc.jl | 2 +- src/prob/pf.jl | 2 +- src/prob/test.jl | 18 ++ test/data/opendss/case3_balanced.dss | 7 +- 17 files changed, 787 insertions(+), 134 deletions(-) diff --git a/docs/src/engineering_model.md b/docs/src/engineering_model.md index 43952ad7f..3f9c76947 100644 --- a/docs/src/engineering_model.md +++ b/docs/src/engineering_model.md @@ -291,6 +291,74 @@ eng_all["line"] You will note the presence of `"dss"` dictionaries under components, and `"dss_options"` at the root level +### Time Series Parsing Example + +In the `ENGINEERING` model, we have included the `time_series` data type, which holds all time series data and can be referred to similar to `"linecode"` as demonstrated above. + +Below we can see an example of a parse that includes some time_series components + + +```julia +eng_ts = parse_file("../test/data/opendss/case3_balanced.dss"; time_series="daily") +``` + + [info | PowerModels]: Circuit has been reset with the "clear" on line 1 in "case3_balanced.dss" + [warn | PowerModels]: Command "calcvoltagebases" on line 36 in "case3_balanced.dss" is not supported, skipping. + [warn | PowerModels]: Command "solve" on line 38 in "case3_balanced.dss" is not supported, skipping. + + + + + + Dict{String,Any} with 10 entries: + "voltage_source" => Dict{Any,Any}("source"=>Dict{String,Any}("source_id"=>"vs… + "name" => "3bus_example" + "line" => Dict{Any,Any}("quad"=>Dict{String,Any}("cm_ub"=>[400.0, 4… + "settings" => Dict{String,Any}("sbase_default"=>100000.0,"vbases_defaul… + "files" => ["case3_balanced.dss"] + "load" => Dict{Any,Any}("l2"=>Dict{String,Any}("model"=>POWER,"conn… + "bus" => Dict{Any,Any}("primary"=>Dict{String,Any}("rg"=>Float64[]… + "linecode" => Dict{Any,Any}("556mcm"=>Dict{String,Any}("b_fr"=>[25.4648… + "time_series" => Dict{Any,Any}("ls1"=>Dict{String,Any}("source_id"=>"loads… + "data_model" => ENGINEERING + + + + +```julia +eng_ts["load"]["l1"]["time_series"] +``` + + + + + Dict{String,Any} with 2 entries: + "qd_nom" => "ls1" + "pd_nom" => "ls1" + + + +You can see that under the actual component, in this case a `"load"`, that there is a `"time_series"` dictionary that contains `ENGINEERING` model variable names and references to the identifiers of a root-level `time_series` object, + + +```julia +eng_ts["time_series"]["ls1"] +``` + + + + + Dict{String,Any} with 5 entries: + "source_id" => "loadshape.ls1" + "time" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + "replace" => true + "values" => [0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.… + "offset" => 0.0 + + + +This feature is useful for building multinetwork data structures, which will be described below in the section on the `MATHEMATICAL` model + ## Running Optimal Power Flow In this section we introduce how to run an optimal power flow (opf) in PowerModelsDistribution on an engineering data model @@ -322,7 +390,7 @@ result = run_mc_opf(eng, ACPPowerModel, ipopt_solver) Dict{String,Any} with 8 entries: - "solve_time" => 3.9884 + "solve_time" => 4.97333 "optimizer" => "Ipopt" "termination_status" => LOCALLY_SOLVED "dual_status" => FEASIBLE_POINT @@ -406,7 +474,7 @@ result_bf = run_mc_opf(eng, SOCNLPUBFPowerModel, ipopt_solver) Dict{String,Any} with 8 entries: - "solve_time" => 0.190441 + "solve_time" => 0.246656 "optimizer" => "Ipopt" "termination_status" => LOCALLY_SOLVED "dual_status" => FEASIBLE_POINT @@ -417,6 +485,32 @@ result_bf = run_mc_opf(eng, SOCNLPUBFPowerModel, ipopt_solver) +### Running Time Series Models + +By default, `time_series` object will be ignored when running a model. To use the time series information you will need to have a multinetwork problem specification + +In the example below we use a test case, which is not exported by default, and therefore requires the specification of the PowerModelsDistribution namespace + + +```julia +result_mn = PowerModelsDistribution._run_mc_mn_opb(eng_ts, NFAPowerModel, ipopt_solver) +``` + + + + + Dict{String,Any} with 8 entries: + "solve_time" => 0.334904 + "optimizer" => "Ipopt" + "termination_status" => LOCALLY_SOLVED + "dual_status" => FEASIBLE_POINT + "primal_status" => FEASIBLE_POINT + "objective" => 0.0 + "solution" => Dict{String,Any}("nw"=>Dict{String,Any}("8"=>Dict{Any… + "objective_lb" => -Inf + + + ## Engineering Model Transformations One of the power things about the engineering model is that data transformations are much more simple. Here we illustrate two examples that are currently included in PowerModelsDistribution, but writing your own data transformation functions will be trivial, as we will show @@ -578,6 +672,10 @@ eng_ut["bus"]["2"] +### Transformations on Multinetworks + +Transformations on Multinetworks should happen __before__ the network is converted into a `MATHEMATICAL` data model, so that they can generally follow the same pattern as shown above and can be seen in the `make_lossless!` and `apply_voltage_bounds!` functions already in PowerModelsDistribution + ## Mathematical Model In this section we introduce the mathematical model, which was the previous user-facing model in PowerModelsDistribution, explain how conversions between the model happen in practice, and give an example of how to do this conversion manually @@ -678,6 +776,73 @@ math = parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model=MATHEM +### Multinetworks + +In this subsection we cover parsing into a multinetwork data structure, which is a structure that only exists in the `MATHEMATICAL` model + +For those unfamiliar, the InfrastructureModels family of packages has a feature called multinetworks, which is useful for, among other things, running optimization problems on time series type problems. + +Multinetwork data structures are formatted like so +mn = Dict{String,Any}( + "multinetwork" => true, + "nw" => Dict{String,Any}( + "1" => Dict{String,Any}( + "bus" => Dict{String,Any}(), + ... + ), + ... + ), + ... +) +To automatically create a multinetwork structure from an engineering model that contains `time_series` elements, we can use the `build_multinetwork` keyword argument in `transform_data_model` + + +```julia +math_mn = transform_data_model(eng_ts; build_multinetwork=true) +``` + + + + + Dict{String,Any} with 8 entries: + "name" => "10 replicates of 3bus_example" + "map" => Dict{String,Any}[Dict("unmap_function"=>"_map_math2eng_root… + "settings" => Dict{String,Any}("sbase_default"=>100000.0,"vbases_default"… + "bus_lookup" => Dict{Any,Int64}("primary"=>1,"sourcebus"=>2,"loadbus"=>3) + "multinetwork" => true + "nw" => Dict{String,Any}("3"=>Dict{String,Any}("bus"=>Dict{String,A… + "per_unit" => true + "data_model" => MATHEMATICAL + + + +Alternatively, we can use `parse_file` with the `build_multinetwork` keyword argument combined with `data_model=MATHEMATICAL` + + +```julia +math_mn = parse_file("../test/data/opendss/case3_balanced.dss"; build_multinetwork=true, data_model=MATHEMATICAL) +``` + + [info | PowerModels]: Circuit has been reset with the "clear" on line 1 in "case3_balanced.dss" + [warn | PowerModels]: Command "calcvoltagebases" on line 36 in "case3_balanced.dss" is not supported, skipping. + [warn | PowerModels]: Command "solve" on line 38 in "case3_balanced.dss" is not supported, skipping. + + + + + + Dict{String,Any} with 8 entries: + "name" => "10 replicates of 3bus_example" + "map" => Dict{String,Any}[Dict("unmap_function"=>"_map_math2eng_root… + "settings" => Dict{String,Any}("sbase_default"=>100000.0,"vbases_default"… + "bus_lookup" => Dict{Any,Int64}("primary"=>1,"sourcebus"=>2,"loadbus"=>3) + "multinetwork" => true + "nw" => Dict{String,Any}("3"=>Dict{String,Any}("bus"=>Dict{String,A… + "per_unit" => true + "data_model" => MATHEMATICAL + + + ### Running `MATHEMATICAL` models There is very little difference from the user point-of-view in running `MATHEMATICAL` models other than the results will not be automatically converted back to the the format of the `ENGINEERING` model @@ -707,7 +872,7 @@ It is also possible to manually convert the solution back to the `ENGINEERING` f ```julia -result_eng = transform_solution(result_math["solution"], math) +sol_eng = transform_solution(result_math["solution"], math) ``` @@ -723,6 +888,43 @@ result_eng = transform_solution(result_math["solution"], math) +#### Running `MATHEMATICAL` Multinetworks + +As with the `ENGINEERING` example of running a multinetwork problem, you will need a multinetwork problem specification, and as with the previous single `MATHEMATICAL` network example above, we only obtain the `MATHEMATICAL` solution, and can transform the solution in the same manner as before + + +```julia +result_math_mn = PowerModelsDistribution._run_mc_mn_opb(math_mn, NFAPowerModel, ipopt_solver) + +result_math_mn["solution"]["nw"]["1"] +``` + + + + + Dict{String,Any} with 3 entries: + "baseMVA" => 100.0 + "gen" => Dict{String,Any}("1"=>Dict{String,Any}("pg"=>[0.0, 0.0, 0.0])) + "conductors" => 3 + + + + +```julia +sol_eng_mn = transform_solution(result_math_mn["solution"], math_mn) + +sol_eng_mn["nw"]["1"] +``` + + + + + Dict{Any,Any} with 2 entries: + "voltage_source" => Dict{Any,Any}("source"=>Dict{String,Any}("pg"=>[0.0, 0.0,… + "settings" => Dict{String,Any}("sbase"=>100.0) + + + ## Conclusion This concludes the introduction to the `ENGINEERING` data model and conversion to the `MATHEMATICAL` model. We hope that you will find this new data model abstraction easy to use and simple to understand diff --git a/examples/engineering_model.ipynb b/examples/engineering_model.ipynb index cf62e57eb..3ca387e01 100644 --- a/examples/engineering_model.ipynb +++ b/examples/engineering_model.ipynb @@ -467,6 +467,117 @@ "You will note the presence of `\"dss\"` dictionaries under components, and `\"dss_options\"` at the root level" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Time Series Parsing Example\n", + "\n", + "In the `ENGINEERING` model, we have included the `time_series` data type, which holds all time series data and can be referred to similar to `\"linecode\"` as demonstrated above.\n", + "\n", + "Below we can see an example of a parse that includes some time_series components" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"case3_balanced.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 36 in \"case3_balanced.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 38 in \"case3_balanced.dss\" is not supported, skipping.\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 10 entries:\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"vs…\n", + " \"name\" => \"3bus_example\"\n", + " \"line\" => Dict{Any,Any}(\"quad\"=>Dict{String,Any}(\"cm_ub\"=>[400.0, 4…\n", + " \"settings\" => Dict{String,Any}(\"sbase_default\"=>100000.0,\"vbases_defaul…\n", + " \"files\" => [\"case3_balanced.dss\"]\n", + " \"load\" => Dict{Any,Any}(\"l2\"=>Dict{String,Any}(\"model\"=>POWER,\"conn…\n", + " \"bus\" => Dict{Any,Any}(\"primary\"=>Dict{String,Any}(\"rg\"=>Float64[]…\n", + " \"linecode\" => Dict{Any,Any}(\"556mcm\"=>Dict{String,Any}(\"b_fr\"=>[25.4648…\n", + " \"time_series\" => Dict{Any,Any}(\"ls1\"=>Dict{String,Any}(\"source_id\"=>\"loads…\n", + " \"data_model\" => ENGINEERING" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ts = parse_file(\"../test/data/opendss/case3_balanced.dss\"; time_series=\"daily\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 2 entries:\n", + " \"qd_nom\" => \"ls1\"\n", + " \"pd_nom\" => \"ls1\"" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ts[\"load\"][\"l1\"][\"time_series\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can see that under the actual component, in this case a `\"load\"`, that there is a `\"time_series\"` dictionary that contains `ENGINEERING` model variable names and references to the identifiers of a root-level `time_series` object, " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 5 entries:\n", + " \"source_id\" => \"loadshape.ls1\"\n", + " \"time\" => [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]\n", + " \"replace\" => true\n", + " \"values\" => [0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.…\n", + " \"offset\" => 0.0" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng_ts[\"time_series\"][\"ls1\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This feature is useful for building multinetwork data structures, which will be described below in the section on the `MATHEMATICAL` model" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -486,7 +597,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -507,7 +618,7 @@ "data": { "text/plain": [ "Dict{String,Any} with 8 entries:\n", - " \"solve_time\" => 3.9884\n", + " \"solve_time\" => 4.97333\n", " \"optimizer\" => \"Ipopt\"\n", " \"termination_status\" => LOCALLY_SOLVED\n", " \"dual_status\" => FEASIBLE_POINT\n", @@ -517,7 +628,7 @@ " \"objective_lb\" => -Inf" ] }, - "execution_count": 13, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -535,7 +646,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -550,7 +661,7 @@ " \"per_unit\" => false" ] }, - "execution_count": 14, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -568,7 +679,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -579,7 +690,7 @@ " \"vm\" => [0.222521, 0.226727, 0.225577]" ] }, - "execution_count": 15, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -597,7 +708,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -615,7 +726,7 @@ " \"vm\" => [0.963546, 0.981757, 0.976779]" ] }, - "execution_count": 16, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -637,7 +748,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -651,7 +762,7 @@ "data": { "text/plain": [ "Dict{String,Any} with 8 entries:\n", - " \"solve_time\" => 0.190441\n", + " \"solve_time\" => 0.246656\n", " \"optimizer\" => \"Ipopt\"\n", " \"termination_status\" => LOCALLY_SOLVED\n", " \"dual_status\" => FEASIBLE_POINT\n", @@ -661,7 +772,7 @@ " \"objective_lb\" => -Inf" ] }, - "execution_count": 17, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -670,6 +781,45 @@ "result_bf = run_mc_opf(eng, SOCNLPUBFPowerModel, ipopt_solver)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Running Time Series Models\n", + "\n", + "By default, `time_series` object will be ignored when running a model. To use the time series information you will need to have a multinetwork problem specification\n", + "\n", + "In the example below we use a test case, which is not exported by default, and therefore requires the specification of the PowerModelsDistribution namespace" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 8 entries:\n", + " \"solve_time\" => 0.334904\n", + " \"optimizer\" => \"Ipopt\"\n", + " \"termination_status\" => LOCALLY_SOLVED\n", + " \"dual_status\" => FEASIBLE_POINT\n", + " \"primal_status\" => FEASIBLE_POINT\n", + " \"objective\" => 0.0\n", + " \"solution\" => Dict{String,Any}(\"nw\"=>Dict{String,Any}(\"8\"=>Dict{Any…\n", + " \"objective_lb\" => -Inf" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_mn = PowerModelsDistribution._run_mc_mn_opb(eng_ts, NFAPowerModel, ipopt_solver)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -683,7 +833,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -717,7 +867,7 @@ " \"imag\" => 0.11" ] }, - "execution_count": 18, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -737,7 +887,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -762,7 +912,7 @@ " \"imag\" => 0.0" ] }, - "execution_count": 19, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -782,7 +932,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -816,7 +966,7 @@ " \"imag\" => 0.0" ] }, - "execution_count": 28, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -836,7 +986,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 25, "metadata": {}, "outputs": [ { @@ -852,7 +1002,7 @@ " \"xg\" => [0.0]" ] }, - "execution_count": 29, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -872,7 +1022,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -897,7 +1047,7 @@ " \"xg\" => [0.0]" ] }, - "execution_count": 30, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -908,6 +1058,15 @@ "eng_ut[\"bus\"][\"2\"]" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Transformations on Multinetworks\n", + "\n", + "Transformations on Multinetworks should happen __before__ the network is converted into a `MATHEMATICAL` data model, so that they can generally follow the same pattern as shown above and can be seen in the `make_lossless!` and `apply_voltage_bounds!` functions already in PowerModelsDistribution" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -923,7 +1082,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -957,7 +1116,7 @@ " \"load\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"model\"=>POWER,\"conne…" ] }, - "execution_count": 23, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -979,7 +1138,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -991,7 +1150,7 @@ " \"unmap_function\" => \"_map_math2eng_voltage_source!\"" ] }, - "execution_count": 24, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -1009,7 +1168,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 29, "metadata": {}, "outputs": [ { @@ -1046,7 +1205,7 @@ " \"load\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"model\"=>POWER,\"conne…" ] }, - "execution_count": 25, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -1055,6 +1214,115 @@ "math = parse_file(\"../test/data/opendss/case3_unbalanced.dss\"; data_model=MATHEMATICAL)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Multinetworks\n", + "\n", + "In this subsection we cover parsing into a multinetwork data structure, which is a structure that only exists in the `MATHEMATICAL` model\n", + "\n", + "For those unfamiliar, the InfrastructureModels family of packages has a feature called multinetworks, which is useful for, among other things, running optimization problems on time series type problems. \n", + "\n", + "Multinetwork data structures are formatted like so" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "mn = Dict{String,Any}(\n", + " \"multinetwork\" => true,\n", + " \"nw\" => Dict{String,Any}(\n", + " \"1\" => Dict{String,Any}(\n", + " \"bus\" => Dict{String,Any}(),\n", + " ...\n", + " ),\n", + " ...\n", + " ),\n", + " ...\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To automatically create a multinetwork structure from an engineering model that contains `time_series` elements, we can use the `build_multinetwork` keyword argument in `transform_data_model`" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 8 entries:\n", + " \"name\" => \"10 replicates of 3bus_example\"\n", + " \"map\" => Dict{String,Any}[Dict(\"unmap_function\"=>\"_map_math2eng_root…\n", + " \"settings\" => Dict{String,Any}(\"sbase_default\"=>100000.0,\"vbases_default\"…\n", + " \"bus_lookup\" => Dict{Any,Int64}(\"primary\"=>1,\"sourcebus\"=>2,\"loadbus\"=>3)\n", + " \"multinetwork\" => true\n", + " \"nw\" => Dict{String,Any}(\"3\"=>Dict{String,Any}(\"bus\"=>Dict{String,A…\n", + " \"per_unit\" => true\n", + " \"data_model\" => MATHEMATICAL" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_mn = transform_data_model(eng_ts; build_multinetwork=true)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, we can use `parse_file` with the `build_multinetwork` keyword argument combined with `data_model=MATHEMATICAL`" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Circuit has been reset with the \"clear\" on line 1 in \"case3_balanced.dss\"\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"calcvoltagebases\" on line 36 in \"case3_balanced.dss\" is not supported, skipping.\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Command \"solve\" on line 38 in \"case3_balanced.dss\" is not supported, skipping.\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 8 entries:\n", + " \"name\" => \"10 replicates of 3bus_example\"\n", + " \"map\" => Dict{String,Any}[Dict(\"unmap_function\"=>\"_map_math2eng_root…\n", + " \"settings\" => Dict{String,Any}(\"sbase_default\"=>100000.0,\"vbases_default\"…\n", + " \"bus_lookup\" => Dict{Any,Int64}(\"primary\"=>1,\"sourcebus\"=>2,\"loadbus\"=>3)\n", + " \"multinetwork\" => true\n", + " \"nw\" => Dict{String,Any}(\"3\"=>Dict{String,Any}(\"bus\"=>Dict{String,A…\n", + " \"per_unit\" => true\n", + " \"data_model\" => MATHEMATICAL" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "math_mn = parse_file(\"../test/data/opendss/case3_balanced.dss\"; build_multinetwork=true, data_model=MATHEMATICAL)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1066,7 +1334,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 32, "metadata": {}, "outputs": [ { @@ -1082,7 +1350,7 @@ " \"per_unit\" => true" ] }, - "execution_count": 26, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -1102,7 +1370,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 33, "metadata": {}, "outputs": [ { @@ -1117,13 +1385,71 @@ " \"per_unit\" => false" ] }, - "execution_count": 27, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sol_eng = transform_solution(result_math[\"solution\"], math)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Running `MATHEMATICAL` Multinetworks\n", + "\n", + "As with the `ENGINEERING` example of running a multinetwork problem, you will need a multinetwork problem specification, and as with the previous single `MATHEMATICAL` network example above, we only obtain the `MATHEMATICAL` solution, and can transform the solution in the same manner as before" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 3 entries:\n", + " \"baseMVA\" => 100.0\n", + " \"gen\" => Dict{String,Any}(\"1\"=>Dict{String,Any}(\"pg\"=>[0.0, 0.0, 0.0]))\n", + " \"conductors\" => 3" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_math_mn = PowerModelsDistribution._run_mc_mn_opb(math_mn, NFAPowerModel, ipopt_solver)\n", + "\n", + "result_math_mn[\"solution\"][\"nw\"][\"1\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{Any,Any} with 2 entries:\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"pg\"=>[0.0, 0.0,…\n", + " \"settings\" => Dict{String,Any}(\"sbase\"=>100.0)" + ] + }, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "result_eng = transform_solution(result_math[\"solution\"], math)" + "sol_eng_mn = transform_solution(result_math_mn[\"solution\"], math_mn)\n", + "\n", + "sol_eng_mn[\"nw\"][\"1\"]" ] }, { diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 3c473d1cf..edc6e7be9 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -568,3 +568,42 @@ function constraint_mc_bus_voltage_drop(pm::_PM.AbstractPowerModel, i::Int; nw:: constraint_mc_bus_voltage_drop(pm, nw, i, f_bus, t_bus, f_idx, r, x, tr, ti, tm) end + + +"ensures that power generation and demand are balanced" +function constraint_mc_network_power_balance(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) + comp_bus_ids = ref(pm, nw, :components, i) + + comp_gen_ids = Set{Int64}() + for bus_id in comp_bus_ids, gen_id in PowerModels.ref(pm, nw, :bus_gens, bus_id) + push!(comp_gen_ids, gen_id) + end + + comp_loads = Set() + for bus_id in comp_bus_ids, load_id in PowerModels.ref(pm, nw, :bus_loads, bus_id) + push!(comp_loads, PowerModels.ref(pm, nw, :load, load_id)) + end + + comp_shunts = Set() + for bus_id in comp_bus_ids, shunt_id in PowerModels.ref(pm, nw, :bus_shunts, bus_id) + push!(comp_shunts, PowerModels.ref(pm, nw, :shunt, shunt_id)) + end + + comp_branches = Set() + for (branch_id, branch) in PowerModels.ref(pm, nw, :branch) + if in(branch["f_bus"], comp_bus_ids) && in(branch["t_bus"], comp_bus_ids) + push!(comp_branches, branch) + end + end + + comp_pd = Dict(load["index"] => (load["load_bus"], load["pd"]) for load in comp_loads) + comp_qd = Dict(load["index"] => (load["load_bus"], load["qd"]) for load in comp_loads) + + comp_gs = Dict(shunt["index"] => (shunt["shunt_bus"], shunt["gs"]) for shunt in comp_shunts) + comp_bs = Dict(shunt["index"] => (shunt["shunt_bus"], shunt["bs"]) for shunt in comp_shunts) + + comp_branch_g = Dict(branch["index"] => (branch["f_bus"], branch["t_bus"], branch["br_r"], branch["br_x"], branch["tap"], branch["g_fr"], branch["g_to"]) for branch in comp_branches) + comp_branch_b = Dict(branch["index"] => (branch["f_bus"], branch["t_bus"], branch["br_r"], branch["br_x"], branch["tap"], branch["b_fr"], branch["b_to"]) for branch in comp_branches) + + constraint_mc_network_power_balance(pm, nw, i, comp_gen_ids, comp_pd, comp_qd, comp_gs, comp_bs, comp_branch_g, comp_branch_b) +end diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index 007f2f190..e67a2a391 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -36,7 +36,7 @@ const _math_edge_elements = Vector{String}([ "list of multinetwork keys that belong at the root level" const _pmd_math_global_keys = Set{String}([ - "conductors", "data_model", "per_unit", "name", "settings", "map", "bus_lookup" + "data_model", "per_unit", "name", "settings", "map", "bus_lookup" ]) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index da226a379..d517d1fff 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -1,7 +1,15 @@ "" function transform_solution(solution_math::Dict{String,<:Any}, data_math::Dict{String,<:Any}; map::Union{Vector{Dict{String,<:Any}},Missing}=missing, make_si::Bool=true)::Dict{String,Any} - @assert get(data_math, "data_model", MATHEMATICAL) == MATHEMATICAL "provided solution cannot be converted to an engineering model: data_model not recognized" - solution_eng = Dict{String, Any}() + @assert get(data_math, "data_model", MATHEMATICAL) == MATHEMATICAL "provided solution cannot be converted to an engineering model" + if ismultinetwork(data_math) + solution_eng = Dict{String,Any}( + "nw" => Dict{String,Any}( + k => Dict{Any,Any}() for k in keys(data_math["nw"]) + ) + ) + else + solution_eng = Dict{String,Any}() + end solution_math = solution_make_si(solution_math, data_math; mult_vbase=make_si, mult_sbase=make_si, convert_rad2deg=true) @@ -9,12 +17,28 @@ function transform_solution(solution_math::Dict{String,<:Any}, data_math::Dict{S @assert !isempty(map) "Map is empty, cannot map solution up to engineering model" for map_item in reverse(map) - getfield(PowerModelsDistribution, Symbol(map_item["unmap_function"]))(solution_eng, solution_math, map_item) + if ismultinetwork(data_math) && map_item["unmap_function"] != "_map_math2eng_root!" + for (n, nw) in solution_math["nw"] + getfield(PowerModelsDistribution, Symbol(map_item["unmap_function"]))(solution_eng["nw"][n], nw, map_item) + end + else + getfield(PowerModelsDistribution, Symbol(map_item["unmap_function"]))(solution_eng, solution_math, map_item) + end end - for (k,v) in solution_eng - if isempty(v) - delete!(solution_eng, k) + if ismultinetwork(data_math) + for (n,nw) in solution_eng["nw"] + for (k,v) in nw + if isempty(v) + delete!(nw, k) + end + end + end + else + for (k,v) in solution_eng + if isempty(v) + delete!(solution_eng, k) + end end end @@ -23,7 +47,7 @@ end "" -function _map_math2eng_voltage_source!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_voltage_source!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) if !haskey(data_eng, "voltage_source") data_eng["voltage_source"] = Dict{Any,Any}() end @@ -48,7 +72,7 @@ end "" -function _map_math2eng_bus!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_bus!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "bus", map) math_obj = _get_math_obj(data_math, map["to"]) @@ -61,7 +85,7 @@ end "" -function _map_math2eng_load!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_load!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "load", map) math_obj = _get_math_obj(data_math, map["to"]) @@ -73,19 +97,7 @@ function _map_math2eng_load!(data_eng::Dict{String,<:Any}, data_math::Dict{Strin end -function _map_math2eng_shunt_capacitor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) - eng_obj = _init_unmap_eng_obj!(data_eng, "shunt_capacitor", map) - math_obj = _get_math_obj(data_math, map["to"]) - - merge!(eng_obj, math_obj) - - if !isempty(eng_obj) - data_eng["shunt_capacitor"][map["from"]] = eng_obj - end -end - - -function _map_math2eng_shunt!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_shunt!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "shunt", map) math_obj = _get_math_obj(data_math, map["to"]) @@ -97,7 +109,7 @@ end end -function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_generator!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "generator", map) math_obj = _get_math_obj(data_math, map["to"]) @@ -109,7 +121,7 @@ function _map_math2eng_generator!(data_eng::Dict{String,<:Any}, data_math::Dict{ end -function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_solar!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "solar", map) math_obj = _get_math_obj(data_math, map["to"]) @@ -121,7 +133,7 @@ function _map_math2eng_solar!(data_eng::Dict{String,<:Any}, data_math::Dict{Stri end -function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_storage!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "storage", map) math_obj = _get_math_obj(data_math, map["to"]) @@ -133,7 +145,7 @@ function _map_math2eng_storage!(data_eng::Dict{String,<:Any}, data_math::Dict{St end -function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_line!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "line", map) math_obj = _get_math_obj(data_math, map["to"]) @@ -145,19 +157,7 @@ function _map_math2eng_line!(data_eng::Dict{String,<:Any}, data_math::Dict{Strin end -function _map_math2eng_line_reactor!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) - eng_obj = _init_unmap_eng_obj!(data_eng, "line_reactor", map) - math_obj = _get_math_obj(data_math, map["to"]) - - merge!(eng_obj, math_obj) - - if !isempty(eng_obj) - data_eng["line_reactor"][map["from"]] = eng_obj - end -end - - -function _map_math2eng_switch!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_switch!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "switch", map) @@ -174,7 +174,7 @@ function _map_math2eng_switch!(data_eng::Dict{String,<:Any}, data_math::Dict{Str end -function _map_math2eng_transformer!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) +function _map_math2eng_transformer!(data_eng::Dict{<:Any,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) eng_obj = _init_unmap_eng_obj!(data_eng, "transformer", map) trans_2wa_ids = [index for (comp_type, index) in split.(map["to"], ".", limit=2) if comp_type=="transformer"] @@ -197,5 +197,11 @@ end function _map_math2eng_root!(data_eng::Dict{String,<:Any}, data_math::Dict{String,<:Any}, map::Dict{String,<:Any}) data_eng["per_unit"] = data_math["per_unit"] - data_eng["settings"] = Dict{String,Any}("sbase" => data_math["baseMVA"]) + if !ismultinetwork(data_math) + data_eng["settings"] = Dict{String,Any}("sbase" => data_math["baseMVA"]) + else + for (n,nw) in data_eng["nw"] + nw["settings"] = Dict{String,Any}("sbase" => data_math["nw"][n]["baseMVA"]) + end + end end diff --git a/src/data_model/units.jl b/src/data_model/units.jl index a7cce115f..ec078d028 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -37,7 +37,9 @@ function make_per_unit!(data::Dict{String,<:Any}; vbases::Union{Dict{<:Any,<:Rea if ismultinetwork(data) for (n, nw) in data["nw"] + nw["data_model"] = data["data_model"] _make_math_per_unit!(nw, data; sbase=sbase, vbases=vbases) + delete!(nw, "data_model") end else _make_math_per_unit!(data, data; sbase=sbase, vbases=vbases) @@ -395,47 +397,94 @@ function solution_make_si(solution, math_model; mult_sbase=true, mult_vbase=true sbase = math_model["settings"]["sbase"] - for (comp_type, comp_dict) in [(x,y) for (x,y) in solution_si if isa(y, Dict)] - dimensionalize_math_comp = get(_dimensionalize_math, comp_type, Dict()) - vbase_props = mult_vbase ? get(dimensionalize_math_comp, "vbase", []) : [] - sbase_props = mult_sbase ? get(dimensionalize_math_comp, "sbase", []) : [] - ibase_props = mult_ibase ? get(dimensionalize_math_comp, "ibase", []) : [] - rad2deg_props = convert_rad2deg ? get(dimensionalize_math_comp, "rad2deg", []) : [] - - + if ismultinetwork(math_model) + for (n,nw) in solution_si["nw"] + for (comp_type, comp_dict) in [(x,y) for (x,y) in nw if isa(y, Dict)] + dimensionalize_math_comp = get(_dimensionalize_math, comp_type, Dict()) + vbase_props = mult_vbase ? get(dimensionalize_math_comp, "vbase", []) : [] + sbase_props = mult_sbase ? get(dimensionalize_math_comp, "sbase", []) : [] + ibase_props = mult_ibase ? get(dimensionalize_math_comp, "ibase", []) : [] + rad2deg_props = convert_rad2deg ? get(dimensionalize_math_comp, "rad2deg", []) : [] + + for (id, comp) in comp_dict + if !isempty(vbase_props) || !isempty(ibase_props) + vbase = math_model["nw"][n][comp_type][id]["vbase"] + ibase = sbase/vbase + end - for (id, comp) in comp_dict - if !isempty(vbase_props) || !isempty(ibase_props) - vbase = math_model[comp_type][id]["vbase"] - ibase = sbase/vbase - end + for (prop, val) in comp + if prop in vbase_props + comp[prop] = _apply_func_vals(comp[prop], x->x*vbase) + elseif prop in sbase_props + comp[prop] = _apply_func_vals(comp[prop], x->x*sbase) + elseif prop in ibase_props + comp[prop] = _apply_func_vals(comp[prop], x->x*ibase) + elseif prop in rad2deg_props + comp[prop] = _apply_func_vals(comp[prop], x->_wrap_to_180(rad2deg(x))) + end + end - for (prop, val) in comp - if prop in vbase_props - comp[prop] = _apply_func_vals(comp[prop], x->x*vbase) - elseif prop in sbase_props - comp[prop] = _apply_func_vals(comp[prop], x->x*sbase) - elseif prop in ibase_props - comp[prop] = _apply_func_vals(comp[prop], x->x*ibase) - elseif prop in rad2deg_props - comp[prop] = _apply_func_vals(comp[prop], x->_wrap_to_180(rad2deg(x))) + if comp_type=="transformer" + # transformers have different vbase/ibase on each side + f_bus = math_model["nw"][n]["transformer"][id]["f_bus"] + f_vbase = math_model["nw"][n]["bus"]["$f_bus"]["vbase"] + t_bus = math_model["nw"][n]["transformer"][id]["t_bus"] + t_vbase = math_model["nw"][n]["bus"]["$t_bus"]["vbase"] + f_ibase = sbase/f_vbase + t_ibase = sbase/t_vbase + + for (prop, val) in comp + if prop in dimensionalize_math_comp["ibase_fr"] + comp[prop] = _apply_func_vals(comp[prop], x->x*f_ibase) + elseif prop in dimensionalize_math_comp["ibase_to"] + comp[prop] = _apply_func_vals(comp[prop], x->x*t_ibase) + end + end + end end end - - if comp_type=="transformer" - # transformers have different vbase/ibase on each side - f_bus = math_model["transformer"][id]["f_bus"] - f_vbase = math_model["bus"]["$f_bus"]["vbase"] - t_bus = math_model["transformer"][id]["t_bus"] - t_vbase = math_model["bus"]["$t_bus"]["vbase"] - f_ibase = sbase/f_vbase - t_ibase = sbase/t_vbase + end + else + for (comp_type, comp_dict) in [(x,y) for (x,y) in solution_si if isa(y, Dict)] + dimensionalize_math_comp = get(_dimensionalize_math, comp_type, Dict()) + vbase_props = mult_vbase ? get(dimensionalize_math_comp, "vbase", []) : [] + sbase_props = mult_sbase ? get(dimensionalize_math_comp, "sbase", []) : [] + ibase_props = mult_ibase ? get(dimensionalize_math_comp, "ibase", []) : [] + rad2deg_props = convert_rad2deg ? get(dimensionalize_math_comp, "rad2deg", []) : [] + + for (id, comp) in comp_dict + if !isempty(vbase_props) || !isempty(ibase_props) + vbase = math_model[comp_type][id]["vbase"] + ibase = sbase/vbase + end for (prop, val) in comp - if prop in dimensionalize_math_comp["ibase_fr"] - comp[prop] = _apply_func_vals(comp[prop], x->x*f_ibase) - elseif prop in dimensionalize_math_comp["ibase_to"] - comp[prop] = _apply_func_vals(comp[prop], x->x*t_ibase) + if prop in vbase_props + comp[prop] = _apply_func_vals(comp[prop], x->x*vbase) + elseif prop in sbase_props + comp[prop] = _apply_func_vals(comp[prop], x->x*sbase) + elseif prop in ibase_props + comp[prop] = _apply_func_vals(comp[prop], x->x*ibase) + elseif prop in rad2deg_props + comp[prop] = _apply_func_vals(comp[prop], x->_wrap_to_180(rad2deg(x))) + end + end + + if comp_type=="transformer" + # transformers have different vbase/ibase on each side + f_bus = math_model["transformer"][id]["f_bus"] + f_vbase = math_model["bus"]["$f_bus"]["vbase"] + t_bus = math_model["transformer"][id]["t_bus"] + t_vbase = math_model["bus"]["$t_bus"]["vbase"] + f_ibase = sbase/f_vbase + t_ibase = sbase/t_vbase + + for (prop, val) in comp + if prop in dimensionalize_math_comp["ibase_fr"] + comp[prop] = _apply_func_vals(comp[prop], x->x*f_ibase) + elseif prop in dimensionalize_math_comp["ibase_to"] + comp[prop] = _apply_func_vals(comp[prop], x->x*t_ibase) + end end end end diff --git a/src/data_model/utils.jl b/src/data_model/utils.jl index ff7692783..1318d32e0 100644 --- a/src/data_model/utils.jl +++ b/src/data_model/utils.jl @@ -445,7 +445,7 @@ end "initialization actions for unmapping" -function _init_unmap_eng_obj!(data_eng::Dict{String,<:Any}, eng_obj_type::String, map::Dict{String,<:Any})::Dict{String,Any} +function _init_unmap_eng_obj!(data_eng::Dict{<:Any,<:Any}, eng_obj_type::String, map::Dict{String,<:Any})::Dict{String,Any} if !haskey(data_eng, eng_obj_type) data_eng[eng_obj_type] = Dict{Any,Any}() end diff --git a/src/form/apo.jl b/src/form/apo.jl index 372f9ac76..b23fa6b65 100644 --- a/src/form/apo.jl +++ b/src/form/apo.jl @@ -137,6 +137,17 @@ function variable_mc_transformer_power_real(pm::_PM.AbstractAPLossLessModels; nw end +"" +function constraint_mc_network_power_balance(pm::_PM.AbstractAPLossLessModels, n::Int, i, comp_gen_ids, comp_pd, comp_qd, comp_gs, comp_bs, comp_branch_g, comp_branch_b) + pg = var(pm, n, :pg) + + for c in conductor_ids(pm) + JuMP.@constraint(pm.model, sum(pg[g][c] for g in comp_gen_ids) == sum(pd[c] for (i,pd) in values(comp_pd)) + sum(gs[c]*1.0^2 for (i,gs) in values(comp_gs))) + # omit reactive constraint + end +end + + "Do nothing, this model is symmetric" function constraint_mc_ohms_yt_to(pm::_PM.AbstractAPLossLessModels, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm) end diff --git a/src/io/common.jl b/src/io/common.jl index 5ecd8587c..0c5f860e7 100644 --- a/src/io/common.jl +++ b/src/io/common.jl @@ -110,11 +110,13 @@ function correct_network_data!(data::Dict{String,Any}; make_pu::Bool=true) make_per_unit!(data) #TODO system-wide vbase does not make sense anymore... #take highest vbase just so it does not break anything for now - data["baseMVA"] = data["settings"]["sbase"]*data["settings"]["power_scale_factor"]/1E6 - if ismultinetwork(data) - data["basekv"] = maximum(maximum(bus["vbase"] for (_, bus) in nw["bus"]) for nw in values(data["nw"])) + for (n,nw) in data["nw"] + nw["basekv"] = maximum(maximum(bus["vbase"] for (_, bus) in nw["bus"]) for nw in values(data["nw"])) + nw["baseMVA"] = data["settings"]["sbase"]*data["settings"]["power_scale_factor"]/1E6 + end else ismultinetwork(data) + data["baseMVA"] = data["settings"]["sbase"]*data["settings"]["power_scale_factor"]/1E6 data["basekv"] = maximum(bus["vbase"] for (_, bus) in data["bus"]) _PM.check_connectivity(data) _PM.correct_transformer_parameters!(data) diff --git a/src/prob/common.jl b/src/prob/common.jl index 580ffceb3..a89501f70 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -1,14 +1,13 @@ "alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" -function run_mc_model(data::Dict{String,<:Any}, model_type::DataType, solver, build_mc::Function; ref_extensions::Vector{<:Function}=Vector{Function}([]), make_si=!get(data, "per_unit", false), kwargs...)::Dict{String,Any} - +function run_mc_model(data::Dict{String,<:Any}, model_type::Type, solver, build_mc::Function; ref_extensions::Vector{<:Function}=Vector{Function}([]), make_si=!get(data, "per_unit", false), multinetwork::Bool=false, kwargs...)::Dict{String,Any} if get(data, "data_model", MATHEMATICAL) == ENGINEERING - data_math = transform_data_model(data) + data_math = transform_data_model(data; build_multinetwork=multinetwork) - result = _PM.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_transformer!, ref_extensions...], multiconductor=true, kwargs...) + result = _PM.run_model(data_math, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_transformer!, ref_extensions...], multiconductor=true, multinetwork=multinetwork, kwargs...) result["solution"] = transform_solution(result["solution"], data_math; make_si=make_si) elseif get(data, "data_model", MATHEMATICAL) == MATHEMATICAL - result = _PM.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_transformer!, ref_extensions...], multiconductor=true, kwargs...) + result = _PM.run_model(data, model_type, solver, build_mc; ref_extensions=[ref_add_arcs_transformer!, ref_extensions...], multiconductor=true, multinetwork=multinetwork, kwargs...) end return result @@ -16,6 +15,6 @@ end "alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" -function run_mc_model(file::String, model_type::DataType, solver, build_mc::Function; ref_extensions::Vector{<:Function}=Vector{Function}([]), kwargs...)::Dict{String,Any} +function run_mc_model(file::String, model_type::Type, solver, build_mc::Function; ref_extensions::Vector{<:Function}=Vector{Function}([]), kwargs...)::Dict{String,Any} return run_mc_model(parse_file(file), model_type, solver, build_mc; ref_extensions=ref_extensions, kwargs...) end diff --git a/src/prob/debug.jl b/src/prob/debug.jl index 1b2806449..ca59db5d7 100644 --- a/src/prob/debug.jl +++ b/src/prob/debug.jl @@ -1,13 +1,13 @@ # These problem formulations are used to debug Distribution datasets # that do not converge using the standard formulations "OPF problem with slack power at every bus" -function run_mc_opf_pbs(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) +function run_mc_opf_pbs(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_opf_pbs; kwargs...) end "PF problem with slack power at every bus" -function run_mc_pf_pbs(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) +function run_mc_pf_pbs(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_pf_pbs; kwargs...) end diff --git a/src/prob/mld.jl b/src/prob/mld.jl index c87cdcbd5..564f85f42 100644 --- a/src/prob/mld.jl +++ b/src/prob/mld.jl @@ -1,17 +1,17 @@ "Run load shedding problem with storage" -function run_mc_mld(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) +function run_mc_mld(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_mld; kwargs...) end "Run Branch Flow Model Load Shedding Problem" -function run_mc_mld_bf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) +function run_mc_mld_bf(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_mld_bf; kwargs...) end "Run unit commitment load shedding problem (!relaxed)" -function run_mc_mld_uc(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) +function run_mc_mld_uc(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_mld_uc; kwargs...) end diff --git a/src/prob/opf.jl b/src/prob/opf.jl index a5f7b1d8e..af966b154 100644 --- a/src/prob/opf.jl +++ b/src/prob/opf.jl @@ -5,7 +5,7 @@ end "Optimal Power Flow" -function run_mc_opf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) +function run_mc_opf(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_opf; kwargs...) end diff --git a/src/prob/opf_oltc.jl b/src/prob/opf_oltc.jl index f4ffcf1e6..462da0070 100644 --- a/src/prob/opf_oltc.jl +++ b/src/prob/opf_oltc.jl @@ -5,7 +5,7 @@ end "on-load tap-changer OPF" -function run_mc_opf_oltc(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) +function run_mc_opf_oltc(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_opf_oltc; kwargs...) end diff --git a/src/prob/pf.jl b/src/prob/pf.jl index e5e9a870c..e9ba1f768 100644 --- a/src/prob/pf.jl +++ b/src/prob/pf.jl @@ -5,7 +5,7 @@ end "Power Flow Problem" -function run_mc_pf(data::Union{Dict{String,<:Any},String}, model_type::DataType, solver; kwargs...) +function run_mc_pf(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; kwargs...) return run_mc_model(data, model_type, solver, build_mc_pf; kwargs...) end diff --git a/src/prob/test.jl b/src/prob/test.jl index e69de29bb..a9773571d 100644 --- a/src/prob/test.jl +++ b/src/prob/test.jl @@ -0,0 +1,18 @@ +"test mc mn" +function _run_mc_mn_opb(data::Union{Dict{String,<:Any},String}, model_type::Type, solver; kwargs...) + return run_mc_model(data, model_type, solver, _build_mc_mn_opb; ref_extensions=[_PM.ref_add_connected_components!], multinetwork=true, kwargs...) +end + + +"Constructor for Optimal Power Flow" +function _build_mc_mn_opb(pm::_PM.AbstractPowerModel) + for (n, network) in nws(pm) + variable_mc_gen_power_setpoint(pm; nw=n) + + for i in ids(pm, :components, nw=n) + constraint_mc_network_power_balance(pm, i; nw=n) + end + end + + _PM.objective_min_fuel_cost(pm) +end diff --git a/test/data/opendss/case3_balanced.dss b/test/data/opendss/case3_balanced.dss index 4ee15474c..e920f9ac2 100755 --- a/test/data/opendss/case3_balanced.dss +++ b/test/data/opendss/case3_balanced.dss @@ -23,10 +23,11 @@ New Line.OHLine bus1=sourcebus.1.2.3 Primary.1.2.3 linecode = 556MCM length New Line.Quad Bus1=Primary.1.2.3 loadbus.1.2.3 linecode = 4/0QUAD length=1 ! 100 ft !Loads - single phase +New Loadshape.ls1 pmult=(file=load_profile.csv) -New Load.L1 phases=1 loadbus.1.0 ( 0.4 3 sqrt / ) kW=6 kvar=3 model=1 -New Load.L2 phases=1 loadbus.2.0 ( 0.4 3 sqrt / ) kW=6 kvar=3 model=1 -New Load.L3 phases=1 loadbus.3.0 ( 0.4 3 sqrt / ) kW=6 kvar=3 model=1 +New Load.L1 phases=1 loadbus.1.0 ( 0.4 3 sqrt / ) kW=6 kvar=3 model=1 daily=ls1 +New Load.L2 phases=1 loadbus.2.0 ( 0.4 3 sqrt / ) kW=6 kvar=3 model=1 daily=ls1 +New Load.L3 phases=1 loadbus.3.0 ( 0.4 3 sqrt / ) kW=6 kvar=3 model=1 daily=ls1 Set voltagebases=[0.4] From feec71694803982bef26dd9eb783d2734d2e9ff2 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 7 May 2020 11:01:45 -0600 Subject: [PATCH 213/224] ADD: example for using helper funcs for eng model creation --- examples/eng_model_helper_functions.ipynb | 240 ++++++++++++++++++++++ src/data_model/components.jl | 25 +-- src/data_model/math2eng.jl | 2 + 3 files changed, 251 insertions(+), 16 deletions(-) create mode 100644 examples/eng_model_helper_functions.ipynb diff --git a/examples/eng_model_helper_functions.ipynb b/examples/eng_model_helper_functions.ipynb new file mode 100644 index 000000000..da1263558 --- /dev/null +++ b/examples/eng_model_helper_functions.ipynb @@ -0,0 +1,240 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Building Engineering Data Models with Helper Functions\n", + "\n", + "In this notebook we will demonstrate an easy way to start building new distribution network models in the engineering format using new helper functions add in PowerModelsDistribution v0.9" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "┌ Info: Precompiling PowerModelsDistribution [d7431456-977f-11e9-2de3-97ff7677985e]\n", + "└ @ Base loading.jl:1260\n" + ] + } + ], + "source": [ + "using PowerModelsDistribution" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we need a optimizer. In this case we will use Ipopt and initialize it with JuMP's `optimizer_with_attributes`, which we have exported from PowerModelsDistribution by default for the user" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "MathOptInterface.OptimizerWithAttributes(Ipopt.Optimizer, Pair{MathOptInterface.AbstractOptimizerAttribute,Any}[MathOptInterface.RawParameter(\"print_level\") => 0, MathOptInterface.RawParameter(\"tol\") => 1.0e-6])" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import Ipopt\n", + "\n", + "ipopt_solver = optimizer_with_attributes(Ipopt.Optimizer, \"print_level\"=>0, \"tol\"=>1e-6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Initialize an empty `ENGINEERING` model" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 3 entries:\n", + " \"settings\" => Dict{String,Any}(\"sbase_default\"=>1.0,\"vbases_default\"=>Dict{…\n", + " \"per_unit\" => false\n", + " \"data_model\" => ENGINEERING" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng = Model()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this block we build a three bus network, with neutrals grounded at the source and loads.\n", + "\n", + "We start with buses, with the sourcebus and loadbus having 4 terminals, with the last terminal grounded.\n", + "\n", + "Then we add a generation source, in this case a voltage source, which is `WYE` configured by default, and therefore expects the last conductor to be a grounded neutral\n", + "\n", + "We add two three phase lines to connect the buses `sourcebus`, `primary`, and `loadbus`. Note that none of the lines have a neutral conductor.\n", + "\n", + "We finally add a three-phase load a the `loadbus` bus, but note again that this is a `WYE` configured load, and like the voltage source, this implies that the last conductor is a grounded neutral for the purposes of kron reduction (which is required by default until explicit 4-wire modeling is added to PowerModelsDistribution)\n", + " \n", + "Lastly, we need to define the default vbase of the system at the `sourcebus`" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 7 entries:\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"vo…\n", + " \"line\" => Dict{Any,Any}(\"primary\"=>Dict{String,Any}(\"xs\"=>[0.2 0.2 …\n", + " \"settings\" => Dict{String,Any}(\"sbase_default\"=>1.0,\"vbases_default\"=>D…\n", + " \"load\" => Dict{Any,Any}(\"balanced\"=>Dict{String,Any}(\"source_id\"=>\"…\n", + " \"bus\" => Dict{Any,Any}(\"primary\"=>Dict{String,Any}(\"source_id\"=>\"b…\n", + " \"per_unit\" => false\n", + " \"data_model\" => ENGINEERING" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "add_bus!(eng, \"sourcebus\"; terminals=[1,2,3,4], grounded=[4])\n", + "add_bus!(eng, \"primary\"; terminals=[1,2,3])\n", + "add_bus!(eng, \"loadbus\"; terminals=[1,2,3,4], grounded=[4])\n", + "\n", + "add_voltage_source!(eng, \"source\", \"sourcebus\", [1,2,3,4]; vm=[1, 1, 1])\n", + "\n", + "add_line!(eng, \"trunk\", \"sourcebus\", \"primary\", [1,2,3], [1,2,3])\n", + "add_line!(eng, \"primary\", \"primary\", \"loadbus\", [1,2,3], [1,2,3])\n", + "\n", + "add_load!(eng, \"balanced\", \"loadbus\", [1,2,3,4]; pd_nom=[5, 5, 5], qd_nom=[1, 1, 1])\n", + "\n", + "add_vbase_default!(eng, \"sourcebus\", 1)\n", + "\n", + "eng" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Running this case with OPF gives the results below" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.001, 0.0]\u001b[39m\n", + "\n", + "******************************************************************************\n", + "This program contains Ipopt, a library for large-scale nonlinear optimization.\n", + " Ipopt is released as open source code under the Eclipse Public License (EPL).\n", + " For more information visit http://projects.coin-or.org/Ipopt\n", + "******************************************************************************\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 8 entries:\n", + " \"solve_time\" => 4.04665\n", + " \"optimizer\" => \"Ipopt\"\n", + " \"termination_status\" => LOCALLY_SOLVED\n", + " \"dual_status\" => FEASIBLE_POINT\n", + " \"primal_status\" => FEASIBLE_POINT\n", + " \"objective\" => 0.0150047\n", + " \"solution\" => Dict{String,Any}(\"voltage_source\"=>Dict{Any,Any}(\"sou…\n", + " \"objective_lb\" => -Inf" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_mc_opf(eng, ACPPowerModel, ipopt_solver)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.4.1", + "language": "julia", + "name": "julia-1.4" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.4.1" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 4fa5901fa..cde98cc4c 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -168,22 +168,15 @@ function create_line(f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Ve kwargs... )::Dict{String,Any} - n_conductors = length(f_connections) + n_conductors = size(f_connections)[1] shape = (n_conductors, n_conductors) for v in [rs, xs, g_fr, b_fr, g_to, b_to, cm_ub, sm_ub, vad_lb, vad_ub] - if isa(v, Matrix) - @assert size(v) == shape - else - @assert length(v) == n_conductors - end - end - - # if no linecode, then populate loss parameters with zero - if !haskey(kwargs, :linecode) - for key in [:rs, :xs, :g_fr, :g_to, :b_fr, :b_to] - if haskey(kwargs, key) - n_conductors = size(kwargs[key])[1] + if !ismissing(v) + if isa(v, Matrix) + @assert size(v) == shape + else + @assert size(v)[1] == n_conductors end end end @@ -199,7 +192,7 @@ function create_line(f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Ve "length" => length, ) - if !ismissing(linecode) + if ismissing(linecode) line["rs"] = !ismissing(rs) ? rs : fill(0.01, shape...) line["rs"] => !ismissing(rs) ? rs : fill(0.01, shape...) line["xs"] = !ismissing(xs) ? xs : fill(0.2, shape...) @@ -216,7 +209,7 @@ function create_line(f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Ve end end - for (k,v) in [("cm_ub", "sm_ub")] + for (k,v) in [("cm_ub", cm_ub), ("sm_ub", sm_ub)] if !ismissing(v) line[k] = v end @@ -301,7 +294,7 @@ function create_load(bus::Any, connections::Union{Vector{Int},Vector{String}}; kwargs... )::Dict{String,Any} - n_conductors = length(connections) + n_conductors = configuration == WYE ? length(connections)-1 : length(connections) for v in [pd_nom, qd_nom] if !ismissing(v) diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index d517d1fff..fcc59d8cb 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -54,6 +54,8 @@ function _map_math2eng_voltage_source!(data_eng::Dict{<:Any,<:Any}, data_math::D eng_obj = _init_unmap_eng_obj!(data_eng, "voltage_source", map) + map["to"] = isa(map["to"], Vector) ? map["to"] : [map["to"]] + for to_id in map["to"] math_obj = _get_math_obj(data_math, to_id) if startswith(to_id, "gen") From 132a857588e9847a1e165467200a4919ec7c9e7e Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 7 May 2020 11:55:14 -0600 Subject: [PATCH 214/224] ADD: more helper funcs to example --- examples/eng_model_helper_functions.ipynb | 94 ++++++++++++++++++++++- src/data_model/components.jl | 8 +- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/examples/eng_model_helper_functions.ipynb b/examples/eng_model_helper_functions.ipynb index da1263558..89c96110d 100644 --- a/examples/eng_model_helper_functions.ipynb +++ b/examples/eng_model_helper_functions.ipynb @@ -51,6 +51,7 @@ } ], "source": [ + "using LinearAlgebra: diagm\n", "import Ipopt\n", "\n", "ipopt_solver = optimizer_with_attributes(Ipopt.Optimizer, \"print_level\"=>0, \"tol\"=>1e-6)" @@ -173,7 +174,7 @@ "data": { "text/plain": [ "Dict{String,Any} with 8 entries:\n", - " \"solve_time\" => 4.04665\n", + " \"solve_time\" => 3.70515\n", " \"optimizer\" => \"Ipopt\"\n", " \"termination_status\" => LOCALLY_SOLVED\n", " \"dual_status\" => FEASIBLE_POINT\n", @@ -191,6 +192,97 @@ "source": [ "result = run_mc_opf(eng, ACPPowerModel, ipopt_solver)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the following example, we provide examples of a wider range of component types that can be added using helper functions to give a flavor of what is possible in PowerModelsDistribution v0.9" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{String,Any} with 11 entries:\n", + " \"bus\" => Dict{Any,Any}(\"primary\"=>Dict{String,Any}(\"source_id\"=>\"b…\n", + " \"settings\" => Dict{String,Any}(\"sbase_default\"=>1.0,\"vbases_default\"=>D…\n", + " \"switch\" => Dict{Any,Any}(\"breaker\"=>Dict{String,Any}(\"source_id\"=>\"s…\n", + " \"generator\" => Dict{Any,Any}(\"secondary\"=>Dict{String,Any}(\"source_id\"=>…\n", + " \"voltage_source\" => Dict{Any,Any}(\"source\"=>Dict{String,Any}(\"source_id\"=>\"vo…\n", + " \"line\" => Dict{Any,Any}(\"primary\"=>Dict{String,Any}(\"xs\"=>[0.2 0.2 …\n", + " \"per_unit\" => false\n", + " \"data_model\" => ENGINEERING\n", + " \"transformer\" => Dict{Any,Any}(\"tx1\"=>Dict{String,Any}(\"source_id\"=>\"trans…\n", + " \"shunt\" => Dict{Any,Any}(\"cap\"=>Dict{String,Any}(\"source_id\"=>\"shunt…\n", + " \"load\" => Dict{Any,Any}(\"tload\"=>Dict{String,Any}(\"source_id\"=>\"loa…" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "eng2 = deepcopy(eng)\n", + "\n", + "add_bus!(eng2, \"ttbus\"; terminals=[1,2,3,4], grounded=[4])\n", + "\n", + "add_transformer!(eng2, \"tx1\", \"sourcebus\", \"ttbus\", [1,2,3,4], [1,2,3,4])\n", + "\n", + "add_bus!(eng2, \"loadbus2\"; terminals=[1,2,3,4], grounded=[4])\n", + "\n", + "add_switch!(eng2, \"breaker\", \"ttbus\", \"loadbus2\", [1,2,3], [1,2,3])\n", + "\n", + "add_load!(eng2, \"tload\", \"loadbus2\", [1,2,3,4]; pd_nom=[5, 5, 5], qd_nom=[1, 1, 1])\n", + "\n", + "add_generator!(eng2, \"secondary\", \"loadbus2\", [1,2,3,4]; cost_pg_parameters=[0.0, 1.2, 0])\n", + "\n", + "add_shunt!(eng2, \"cap\", \"loadbus2\", [1,2,3,4]; bs=diagm(0=>fill(1, 3)))\n", + "\n", + "eng2" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[35m[warn | PowerModels]: active generators found at bus 4, updating to bus type from 1 to 2\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.0012, 0.0]\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 2 cost function with order 3 to a function of order 2: [0.001, 0.0]\u001b[39m\n" + ] + }, + { + "data": { + "text/plain": [ + "Dict{String,Any} with 8 entries:\n", + " \"solve_time\" => 0.087898\n", + " \"optimizer\" => \"Ipopt\"\n", + " \"termination_status\" => LOCALLY_SOLVED\n", + " \"dual_status\" => FEASIBLE_POINT\n", + " \"primal_status\" => FEASIBLE_POINT\n", + " \"objective\" => -83.3003\n", + " \"solution\" => Dict{String,Any}(\"voltage_source\"=>Dict{Any,Any}(\"sou…\n", + " \"objective_lb\" => -Inf" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result2 = run_mc_opf(eng2, ACPPowerModel, ipopt_solver)" + ] } ], "metadata": { diff --git a/src/data_model/components.jl b/src/data_model/components.jl index cde98cc4c..de15debf9 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -495,8 +495,8 @@ end "creates a generic shunt with some defaults" function create_shunt(bus, connections; - gs::Union{Vector{<:Real},Missing}=missing, - bs::Union{Vector{<:Real},Missing}=missing, + gs::Union{Matrix{<:Real},Missing}=missing, + bs::Union{Matrix{<:Real},Missing}=missing, model::ShuntModel=GENERIC, dispatchable::Dispatchable=NO, status::Status=ENABLED, @@ -508,8 +508,8 @@ function create_shunt(bus, connections; shunt = Dict{String,Any}( "bus" => bus, "connections" => connections, - "gs" => !ismissing(gs) ? gs : fill(0.0, n_conductors), - "bs" => !ismissing(bs) ? bs : fill(0.0, n_conductors), + "gs" => !ismissing(gs) ? gs : fill(0.0, n_conductors, n_conductors), + "bs" => !ismissing(bs) ? bs : fill(0.0, n_conductors, n_conductors), "model" => model, "dispatchable" => dispatchable, "status" => status, From f0937a627504a8b0046c3e190874999e7f30345c Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 7 May 2020 12:00:30 -0600 Subject: [PATCH 215/224] ADD: unit tests for new features --- test/data_model.jl | 65 ++++++++++++++++++++++++++++++++++++++++++++ test/multinetwork.jl | 27 ++++-------------- test/runtests.jl | 2 ++ 3 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 test/data_model.jl diff --git a/test/data_model.jl b/test/data_model.jl new file mode 100644 index 000000000..667362b06 --- /dev/null +++ b/test/data_model.jl @@ -0,0 +1,65 @@ +@info "running data model creation and conversion tests" + +@testset "engineering data model" begin + @testset "helper functions for building engineering data model" begin + eng = Model() + + add_bus!(eng, "sourcebus"; terminals=[1,2,3,4], grounded=[4]) + add_bus!(eng, "primary"; terminals=[1,2,3]) + add_bus!(eng, "loadbus"; terminals=[1,2,3,4], grounded=[4]) + + add_voltage_source!(eng, "source", "sourcebus", [1,2,3,4]; vm=[1, 1, 1]) + + add_line!(eng, "trunk", "sourcebus", "primary", [1,2,3], [1,2,3]) + add_line!(eng, "primary", "primary", "loadbus", [1,2,3], [1,2,3]) + + add_load!(eng, "balanced", "loadbus", [1,2,3,4]; pd_nom=[5, 5, 5], qd_nom=[1, 1, 1]) + + add_vbase_default!(eng, "sourcebus", 1) + + result = run_mc_opf(eng, ACPPowerModel, ipopt_solver) + + @test result["termination_status"] == LOCALLY_SOLVED + @test isapprox(result["objective"], 0.0150; atol=1e-4) + + eng2 = deepcopy(eng) + + add_bus!(eng2, "ttbus"; terminals=[1,2,3,4], grounded=[4]) + + add_transformer!(eng2, "tx1", "sourcebus", "ttbus", [1,2,3,4], [1,2,3,4]) + + add_bus!(eng2, "loadbus2"; terminals=[1,2,3,4], grounded=[4]) + + add_switch!(eng2, "breaker", "ttbus", "loadbus2", [1,2,3], [1,2,3]) + + add_load!(eng2, "tload", "loadbus2", [1,2,3,4]; pd_nom=[5, 5, 5], qd_nom=[1, 1, 1]) + + add_generator!(eng2, "secondary", "loadbus2", [1,2,3,4]; cost_pg_parameters=[0.0, 1.2, 0]) + + add_shunt!(eng2, "cap", "loadbus2", [1,2,3,4]; bs=diagm(0=>fill(1, 3))) + + result2 = run_mc_opf(eng, ACPPowerModel, ipopt_solver) + + @test result["termination_status"] == LOCALLY_SOLVED + @test isapprox(result["objective"], -83.3003; atol=1e-4) + end + + @testset "engineering model transformations" begin + eng = parse_file("../test/data/opendss/case3_balanced.dss"; transfomations=[(apply_voltage_bounds!, "vm_ub"=>Inf)]) + + @test all(all(isapprox.(bus["vm_lb"], 0.4 / sqrt(3) * 0.9)) && all(isinf.(bus["vm_ub"])) for (id,bus) in eng["bus"] if id != "sourcebus") + + math = transform_data_model(eng) + + @test all(all(isapprox.(bus["vmin"], 0.9)) for (_,bus) in math["bus"] if bus["name"] != "sourcebus" && !startswith(bus["name"], "_virtual")) + + eng = parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; transformations=[make_lossless!, (apply_voltage_bounds!, "vm_lb"=>0.95, "vm_ub"=>1.05)]) + + @test all(all(eng["transformer"]["tx1"][k] .== 0) for k in ["rw", "xsc", "noloadloss", "imag"]) + + math = transform_data_model(eng) + + @test length(math["bus"]) == 5 + @test all(all(isapprox.(bus["vmin"], 0.95)) && all(isapprox.(bus["vmax"], 1.05)) for (_,bus) in math["bus"] if bus["name"] != "sourcebus" && !startswith(bus["name"], "_virtual")) + end +end diff --git a/test/multinetwork.jl b/test/multinetwork.jl index 5c19a70e2..5bdef23a5 100644 --- a/test/multinetwork.jl +++ b/test/multinetwork.jl @@ -1,27 +1,10 @@ @info "running multinetwork tests" @testset "test multinetwork" begin - # TODO: Add new Multinetwork tests after support for LoadShapes -# @testset "5-bus storage multinetwork acp opf_strg" begin -# mp_data = PowerModels.parse_file("../test/data/matpower/case5_strg.m") -# PMD.make_multiconductor!(mp_data, 3) -# mn_mp_data = PowerModels.replicate(mp_data, 5) + @testset "3-bus balanced multinetwork nfa opb" begin + eng_ts = parse_file("../test/data/opendss/case3_balanced.dss"; time_series="daily") + result_mn = PowerModelsDistribution._run_mc_mn_opb(eng_ts, NFAPowerModel, ipopt_solver) -# result = PMD._run_mn_mc_opf_strg(mn_mp_data, PowerModels.ACPPowerModel, ipopt_solver) - -# @test result["termination_status"] == PM.LOCALLY_SOLVED -# @test isapprox(result["objective"], 2.64596e5; atol = 1e2) - -# for (n, network) in result["solution"]["nw"], c in 1:network["conductors"] -# # @test isapprox(network["storage"]["1"]["ps"][c], -0.012; atol = 1e-3) -# # @test isapprox(network["storage"]["1"]["qs"][c], -0.012; atol = 1e-3) -# @test isapprox(network["storage"]["1"]["ps"][c], -0.012; atol = 1e-1) -# # @test isapprox(network["storage"]["1"]["qs"][c], -0.012; atol = 1e-1) - -# # @test isapprox(network["storage"]["2"]["ps"][c], -0.016; atol = 1e-3) -# # @test isapprox(network["storage"]["2"]["qs"][c], 0.000; atol = 1e-3) -# @test isapprox(network["storage"]["2"]["ps"][c], -0.016; atol = 1e-1) -# @test isapprox(network["storage"]["2"]["qs"][c], 0.000; atol = 1e-1) -# end -# end + @test result_mn["termination_status"] == LOCALLY_SOLVED + end end diff --git a/test/runtests.jl b/test/runtests.jl index 043c98896..db20e0fc3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -64,4 +64,6 @@ include("common.jl") include("shunt.jl") include("mld.jl") + + include("data_model.jl") end From ea97e10290248885d8d227d5af99a2d910cbac58 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 7 May 2020 13:13:58 -0600 Subject: [PATCH 216/224] FIX: bug in data_model tests --- test/data_model.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/data_model.jl b/test/data_model.jl index 667362b06..801b3328c 100644 --- a/test/data_model.jl +++ b/test/data_model.jl @@ -38,14 +38,14 @@ add_shunt!(eng2, "cap", "loadbus2", [1,2,3,4]; bs=diagm(0=>fill(1, 3))) - result2 = run_mc_opf(eng, ACPPowerModel, ipopt_solver) + result2 = run_mc_opf(eng2, ACPPowerModel, ipopt_solver) - @test result["termination_status"] == LOCALLY_SOLVED - @test isapprox(result["objective"], -83.3003; atol=1e-4) + @test result2["termination_status"] == LOCALLY_SOLVED + @test isapprox(result2["objective"], -83.3003; atol=1e-4) end @testset "engineering model transformations" begin - eng = parse_file("../test/data/opendss/case3_balanced.dss"; transfomations=[(apply_voltage_bounds!, "vm_ub"=>Inf)]) + eng = parse_file("../test/data/opendss/case3_balanced.dss"; transformations=[(apply_voltage_bounds!, "vm_ub"=>Inf)]) @test all(all(isapprox.(bus["vm_lb"], 0.4 / sqrt(3) * 0.9)) && all(isinf.(bus["vm_ub"])) for (id,bus) in eng["bus"] if id != "sourcebus") From 9c9659b7f1c1f5412882f1623fd14dcef07f3275 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Thu, 7 May 2020 14:04:48 -0600 Subject: [PATCH 217/224] ADD: missing si -> pu conversions storage and switch were missing --- CHANGELOG.md | 1 - src/data_model/units.jl | 63 ++++++++++++++++++++++++++++------------- test/data_model.jl | 2 +- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dc9d40e3..bada2af61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ - Add support for PowerModels v0.17 (breaking) - Add support for InfrastructureModels v0.5 - Updates JSON parser to handle enum (`"data_model"` values) -- Adds Dierckx dependency for creation of 1d splines for xycurve object - Adds some commonly used InfrastructureModels and PowerModels functions as exports - Adds model building functions `add_{component}!` to aid in building simple models for testing (experimental) - Add run_mc_model (adds `ref_add_arcs_transformer!` to ref_extensions, and sets `multiconductor=true` by default) (breaking) diff --git a/src/data_model/units.jl b/src/data_model/units.jl index ec078d028..f29d17ff1 100644 --- a/src/data_model/units.jl +++ b/src/data_model/units.jl @@ -24,7 +24,9 @@ const _dimensionalize_math = Dict{String,Dict{String,Vector{String}}}( "converts data model between per-unit and SI units" -function make_per_unit!(data::Dict{String,<:Any}; vbases::Union{Dict{<:Any,<:Real},Missing}=missing, sbase::Union{Real,Missing}=missing, data_model_type::DataModel=get(data, "data_model", MATHEMATICAL)) +function make_per_unit!(data::Dict{String,<:Any}; vbases::Union{Dict{<:Any,<:Real},Missing}=missing, sbase::Union{Real,Missing}=missing) + data_model_type = get(data, "data_model", MATHEMATICAL) + if data_model_type == MATHEMATICAL if !get(data, "per_unit", false) if ismissing(vbases) @@ -160,7 +162,7 @@ end "converts to per unit from SI" -function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math::Dict{String,<:Any}; sbase::Union{Real,Missing}=missing, vbases::Union{Dict{String,<:Real},Missing}=missing) +function _make_math_per_unit!(nw::Dict{String,<:Any}, data_math::Dict{String,<:Any}; sbase::Union{Real,Missing}=missing, vbases::Union{Dict{String,<:Real},Missing}=missing) if ismissing(sbase) if haskey(data_math["settings"], "sbase_default") sbase = data_math["settings"]["sbase_default"] @@ -180,7 +182,7 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math::Dict{St if haskey(data_math["settings"], "vbases_default") vbases = Dict{String,Real}("$(data_math["bus_lookup"][id])" => vbase for (id, vbase) in data_math["settings"]["vbases_default"]) else - buses_type_3 = [(id, sum(bus["vm"])/length(bus["vm"])) for (id,bus) in data_model["bus"] if haskey(bus, "bus_type") && bus["bus_type"]==3] + buses_type_3 = [(id, sum(bus["vm"])/length(bus["vm"])) for (id,bus) in nw["bus"] if haskey(bus, "bus_type") && bus["bus_type"]==3] if !isempty(buses_type_3) vbases = Dict([buses_type_3[1]]) else @@ -189,40 +191,41 @@ function _make_math_per_unit!(data_model::Dict{String,<:Any}, data_math::Dict{St end end - bus_vbase, line_vbase = calc_voltage_bases(data_model, vbases) + bus_vbase, line_vbase = calc_voltage_bases(nw, vbases) voltage_scale_factor = data_math["settings"]["voltage_scale_factor"] - for (id, bus) in data_model["bus"] + for (id, bus) in nw["bus"] _rebase_pu_bus!(bus, bus_vbase[id], sbase, sbase_old, voltage_scale_factor) end - for (id, line) in data_model["branch"] + for (id, line) in nw["branch"] vbase = line_vbase["branch.$id"] _rebase_pu_branch!(line, vbase, sbase, sbase_old, voltage_scale_factor) end - for (id, shunt) in data_model["shunt"] + for (id, shunt) in nw["shunt"] _rebase_pu_shunt!(shunt, bus_vbase[string(shunt["shunt_bus"])], sbase, sbase_old, voltage_scale_factor) end - for (id, load) in data_model["load"] + for (id, load) in nw["load"] _rebase_pu_load!(load, bus_vbase[string(load["load_bus"])], sbase, sbase_old, voltage_scale_factor) end - for (id, gen) in data_model["gen"] + for (id, gen) in nw["gen"] _rebase_pu_generator!(gen, bus_vbase[string(gen["gen_bus"])], sbase, sbase_old, data_math) end - for (id, storage) in data_model["storage"] - # TODO + for (id, storage) in nw["storage"] + _rebase_pu_storage!(storage, bus_vbase[string(storage["storage_bus"])], sbase, sbase_old) end - for (id, switch) in data_model["switch"] - # TODO + for (id, switch) in nw["switch"] + vbase = line_vbase["switch.$id"] + _rebase_pu_switch!(switch, vbase, sbase, sbase_old) end - if haskey(data_model, "transformer") # transformers are not required by PowerModels - for (id, trans) in data_model["transformer"] + if haskey(nw, "transformer") + for (id, trans) in nw["transformer"] # voltage base across transformer does not have to be consistent with the ratio! f_vbase = bus_vbase[string(trans["f_bus"])] t_vbase = bus_vbase[string(trans["t_bus"])] @@ -295,6 +298,14 @@ function _rebase_pu_branch!(branch::Dict{String,<:Any}, vbase::Real, sbase::Real end +"per-unit conversion for switches" +function _rebase_pu_switch!(switch::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, voltage_scale_factor::Real) + sbase_scale = sbase / sbase_old + + _scale_props!(switch, ["psw", "qsw", "thermal_rating", "current_rating"], sbase_scale) +end + + "per-unit conversion for shunts" function _rebase_pu_shunt!(shunt::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, voltage_scale_factor::Real) if !haskey(shunt, "vbase") @@ -339,8 +350,8 @@ end "per-unit conversion for generators" -function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, data_model::Dict{String,<:Any}) - vbase_old = get(gen, "vbase", 1.0/data_model["settings"]["voltage_scale_factor"]) +function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real, data_math::Dict{String,<:Any}) + vbase_old = get(gen, "vbase", 1.0/data_math["settings"]["voltage_scale_factor"]) vbase_scale = vbase_old/vbase sbase_scale = sbase_old/sbase @@ -348,9 +359,13 @@ function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real _scale(gen, key, sbase_scale) end + for key in ["vg"] + _scale(gen, key, vbase_scale) + end + # if not in per unit yet, the cost has is in $/MWh - if !haskey(data_model["settings"], "sbase") - sbase_old_cost = 1E6/data_model["settings"]["power_scale_factor"] + if !haskey(data_math["settings"], "sbase") + sbase_old_cost = 1E6/data_math["settings"]["power_scale_factor"] sbase_scale_cost = sbase_old_cost/sbase else sbase_scale_cost = sbase_scale @@ -363,6 +378,16 @@ function _rebase_pu_generator!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real end +"per-unit conversion for storage" +function _rebase_pu_storage!(gen::Dict{String,<:Any}, vbase::Real, sbase::Real, sbase_old::Real) + sbase_scale = sbase_old/sbase + + for key in ["energy", "energy_rating", "charge_rating", "discharge_rating", "thermal_rating", "current_rating", "qmin", "qmax", "p_loss", "q_loss"] + _scale(gen, key, sbase_scale) + end +end + + "per-unit conversion for ideal 2-winding transformers" function _rebase_pu_transformer_2w_ideal!(transformer::Dict{String,<:Any}, f_vbase_new::Real, t_vbase_new::Real, sbase_old::Real, sbase_new::Real, voltage_scale_factor::Real) f_vbase_old = get(transformer, "f_vbase", 1.0) diff --git a/test/data_model.jl b/test/data_model.jl index 801b3328c..3b03f7f08 100644 --- a/test/data_model.jl +++ b/test/data_model.jl @@ -41,7 +41,7 @@ result2 = run_mc_opf(eng2, ACPPowerModel, ipopt_solver) @test result2["termination_status"] == LOCALLY_SOLVED - @test isapprox(result2["objective"], -83.3003; atol=1e-4) + @test isapprox(result2["objective"], -83.3003; atol=0.2) end @testset "engineering model transformations" begin From 4363f45b8876ec238ca39ec057f794d874f696da Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 11 May 2020 08:21:19 -0600 Subject: [PATCH 218/224] FIX: Address review comments FIX: spelling errors REF: linecode requires rs/xs, lines require either linecode or rs/xs REF: DROOP to FREQUENCYDROOP ADD: vm bounds to _1to1_maps constant ADD: convert_rad2deg kwarg to transform_solution --- docs/src/eng-data-model.md | 16 +++++++------- docs/src/eng2math.md | 2 +- docs/src/engineering_model.md | 16 +++++++------- docs/src/enums.md | 2 +- examples/engineering_model.ipynb | 4 ++-- src/core/types.jl | 2 +- src/data_model/components.jl | 36 +++++++++++++------------------- src/data_model/eng2math.jl | 4 ++-- src/data_model/math2eng.jl | 4 ++-- src/io/opendss.jl | 2 +- test/data_model.jl | 6 ++++-- test/delta_gens.jl | 2 +- test/pf.jl | 2 +- 13 files changed, 45 insertions(+), 53 deletions(-) diff --git a/docs/src/eng-data-model.md b/docs/src/eng-data-model.md index d70999606..157346349 100644 --- a/docs/src/eng-data-model.md +++ b/docs/src/eng-data-model.md @@ -26,11 +26,11 @@ Dict{String,Any}( Valid component types are those that are documented in the sectios below. Each component object is identified by an `id`, which can be any immutable value (`id <: Any`), but `id` does not appear inside of the component dictionary, and only appears as keys to the component dictionaries under each component type. Note that by default, if using one of the parsers, component `id` will be of type `String`, and if a model is created that uses `id` which is not type `String`, it will not be JSON serializable (_i.e._ the `id` will be converted to its `String` representation on export to JSON). -Each edge or node component (_i.e._ all those that are not data objects or buses), are expected to have `status` fields to specify whether the component is active or disabled, `bus` or `f_bus` and `t_bus`, to specify the buses that are connected to the component, and `connections` or `f_connections` and `t_connections`, to specify the terminals of the buses that are actively connected in an ordered list. Terminals/connections can be any immutable value, as can bus ids. __NOTE__: `terminals`, `connections`, `f_connections`, and `t_connections`, can either be type `Vector{Int}` _or_ `Vector{String}`, as long as they are consistant across the whole model. +Each edge or node component (_i.e._ all those that are not data objects or buses), is expected to have `status` fields to specify whether the component is active or disabled, `bus` or `f_bus` and `t_bus`, to specify the buses that are connected to the component, and `connections` or `f_connections` and `t_connections`, to specify the terminals of the buses that are actively connected in an ordered list. Terminals/connections can be any immutable value, as can bus ids. __NOTE__: `terminals`, `connections`, `f_connections`, and `t_connections`, can either be type `Vector{Int}` _or_ `Vector{String}`, as long as they are consistent across the whole model. Parameter values on components are expected to be specified in SI units by default (where applicable) in the engineering data model. Relevant expected units are noted in the sections below. It is possible for the user to select universal scalar factors for power and voltages. For example, if `power_scalar_factor` and `voltage_scalar_factor` are their default values given below, where units is listed as watt or var, real units will be kW and kvar. Where units are listed as volt, real units will be kV (multiplied by `vm_nom`, where that value exists). -The Used column describes the situtations where certain parameters are used. "always" indicates those values are used in all contexts, `opf`, `mld`, or any other problem name abbreviation indicate they are used in particular for those problems. "solution" indicates that those parameters are outputs from the solvers. "multinetwork" indictes these values are only used to build multinetwork problems. +The Used column describes the situtations where certain parameters are used. "always" indicates those values are used in all contexts, `opf`, `mld`, or any other problem name abbreviation indicate they are used in particular for those problems. "solution" indicates that those parameters are outputs from the solvers. "multinetwork" indicates these values are only used to build multinetwork problems. Those parameters that have a default may be omitted by the user from the data model, they will be populated by the specified default values. @@ -185,9 +185,9 @@ These are n-winding (`nwinding`), n-phase (`nphase`), lossy transformers. Note t | `sm_nom` | | `Vector{Real}` | watt | always | | | `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | -#### Assymetric, Lossless, Two-Winding (AL2W) Transformers (`transformer`) +#### Asymmetric, Lossless, Two-Winding (AL2W) Transformers (`transformer`) -Special case of the Generic transformer, which is still a `transformer` object, but has a simplified method for its definition. These are transformers are assymetric (A), lossless (L) and two-winding (2W). Assymetric refers to the fact that the secondary is always has a `WYE` configuration, whilst the primary can be `DELTA`. The table below indicates alternate, more simple ways to specify the special case of an AL2W Transformer. `xsc` and `rw` cannot be specified for an AL2W transformer, because it is lossless. To use this definition format, all of `f_bus`, `t_bus`, `f_connections`, `t_connections`, and `configuration` must be used, and none of `buses`, `connections`, `configurations` may be used. `xfmrcode` is ignored for this component. +Special case of the Generic transformer, which is still a `transformer` object, but has a simplified method for its definition. These are transformers are asymmetric (A), lossless (L) and two-winding (2W). Asymmetric refers to the fact that the secondary is always has a `WYE` configuration, whilst the primary can be `DELTA`. The table below indicates alternate, more simple ways to specify the special case of an AL2W Transformer. `xsc` and `rw` cannot be specified for an AL2W transformer, because it is lossless. To use this definition format, all of `f_bus`, `t_bus`, `f_connections`, `t_connections`, and `configuration` must be used, and none of `buses`, `connections`, `configurations` may be used. `xfmrcode` is ignored for this component. | Name | Default | Type | Units | Used | Description | | --------------- | -------------------- | ----------------------------- | ----- | ------ | ----------------------------------------------------------------------------------------------------------- | @@ -298,7 +298,7 @@ Two more model types are supported, which need additional fields and are defined | `qd_ci` | | `Real` | | `model==ZIP` | | | `qd_cp` | | `Real` | | `model==ZIP` | | -### Generators `generator` (or Synchronous Machines `synchronous_machine`?) +### Generators (`generator`) | Name | Default | Type | Units | Used | Description | | --------------- | -------------------- | ----------------------------- | ----- | --------------------------- | ------------------------------------------------------------------------------------ | @@ -312,7 +312,7 @@ Two more model types are supported, which need additional fields and are defined | `qg_ub` | `pg_ub` | `Vector{Real}` | var | opf | Upper bound on reactive power generation per phase, `size=nphases` | | `pg` | | `Vector{Real}` | watt | solution | Present active power generation per phase, `size=nphases` | | `qg` | | `Vector{Real}` | var | solution | Present reactive power generation per phase, `size=nphases` | -| `control_mode` | `DROOP` | `ControlMode` | | | `DROOP` or `ISOCHRONOUS` | +| `control_mode` | `FREQUENCYDROOP` | `ControlMode` | | | `FREQUENCYDROOP` or `ISOCHRONOUS` | | `status` | `ENABLED` | `Status` | | always | `ENABLED` or `DISABLED`. Indicates if component is enabled or disabled, respectively | | `time_series` | | `Dict{String,Any}` | | multinetwork | Dictionary containing time series parameters. | @@ -327,8 +327,6 @@ The generator cost model is currently specified by the following fields. ### Photovoltaic Systems (`solar`) -TODO Loss model, Inverter settings, Irradiance Model - | Name | Default | Type | Units | Used | Description | | --------------- | --------- | ----------------------------- | ----- | ------------ | ------------------------------------------------------------------------------------ | | `bus` | | `Any` | | always | id of bus connection | @@ -354,7 +352,7 @@ The cost model for a photovoltaic system currently matches that of generators. ### Wind Turbine Systems (`wind`) -Wind turbine systems are most closely approximated by induction machines, also known as asynchornous machines. These are not currently supported, but there is plans to support them in the future. +Wind turbine systems are most closely approximated by induction machines, also known as asynchronous machines. These are not currently supported, but there is plans to support them in the future. ### Storage (`storage`) diff --git a/docs/src/eng2math.md b/docs/src/eng2math.md index 4a531cbb0..05cd99e86 100644 --- a/docs/src/eng2math.md +++ b/docs/src/eng2math.md @@ -16,7 +16,7 @@ Switches are parsed into `switch`. If there are loss parameters provided (_i.e._ ## `transformer` -Transformers are parsed into ayssmetric lossless 2-winding transformers. When parsing n-winding transformers with n>2 additionally virtual branches and buses are created to connect the new 2-winding transformers. Furthermore, if the loss parameters are non-zero, additional virtual buses and branches to model the transformer impedances +Transformers are parsed into asymmetric lossless 2-winding transformers. When parsing n-winding transformers with n>2 additionally virtual branches and buses are created to connect the new 2-winding transformers. Furthermore, if the loss parameters are non-zero, additional virtual buses and branches to model the transformer impedances ## `shunt` diff --git a/docs/src/engineering_model.md b/docs/src/engineering_model.md index 3f9c76947..83be799e9 100644 --- a/docs/src/engineering_model.md +++ b/docs/src/engineering_model.md @@ -1,7 +1,7 @@ # Introduction to the PowerModelsDistribution Data Models -In this notebook we introduce the engineering data model added to PowerModelsDistribution in version v0.9.0. We will give serveral examples of how to use this new data model directly, including new transformations that have become easier with it's introduction, how to convert it to the the lower-level mathematical model that was previously the only user interface we offered, and how to get various types of results using this new model. +In this notebook we introduce the engineering data model added to PowerModelsDistribution in version v0.9.0. We will give several examples of how to use this new data model directly, including new transformations that have become easier with its introduction, how to convert it to the the lower-level mathematical model that was previously the only user interface we offered, and how to get various types of results using this new model. ## Imports @@ -110,11 +110,11 @@ eng["bus"] -We can see there are three buses in this system, identified by ids `"primary"`, `"sourcebus"`, and `"loadbus"`. +We can see there are three buses in this system, identified by ids `"primary"`, `"sourcebus"`, and `"loadbus"`. -__NOTE__: In Julia, order of Dictionary keys is not fixed, nor does it retain the order in which it was parsed like _e.g._ `Vectors`. +__NOTE__: In Julia, order of Dictionary keys is not fixed, nor does it retain the order in which it was parsed like _e.g._ `Vectors`. -Identifying components by non-integer names is a new feature of the `ENGINEERING` model, and makes network debugging more straightforward. +Identifying components by non-integer names is a new feature of the `ENGINEERING` model, and makes network debugging more straightforward. __NOTE__: all names are converted to lowercase on parse from the originating dss file. @@ -338,7 +338,7 @@ eng_ts["load"]["l1"]["time_series"] -You can see that under the actual component, in this case a `"load"`, that there is a `"time_series"` dictionary that contains `ENGINEERING` model variable names and references to the identifiers of a root-level `time_series` object, +You can see that under the actual component, in this case a `"load"`, that there is a `"time_series"` dictionary that contains `ENGINEERING` model variable names and references to the identifiers of a root-level `time_series` object, ```julia @@ -377,13 +377,13 @@ result = run_mc_opf(eng, ACPPowerModel, ipopt_solver) ``` [warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0] - + ****************************************************************************** This program contains Ipopt, a library for large-scale nonlinear optimization. Ipopt is released as open source code under the Eclipse Public License (EPL). For more information visit http://projects.coin-or.org/Ipopt ****************************************************************************** - + @@ -780,7 +780,7 @@ math = parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model=MATHEM In this subsection we cover parsing into a multinetwork data structure, which is a structure that only exists in the `MATHEMATICAL` model -For those unfamiliar, the InfrastructureModels family of packages has a feature called multinetworks, which is useful for, among other things, running optimization problems on time series type problems. +For those unfamiliar, the InfrastructureModels family of packages has a feature called multinetworks, which is useful for, among other things, running optimization problems on time series type problems. Multinetwork data structures are formatted like so mn = Dict{String,Any}( diff --git a/docs/src/enums.md b/docs/src/enums.md index b86a27565..44bc15712 100644 --- a/docs/src/enums.md +++ b/docs/src/enums.md @@ -67,5 +67,5 @@ Some components can be dispatchable, _e.g._ if a switch is dispatchable that mea For generator objects, the `"control_mode"` field expects a `ControlMode` type to specify whether the generator is operating in an isochronous mode (_i.e._ is frequency forming) or droop mode (_i.e._ is frequency following): ```julia -@enum ControlMode DROOP ISOCHRONOUS +@enum ControlMode FREQUENCYDROOP ISOCHRONOUS ``` diff --git a/examples/engineering_model.ipynb b/examples/engineering_model.ipynb index 3ca387e01..b73a0fbf8 100644 --- a/examples/engineering_model.ipynb +++ b/examples/engineering_model.ipynb @@ -6,7 +6,7 @@ "source": [ "# Introduction to the PowerModelsDistribution Data Models\n", "\n", - "In this notebook we introduce the engineering data model added to PowerModelsDistribution in version v0.9.0. We will give serveral examples of how to use this new data model directly, including new transformations that have become easier with it's introduction, how to convert it to the the lower-level mathematical model that was previously the only user interface we offered, and how to get various types of results using this new model." + "In this notebook we introduce the engineering data model added to PowerModelsDistribution in version v0.9.0. We will give several examples of how to use this new data model directly, including new transformations that have become easier with its introduction, how to convert it to the the lower-level mathematical model that was previously the only user interface we offered, and how to get various types of results using this new model." ] }, { @@ -1506,4 +1506,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/src/core/types.jl b/src/core/types.jl index a5234cb1c..131b59b08 100644 --- a/src/core/types.jl +++ b/src/core/types.jl @@ -11,7 +11,7 @@ @enum SwitchState OPEN CLOSED "Generator, Solar, Storage, Wind Control Modes" -@enum ControlMode DROOP ISOCHRONOUS +@enum ControlMode FREQUENCYDROOP ISOCHRONOUS "Configurations" @enum ConnConfig WYE DELTA diff --git a/src/data_model/components.jl b/src/data_model/components.jl index de15debf9..2fff014af 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -1,8 +1,3 @@ -#TODO -# Can buses in a voltage zone have different terminals? -# Add current/power bounds to data model - - "adds kwargs that were specified but unused by the required defaults to the component" function _add_unused_kwargs!(object::Dict{String,<:Any}, kwargs) for (property, value) in kwargs @@ -106,9 +101,7 @@ end "creates a linecode with some defaults" -function create_linecode(; - rs::Union{Matrix{<:Real},Missing}=missing, - xs::Union{Matrix{<:Real},Missing}=missing, +function create_linecode(rs::Matrix{<:Real}, xs::Matrix{<:Real}; g_fr::Union{Matrix{<:Real},Missing}=missing, b_fr::Union{Matrix{<:Real},Missing}=missing, g_to::Union{Matrix{<:Real},Missing}=missing, @@ -117,13 +110,7 @@ function create_linecode(; kwargs... )::Dict{String,Any} - shape = () - for v in [rs, xs, g_fr, g_to, b_fr, b_to] - if !ismissing(v) - shape = size(v) - break - end - end + shape = size(rs) for v in [rs, xs, g_fr, g_to, b_fr, b_to] if !ismissing(v) @@ -132,8 +119,8 @@ function create_linecode(; end linecode = Dict{String,Any}( - "rs" => !ismissing(rs) ? rs : fill(0.01, shape...), - "xs" => !ismissing(xs) ? xs : fill(0.2, shape...), + "rs" => rs, + "xs" => xs, "g_fr" => !ismissing(g_fr) ? g_fr : fill(0.0, shape...), "b_fr" => !ismissing(b_fr) ? b_fr : fill(0.0, shape...), "g_to" => !ismissing(g_to) ? g_to : fill(0.0, shape...), @@ -193,9 +180,14 @@ function create_line(f_bus::Any, t_bus::Any, f_connections::Union{Vector{Int},Ve ) if ismissing(linecode) - line["rs"] = !ismissing(rs) ? rs : fill(0.01, shape...) - line["rs"] => !ismissing(rs) ? rs : fill(0.01, shape...) - line["xs"] = !ismissing(xs) ? xs : fill(0.2, shape...) + if !ismissing(rs) && !ismissing(xs) + line["rs"] = rs + line["xs"] = xs + + else + Memento.error(_LOGGER, "A linecode or rs & xs must be specified to create a valid line object") + end + line["g_fr"] = !ismissing(g_fr) ? g_fr : fill(0.0, shape...) line["b_fr"] = !ismissing(b_fr) ? b_fr : fill(0.0, shape...) line["g_to"] = !ismissing(g_to) ? g_to : fill(0.0, shape...) @@ -330,7 +322,7 @@ function create_generator(bus::Any, connections::Union{Vector{Int},Vector{String pg_ub::Union{Vector{<:Real},Missing}=missing, qg_lb::Union{Vector{<:Real},Missing}=missing, qg_ub::Union{Vector{<:Real},Missing}=missing, - control_mode::ControlMode=DROOP, + control_mode::ControlMode=FREQUENCYDROOP, status::Status=ENABLED, kwargs... )::Dict{String,Any} @@ -650,7 +642,7 @@ end # Data objects add_bus!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "bus", id, create_bus(; kwargs...)) -add_linecode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "linecode", id, create_linecode(; kwargs...)) +add_linecode!(data_eng::Dict{String,<:Any}, id::Any, rs::Matrix{<:Real}, xs::Matrix{<:Real}; kwargs...) = add_object!(data_eng, "linecode", id, create_linecode(rs, xs; kwargs...)) add_xfmrcode!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "xfmrcode", id, create_xfmrcode(; kwargs...)) # add_time_series!(data_eng::Dict{String,<:Any}, id::Any; kwargs...) = add_object!(data_eng, "time_series", id, create_timeseries(; kwargs...)) diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index e67a2a391..df7cdd992 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -2,7 +2,7 @@ import LinearAlgebra: diagm "items that are mapped one-to-one from engineering to math models" const _1to1_maps = Dict{String,Vector{String}}( - "bus" => ["vm", "va", "terminals", "phases", "neutral", "dss"], + "bus" => ["vm", "va", "terminals", "phases", "neutral", "vm_pn_lb", "vm_pn_ub", "vm_pp_lb", "vm_pp_ub", "vm_ng_ub", "dss"], "line" => ["f_connections", "t_connections", "source_id", "dss"], "transformer" => ["f_connections", "t_connections", "source_id", "dss"], "switch" => ["status", "f_connections", "t_connections", "source_id", "dss"], @@ -586,7 +586,7 @@ function _map_eng2math_generator!(data_math::Dict{String,<:Any}, data_eng::Dict{ math_obj["gen_bus"] = data_math["bus_lookup"][eng_obj["bus"]] math_obj["gen_status"] = Int(eng_obj["status"]) - math_obj["control_mode"] = get(eng_obj, "control_mode", DROOP) + math_obj["control_mode"] = get(eng_obj, "control_mode", FREQUENCYDROOP) math_obj["pmax"] = get(eng_obj, "pg_ub", fill(Inf, nconductors)) for (f_key, t_key) in [("qg_lb", "qmin"), ("qg_ub", "qmax"), ("pg_lb", "pmin")] diff --git a/src/data_model/math2eng.jl b/src/data_model/math2eng.jl index fcc59d8cb..55b0b8b1e 100644 --- a/src/data_model/math2eng.jl +++ b/src/data_model/math2eng.jl @@ -1,5 +1,5 @@ "" -function transform_solution(solution_math::Dict{String,<:Any}, data_math::Dict{String,<:Any}; map::Union{Vector{Dict{String,<:Any}},Missing}=missing, make_si::Bool=true)::Dict{String,Any} +function transform_solution(solution_math::Dict{String,<:Any}, data_math::Dict{String,<:Any}; map::Union{Vector{Dict{String,<:Any}},Missing}=missing, make_si::Bool=true, convert_rad2deg::Bool=true)::Dict{String,Any} @assert get(data_math, "data_model", MATHEMATICAL) == MATHEMATICAL "provided solution cannot be converted to an engineering model" if ismultinetwork(data_math) solution_eng = Dict{String,Any}( @@ -11,7 +11,7 @@ function transform_solution(solution_math::Dict{String,<:Any}, data_math::Dict{S solution_eng = Dict{String,Any}() end - solution_math = solution_make_si(solution_math, data_math; mult_vbase=make_si, mult_sbase=make_si, convert_rad2deg=true) + solution_math = solution_make_si(solution_math, data_math; mult_vbase=make_si, mult_sbase=make_si, convert_rad2deg=convert_rad2deg) map = ismissing(map) ? get(data_math, "map", Vector{Dict{String,Any}}()) : map @assert !isempty(map) "Map is empty, cannot map solution up to engineering model" diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 485b1f2b5..2ec6d6a27 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -331,7 +331,7 @@ function _dss2eng_generator!(data_eng::Dict{String,<:Any}, data_dss::Dict{String "qg_ub" => fill(defaults["maxkvar"] / nphases, nphases), "pg_lb" => fill(0.0, nphases), "pg_ub" => fill(defaults["kw"] / nphases, nphases), - "control_mode" => DROOP, + "control_mode" => FREQUENCYDROOP, "configuration" => WYE, "status" => defaults["enabled"] ? ENABLED : DISABLED, "source_id" => "generator.$id" diff --git a/test/data_model.jl b/test/data_model.jl index 3b03f7f08..d3af1bf55 100644 --- a/test/data_model.jl +++ b/test/data_model.jl @@ -10,8 +10,10 @@ add_voltage_source!(eng, "source", "sourcebus", [1,2,3,4]; vm=[1, 1, 1]) - add_line!(eng, "trunk", "sourcebus", "primary", [1,2,3], [1,2,3]) - add_line!(eng, "primary", "primary", "loadbus", [1,2,3], [1,2,3]) + add_linecode!(eng, "default", diagm(0=>fill(0.01, 3)), diagm(0=>fill(0.2, 3))) + + add_line!(eng, "trunk", "sourcebus", "primary", [1,2,3], [1,2,3]; linecode="default") + add_line!(eng, "primary", "primary", "loadbus", [1,2,3], [1,2,3]; linecode="default") add_load!(eng, "balanced", "loadbus", [1,2,3,4]; pd_nom=[5, 5, 5], qd_nom=[1, 1, 1]) diff --git a/test/delta_gens.jl b/test/delta_gens.jl index eead62106..2bba50e4f 100644 --- a/test/delta_gens.jl +++ b/test/delta_gens.jl @@ -24,7 +24,7 @@ "bus" => load["bus"], "connections" => load["connections"], "cost_pg_parameters" => [0, 0, 0], - "control_mode" => DROOP, + "control_mode" => FREQUENCYDROOP, "pg_lb" => -load["pd_nom"], "pg_ub" => -load["pd_nom"], "qg_lb" => -load["qd_nom"], diff --git a/test/pf.jl b/test/pf.jl index ce5d47a92..d83bf583b 100644 --- a/test/pf.jl +++ b/test/pf.jl @@ -114,7 +114,7 @@ @test isapprox(sum(sol["solution"]["voltage_source"]["source"]["qg"]), 0.01854; atol=1e-4) end - @testset "3-bus unbalanced w/ assymetric linecode & phase order swap acp pf" begin + @testset "3-bus unbalanced w/ asymmetric linecode & phase order swap acp pf" begin pmd = parse_file("../test/data/opendss/case3_unbalanced_assym_swap.dss") sol = run_ac_mc_pf(pmd, ipopt_solver; make_si=false) From 14e6c1be2de05be2403d729e4ba5cce568bb97ac Mon Sep 17 00:00:00 2001 From: Sander Claeys Date: Tue, 12 May 2020 15:16:39 +1000 Subject: [PATCH 219/224] added docs --- docs/make.jl | 1 + docs/src/assets/line_connection_example.pdf | Bin 0 -> 23921 bytes docs/src/assets/line_connection_example.svg | 436 +++++++++++++++++++ docs/src/assets/loads.pdf | Bin 0 -> 25684 bytes docs/src/assets/loads.svg | 100 +++++ docs/src/assets/loads_connection_example.pdf | Bin 0 -> 23572 bytes docs/src/assets/loads_connection_example.svg | 334 ++++++++++++++ docs/src/assets/loads_delta_3ph.pdf | Bin 0 -> 16982 bytes docs/src/assets/loads_delta_3ph.svg | 183 ++++++++ docs/src/assets/loads_wye_1ph.pdf | Bin 0 -> 16498 bytes docs/src/assets/loads_wye_1ph.svg | 176 ++++++++ docs/src/assets/loads_wye_2ph.pdf | Bin 0 -> 16407 bytes docs/src/assets/loads_wye_2ph.svg | 153 +++++++ docs/src/assets/loads_wye_3ph.pdf | Bin 0 -> 17510 bytes docs/src/assets/loads_wye_3ph.svg | 206 +++++++++ docs/src/connections.md | 45 ++ docs/src/eng2math.md | 33 +- docs/src/load-model.md | 2 +- 18 files changed, 1662 insertions(+), 7 deletions(-) create mode 100644 docs/src/assets/line_connection_example.pdf create mode 100644 docs/src/assets/line_connection_example.svg create mode 100644 docs/src/assets/loads.pdf create mode 100644 docs/src/assets/loads.svg create mode 100644 docs/src/assets/loads_connection_example.pdf create mode 100644 docs/src/assets/loads_connection_example.svg create mode 100644 docs/src/assets/loads_delta_3ph.pdf create mode 100644 docs/src/assets/loads_delta_3ph.svg create mode 100644 docs/src/assets/loads_wye_1ph.pdf create mode 100644 docs/src/assets/loads_wye_1ph.svg create mode 100644 docs/src/assets/loads_wye_2ph.pdf create mode 100644 docs/src/assets/loads_wye_2ph.svg create mode 100644 docs/src/assets/loads_wye_3ph.pdf create mode 100644 docs/src/assets/loads_wye_3ph.svg create mode 100644 docs/src/connections.md diff --git a/docs/make.jl b/docs/make.jl index 1c0bc33d6..0583f77d9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -9,6 +9,7 @@ makedocs( "Home" => "index.md", "Manual" => [ "Getting Started" => "quickguide.md", + "Connecting Components" => "connections.md", "Mathematical Model" => "math-model.md", "Engineering Data Model" => "eng-data-model.md", "Enums in Engineering Model" => "enums.md", diff --git a/docs/src/assets/line_connection_example.pdf b/docs/src/assets/line_connection_example.pdf new file mode 100644 index 0000000000000000000000000000000000000000..21c82a3338fc5631b3c1856c14e8ba8020b1019f GIT binary patch literal 23921 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≈U8BH^n3ETFq^~Vc)Fu~=xb;sEO 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 tXrJ$|2}C +image/svg+xmlf +connections=[“a”,“n”] +1 +2 +1 +2 +line +a +f +bus +b +c +n +a +t +bus +n +t +connections=[“a”,“n”] + \ No newline at end of file diff --git a/docs/src/assets/loads.pdf b/docs/src/assets/loads.pdf new file mode 100644 index 0000000000000000000000000000000000000000..46ae00b00d3de9ee87e4f852825304228f4f0c35 GIT binary patch literal 25684 zcmeIb1ys~q_cu;RgOmb-;Ls&8!wf?s-Cfck-7Ou`A*q0f(nxoQ0s_(^Qqm35-Sr>v zUcFcEyMFh*cm3D9*7N*W$egp!KDEDl&faIA&-qF%FD%LgWCo){0jvOGFBuN|-aURAwleq|8weL|;T@JY0$l~iQ)Vv_0oaIs({g-BIA>^C%g9LCt~GEz z2yiLI9P+}iBC#8L+h`A1Qb?rlroin4enAAg!kJsk7t_*|)hE{7!<_wk!+Ug5R|+nX zlpBtYhUWdg8rOrN+n9b|o}|FZ;EbcKos_4Owj-r%8l%8BpI_wNNX)>$yTSaiYjtj} z8PR@!-p-Fxvcv)217J(*L-qw9a~F9G@d5|g^odrJH``p3Au5dtM5=0^1WKey|fgaJcnS;-}T;vC1q?qR<`P)z*jIG`gI3J!y@Ay;NQ#o?fy&}V? z*RK=vxKUS7o^T=D1u*ORRK_4Ob05S5xboYAe^xe@_2EPp>Y^;BfBz)cAF( zHQy&6Z%h=_fyo(cGpc#q3r`XY=^jKklQqiPhuLD}lZHuj-yC&Bb-u5T28zhv8*BA~%REe`t9 z1YfBqhI(t4e=vDmFQ{QXt1C#r1Afs%ZWIyW=)&XTZK+30{9BTN)=mDchK+bNfsuh0 z>4aUI50?=g9W}=Y2-Qf(aN}o!9&pq5qCa(6aqla?l%*0ehJLn5>V(25qjzU9-tN+HpTBl+vnDljQpT~-3&A3lEfVI!rc_obb!M$d`YSj;Fp9fK(O+4 zXVbvng+aih>3|ET`gQ-5`SF;|kx8c(K)yI4k#@ef3}M=<_T}YmcVZ^)rL3&xU5Oa) zfJV7zyHlDBg6rdJmrscrqFCW>_&qseO=dpsz(cqJmu%cewssmH3}4S94?R*spAON% zX>7s@JYEuQ^?}pTBx?V7=WOqUv+?rA;thgwCmVqTm^v4hodzO9QFz zBe=<|;SGM56da%7UH+r2^J!VuEpIrvZkHK8YwwXuk)Dqj$nGyU8d?rpPIV7-<be%XZ-L(zWe~iQBo6V(r;5)5I)s`3%`v zER#qjMgn#1sZaCJ2NR%GPVIWt6X&50w`bhzFEH>k5a(KhO7ESRv1Zp`89qkb1<hezG_=--m6(zRu3awRrY2ry+k&t zk3PstZv>VAXUkzwhi&(ltTt!SvP=p1Y+vaFQm7g&{VwnI_Yj$f?xeQ89CT6 zlNc~6>#5r(ZHZ;@i6+OQDuULxfzTGYf6bYzz2NZ~`^saNXo0s>?E5pgtbPV7jzpuM zNo~izmT;XJ`J95d($~$akGoWzqwEqK8^9Im*ik}#9X!@X>O5gIh!uEzp#w&ks?;Br zG-rdYKJINWW0Sd8h&I*~{*(M^- z-#0zwoLlOBgiZ@DlDKAC>3hF*BOb{jB)=99Yx+$>u;gSi2_%CD{kE4QF@2&SMwXv6 zwQp|cTkhS8W;=ozFP6gAP!}(vL83}pwA;&{ZSJ)?L~cL>usTG9_e0Jrr>4%dRj5rw zb|!kho|uHo<&STz+8|RzBe8Jr3+~qPRItSw%|%PHR2jPiE$>8P&DSq$C*p5E#Y&p8 z41XLud2Fka%fMO|ci;RV&pZApnpU}%Jl%3;Hg+tzr)%Hc&Z#Yl(@-pUZW8Z(h{M4K z2$a5`HDjFKk0Qy*;OUnqD0+$IKY5(f$8=KkOj8=38A4#>>&XO%ma3M}&fIZ-Z&x#Q z^W$)G+1pTe+h7ZIw!LU8tu(J>2pgOZKQXaDw1LNggiqu{^EGiKd99|GZ{H}qmpJ2y ze|h?-pI>Y-khx^RwIEBRnRq+EPycCkukWByU&S( zI}0Qg>qgeAxs9Y|HrW>>An{cutvAQ(zLMUA!HdMg`7e_XkqL$}WVXaQ?rH#ZMF4J# z>4vAy!_Ph}t_G}C=w~Hc97nH0riA(uU#_f{WdzY%X7q}D#+E3_7u!+j>F4Sd@pPg> zdQt{n>YbTWV?dR1;lO*JP{1U>>cleb{AA2Ia0fLz$~hyPVe4gJ$8yS)*L=AHcGKhX zZh}=Z+EfB7t2*CN-^sm2^`ZD&tAKsldn+5_$dA`&>5qrZazBCx3n9DG$GKv7ZxQ{6 zJEpqTwe}IB^jyiBH;Hc0CFxQ$bO2v~RF>2gX!(hZHy0_CF4D;0LSv!Nls=yOOGwRC zbqA7UeI%afD(Zn+Jy&4N&uwm*w?939Qyah?YuQ{Mc4mvr!*$miA|4`}$_MIw-@A77 zq4FdDqN0^Q#j~z1riW!O8zRTOdl;$N_>Dzh%xQCr(aU;c%MQ+`e_1Noaaw*yu@B97 zyf&}f#?qjE=Gg5wTr93;`Ic<+)Cog`jbrcldF@!!bDJU~@~IdVp301KcWbRTEib0x zt#t?dbeg?#ABT0Wv$~L&ww#)aZ`5?3>O$N0Jm@FhAX#4wOScW<$NQyrdqR?FgCsoO z6?o27Ip(ctqYP+FN#${Qw(gFyL^+(N`is&`-e?ig6|WL2BHF=!hqhAw)y!*NwF$ zjFw?YaTE4w|yY!o=HEhkC&RvE>$jGpgsJaSSvYP4`)g?}Zg0mI*)GAHSL2V*w?9=SAGyzGw`}XiNd{yks*>f*Gbh zlfIhh0?x_scg89!w&kcaM}lznA3$a9Tcp)2V;cDGqpZx!ad?frJdJ#xs&c$pz^Cmy zUE}x_G}_pVFnRYzrcy2R9@3fV9k!~qEihz>_W5bghFVXwcHX_Qvo>A&fE0b4T4fzI z;9j=GpsxbK(|Nq`g>1irr(d4QtL_EGk>AonqSd!BqIL7nV9KCQ9*xCAj%MUvp5I#Y z{`?>TbN{U%J#`iPJvLw{HGqWYGBZ&($RK6cWrj4`I~?ueeeCQ-vG5&u3_HoUW)k}p zyfUF4M$OB34??`qhTguCDvh%P;1i@Ms}IAw%AF_?MZ`QDUJA#r5Hf{(h2 z2jN7qnh;8-Mo?uGV|{P#h$Ma({*;OUolDdp%gzqfqQbS@0SeF}O-oa8D906h+ourW z+RKz=)6Pm~HJ;6=edpGf$#*gvV08EO_lvU+$6l8ia;rQmmYgcpOlnRvZa6W76_WHopxZMQdOzjwM-%K+L(E|lF#q{yN;7RYEIL#-a4Wi-V+&vWZ z=ERnkU8BZFD{obPCrakhk5W_~uU~(kP~?T}P-sGkT9H_Hava?X*9!?JG^JX7p=J%2 zOeZq6P`Z-sf|iTa+NA>`U?%dV_3Ly>S^Jc7fK^^HT}(FKNLik;bg2XJGCsfMs(xxv_8U7x2iNZwTsY` z`Y=4JVIvt4$dko_JO%FWNZ}OLGtulLDiST(!Z^WEtv)hX2lZ@`B;!Di@z>dWeTR?B|*#qlONn&h`P1cI{ z)1+}|B#%t{sV3HnKO*JI(pemj=FF#BwQk=_OaA(00P*g?w$~HlI;so%Ca~zzBH`i- zytLFuGkwR+dCC1NxY}hd3`Gw~US{C*I2wJ}QYM5>cZ-ZgE{|w0F04^_-hb_UENk%T z?%Lt4DH(N>I04>!SZ;FaGJ51}s|oe;7N*3dFGb<$3Ai+CF^!vbE`~##UqECu!0;=Q zGOJzu1+U61huu<8nljqhiJe5^zILTNYDQ=oy6~xcD7e{!AHE-3IU(3Ipxam_O)ku; zHqv%4$*hGJamhr!%*imK`(WvIye~nn$HmxVn64yWnp^CtjbsY|a42QEeP(^Qk{F8A>=?^AdYjer z{_Yo9@m0zE&)T87!UftJ7xf*_2#>|a#l$LgZ@;6e)vC6o-yN0d>r*!By@+GAkN+PBC>Aq*L3afc#y1EQCY;5|RR>`A;Agns|SK&nvem_sd1fwU>Zs*$@3 zrS0JC%bQQ5Qq_}0lvb1ykmw|C-zS|q8OC*_&PO55QaZK9V9pEZEO>$_6WJRjTA@Q( zQ5vD~m`c_R{UbH0_;c~%)PiIVX0`Y*^;3Gxjngl01=Zrmed+g&rxwZz-R;3fD*EkU zceTB?SF3Mg*d}j;n4H|o|DswzI6G#0tKyKw@Mf@Q9-lB>={CK0G?4|R`q2IZH*Mh2 zhY_EOeG}$)!i9!0D7PK;#~3j;(#Wx;AX(uJT5dJ1KS8Ra=FCLygeyGfZmR`w-`sgm zOeahI*0?#}saEzAV-xFF2k$AJ%_4j7BcVpB^I^yS@^nOhi<3LTDtmYOU+*mQpgS#k z*5S3*#!-%r^S=Iq>X|#{<_(E6) zyNYz!~(TBfDnVyNMv8dBBG zut`Nq^PK)pj+~cVzt>sCyHjPFia;KeInwM};PXTdCH59(=99XxMBt-znlW_>V$9T< zr*t!;u#wm_f4{(2 zIf|QjsZc00{RKXLkBlpST4s01VnvVdpmJX6i49sRL;0iRrsl`btyEsLg;>`3_*ov+ z(!;mpah01m)g7+&&v*3QN~OoV}!B_IT61imUA%sU^)DKoOJD``|5t;rsISIgVE+3f%8K@H?PMod&FF?bsr* zOy?zYNbjOV)icfGwUy*k@T~}yN*mtOM9;X$#mLo}AuMH`nzL<}JbU|Yd-lR|dZ~;i zK1#%va1f9dJ{*gQX9SSk$0h9;mt6CjtAo#@*3Z3HU*q+xiajM6$v;zw!*0>Z!0w1$ z={03&jR-H_^NDORfCj@F!lib50y169Q`WDo@h5Nf6UNf2U#DE)=+;l*&qZeUf}B%; zNkvmT0-UypwyRnhJq%~PbN%mdK1fxpg^=~vI9N!RV&0yhNX{M;hqol|N^rOQjNzT{ zMRt(?+Bm-kkHecmmLmmh;0g= z4pym(L^IDiq&svdo{=;R#&xcsWspSN%ph-@iuCIX!87~l(M}6P5u$e}bi%6=J3GyrkW&>chIrPB6f}3jwZ)P{lvA~y98QxZ|hH~wkYdpt%=!e&9JOs1ebB2Lzm->=nSjg&N3r~o#aK|kg+cz zSe*ttjD&)Ol_-XEw9OvSAm*m|d`Fa4903xcUn_Ke2%dXbD&WOd_<13`m2BZWb5NQA zghdUC6Q&VWn|?y}pec>zu2`#u=#fIzrPnNzMMPm^RDnN>L6-&B69=wy-0e=n${TH6 z-AuL>^CtC*y6ENiybb#spANluqw`=|)$HTS_yU#NsZG2uq-|dq9Wc(d-E)Xe^PFm{ zru<>2dI;dQ9l>UWW1UK`vazVQ%qJ%alHhw?_vv0ot();a;_Zhn>nymlsP{~6l!0a9ig{NFq!d#$E%CwTcLtl-5Q|%nuNhPB%LP`Z74{#tu{d&N3ZHj#!%hS zV6u29_auTIqdO8~Yc2?--$sx<#89$nNd4Ue8g6@F&6DkX8hRN&V00(QCD7sMeeOd7 z$AoBi#)iy|mZ0!_o9MtyW2sgSxG`1jceUgY^)4C+1YXil?uspg~B`osrEw^>G@)+(^PgF zS$qWEQx2i?oV(ZuJs3I8<|71*_r%RCvU|nXBjVbHHVd~A`rf_g)`g=*DK$LQ!?vTU z(0*2deT&k3l2>cR@kua|Nw~tCnW%+?#K;Sq%~D57k{-1oDy%x0mK=rm@o|~!9Nug; z^=%wO=DN}Sgm_`^Lcd=4ypT`m`oujPf8Jy_HSONEQWJxN>XPal>I*Hvr{BEg)dXHd0lrV+dZ`^ zYXQ`4rZ2dlt!KG*Ar}#{XQl;~nIH?{duCIeSR+9pIk>I(7XfIuNu#Yxxke58t>*MM zkTVO9nQsb>nV<>MqP4fyVV`A{saoi#Z~EH4R@~cfwSMW3TN4jR zaHuB}7GdE65w_PyLX`pR^H#cBvkk=ZEP!4{qeMw=G{*a6lqWIH10Ou=J_(6@HO9YD zKV&AqBpE~5S%UsDnqoXrJE~x-kSLYPP)xb(9nec9gpP7g#H`Q}ZQ@k5B479Gvj{d? zzwih8AD+zTtqmWGQ?@oSP0OjWKericP1iH2&N{b3IT_?VNqBc-n@E*q_T6Y9x>w%X zgbQn_RuXXR@qLUS^$$|p;rw0C7O;Xs5}!@p)dRgWT)VLls}o&ONvTvPN0+)N&C7i& zcgeyPd$QP!tA}F-Kw8BTeI{um~^o zIiXW+#o>&E!rfR@(!JZDO|epJ^V)+HIgAr{S;m~00}pX5EPJKymJ!;WD12K&U0u#m zA(7h=z|9p)30}pD)m~4cx)VOaBAbak&tA4LN$%ioT~xBZXL6N|4AaXsxg|nOJn7ZG zPOq4CZ3Xf6%Fs+(P$CKZoCZL`{!zp)mpt=Zy2Hkpi(LAgj3W&3^5Z9JRD|SCveN?k z@5$Lzi=Hp1Ks!*p?3fn~{1lnW$RF zLv;<(*OE>;VfPnzdq(4JQhUTFXEcYN6K@^?(bEi;qLSwxuT?!q4haC?T~6(vzJdI> z0F};buq>%14cq+{4k;$bdiGZdjeE;AcoS(_xVloKqE9)uN{FOyHp{DMWTsaJiyC>| z^ZOKM6Ssmido z)8sE`)a%Zfv(HR{=yNUH?2-G8Px-&75xpe0TFFqOM7+|v`zfhzu{VdYGXNvc7;cJTUQ=k3@UX0_{yq2@ zBTm-1`v&8TrmkIG!q|*BS>a992Y03eK8E0ixv!6)N}tQe4FVb7VUwjxcYK(p|gP$G`Lh)d`GAWR2cIQ&>tb<5Ds zhKZRZ7dubl=}g4-s2{N?P4NSuaW6*AQ|gBPgp~F8;`YEci!zSa9HBbwT!U^1DKD4Q zl>DA>mXt)Q9V0am7**s*b+`yU&46mLoBA%E&P_&srBRh_53Vk0pRq9Z)G{YgV8k(Y zAbAdnPG2Pw^-1$7eg9N_^W$by>*0fo6^pPcxB$}bBZoQjx1TVQN=SYEzbd>U zFLOS5-85i|`vIk3@p)2_6@P9Q&@Nin@s+rvo>RV}J_@nKf%LiFg0Nru^bLO~bav=i zX;JLc*8`7e$>P-K1&*%FpA!@E;v+|_sK!3E`Dip?R(jw4f_GcU0G?2U99j9cjl3h? z=b>;6J+6Iz;ek1g4?M)i09AYjVP*JSC9iTeU$oK8JgY4E9I@I^osYYu-GhlT)O2IA z>@@l=fbK^jAeJvH>d)8SbS-v2$J=puO(7d#kVjhw5{vF9dX?0GDizcGftf8gQs@K6 ztx_pNhM~(zH&&1g?P&$fvBWMuq9z;Qc~OU2fu=!KM&DmhzPd8VpvrKVlwuGHh0 zs)A<$d>6WlPltJLVLBf@bbx58H8hOA)Op1pG&zQYU_7288!-rAuJB2#Cr;m*Wval& zl{S2r;=fa8%^k~T_)K@6;?})+Wum!Q_LG)@>O1W<2~ePku@3%1goIn+O*ORTw>?^= z0}ELhw`gj*b@3;ytRacG1LR4SxgWVkCl~IXTeA7g;&02)hblAm7c@tBFR`zS;ZJK| z40CO{#gX-p8L95j$w%OFmH4HKsZkyqP4E#OHv(x4_c`YHSce^C`c*7O&oj`u+ywj} zPeeXu+uT@7lobIiyeZe={ZcbGC)g}9wHp^Ld+1$#GMOX3YI)y7S)wjfgLO{K#DUk+ z+{-UpXE9&pZcr+jZ|*=@3~SwEQD}(Y`J^_J;L7J5Q6swn-DQrFGnWiQo=JN?;pe~( zOqN1ML?rV_1?`suBd&B6wq^oL-OK3uyo0*4s1Gp0&r&#~2>Rn%M)P@! z3-Rbj!~~n!yduzdc~V_u@apn-$J$e(93vIEXpB~AFy{rYDQ%t##nfjjiqR5=?b`0g zI>Ev6k;e*ix4YPK`2=P%7F=$9g3mvhTy)rcYIkUHyY_kQCz3TzZ|pPW2IY*mei!g< zq!cQ;Q>U~TC6eaEANu4?t3l}nJl)g0J zl#Qq#niTdtdxz2D?cj|HdGCYaswtaQWw1C+G8wo6;;PKZv0#5hT1n1) zP}sZ`>rn6eiFidk@99EYq$QnHUTIu5G~ob~iiK3|Ym_HV-U`0Qa=FQA&w{%u?c_14 z2rOvS&i~STYfu*|dP#ksn3JO=QTqOR3__0;uF?Aqgew zCir06t>uYd=|hk`Vm9aL8AV@2yq)X&B1_=asCs60Sdkd*5;~gfox>NZ4!DN45uQx4 z^0K#{W>>wsy>a8dBya!p-Q(DydaqMK2~mo=%tUY%7hKh}d27{d0Q!@}W<|fQMq7fr zb(d_og;aHzoTIp#Vv;C=;LFwB7w(D{7Hf!RXNA}yE|G`LegexEUoVD&fZG|es)3Ia z5G(~r^MI0JM%LB(0=K!e&hMW1hQAp&qZ~Z$hzFLPvd?2#e7c>E*IL$=*Dr^M(eOTo zV<*dGMA*;ZFFU1Yzxd7)Xr}H2GY5XsJnhLg^!)Od)8PH*ZEzuxfFaj(e;y_&Mlm3Qt=MYb{j zlw47LGI=Ap(aXCB_Sp%qH!y@B$q1^Sj}CQ2@Gz=af)K8O7bA<_B0$)J2+Y*_FccD zoM-Va%vkt>Oecx#ey2JUQmgA`cWA|wm_%2H58vMJcIIKVU6U$i1Wz3DTaXS8Cb+e$ zk%tCKa|nwY8}=39Czmed#u-V@d+1SM?ToA(VQpEUuiMdJ_e1Upr~_|CxxcP#XAw#? zE7yI~VRN!K<+Y#Djxf=tgC!QP9b;YL@6!mZ5D=@(dvjbKvsCkt%Gw2~ix$jr5x*_B z$*IU|?@%>u7U@QiJ7X^{rqf!eZ(VHN+T&WE6ogNE-~jX5s(BY=+Ok-ffwg^{DBk+I|N zY5S|TSqxc>ZJg{`Y^;q~99SGp?Tw6B9Gz|GzYP?Iy)yz_;rA5z_=e{ryIP%^v_|;jo+x(eSM>Fu(81= z`>mB9+>mPyHclXHDr}svwzYqkJ4}nd<0yaV9_-OOJ#xHO#VaE0nG5PW7+q=MFBjAvsQ|@>RR6@AjQQV|oml@y{4E8t7>|cx? z@P8WqZ>tm#{G83_AeXZwGs${f)D zQI#oq5e*#vQkTnX0z#5s{=2dM>s9c7T1P;?n4JG!iQD}m@lpKHUnLIu zch(Vh;o`o|N0Yln*6Pt?T_U!)Y5f18{m8S71HHG11#~q z1=t9_vwRx>@T%|!!i4>Y6-HbuKI%I58=u0qZQ#X;>B60;%T^vgM7gE7-!(Vpz)sMGTyCiv!@DEJ zgWkK#x39ohquO-2q&!wss-b4X#Bz5s3%3a|o`KXZ>1+2$8AFAx(Yr#oqyj43)MAD? zclWcC%+%yYL|KGG3Kgd>N-D0BfQ5vI3Ro6#$plzya#cn;A#|N zOgVUtHp+0(o;j?L*=&%AJwsP3eK%bBajPLaEcU2{qS`vb9#eop|i z`dDd2Djq42&72a^oP=~N0d8_dqcKB zoV?AnM>ZT0rEX~<>z^H8R#rWu@e&mhui}PQzQ9kceLs{H)3SbiKQ_vym^ts|raNP_ zd#|%r6ooTU>OrVzb);x)1czL?VQ=n5UU$&Az5{g#YJkd}sSsXM`6|Kvn{s;B5@DUq z{@mw}?G%LFI<3yMMXVpHVB`_M^_V9YdZb+TKJ4Wd!E$h!q!=RKV0Ba0kY4s~AF>T@ zYA{B9_u;(NjdQ(o$HKk7F%HU%gy>$7VrzoJ$5Vtlr1mjp7b`y1ar?WxHhg=_HOE_i zHs_ZI=SB6N=UGL%KHhW45h{{4RqsZ~?M<{s+~m4B%^s;ahbZ5V?zEzClWOULhBBEx zT^f~KGci}Tx_=Hu>V>!_KK&F2tZI%MX(HYQ(X?;6%LE-$+dO>B@<0+Nl*yt zt9)?&OhP>0L+TckWxl2C>=OQMj#>51N+1_W?s`#wilQxL>*?dhpvjrj;ne$q#myG} zxMHypG&}>OWT8z$BQucqx6(V0m}fE-0w<@Hu+Uk|-)mBlRx2-?HfEudc;xELh{(8v zp(5Si0!=+*)ZeJGbzKrT`YQQKFwd^f4E_G5YOo1?PX1)vM{U|I?sV^qfD3`d6E)hr z&lA4s`APLmU$d+N3G2g`&^lCMzY>$G`c5_I1>SCeiTv5H0@yF!Csj0;D)fB5iIxgE z{DcbJi5hd?nJ2cZ{UtBt+7t`B72`jcsda#U$OWI9NZM}Cn-qn zRAIAhE9LgQ596V!7x91O-*C&JDG9bY95!Sa18w?Npdw;MIT^|E{D}ey6bDFOa2)~s zmf-+g2dBPeI04sjlj{s@?AKfRZy6X0^~c;nfa^`tw-OkP_xlK7^SX946~9RoCRP?1 zBSSO&YcMNpn6M^c?>^-KgJ3RzkiCuVH5~|rHO|4t4CMqs*uYolXATZ#5D>;9VDI=p zaLDW1U%|q^J+L2M(68Qrge^?Be)l?9VZ;BQt(~Tf%^W+<+fy}E1oSmBs<3+}3^8y> z8jcuy1FOc^rA%XjhFy#u65}GLW875K3D4;?4?d`8wYR3Wx3#q;;F@Gx+r-FN@_cZO zHeA*0cP^ew_yCSf2X)!>@aui5Dha>4Ywh?fe<^_|%%p0uh~MA=H}m`8Pf_yy4~@zU zlgdBPJytM{7}=B2dR_-+AL?Ux2ccDnmcCGJ(6F=}sC{>u?7m1*N^>Ph3KA0PlH>w^p2C&BdlnQ?s9UA*NU+DeGb6^9=uVB`0RBJkyqPs6*kPhXqCfub5VfTi|PuP zjwX?PhEy}>#fsDDhVYz~( zhUDLA>C^Dad@*v!`ShG-m7_C~(LBU7PS=iiWfZXh*j0%i*TzD`V(Ak;ch12#A)1B8 z+whQAh-phzG2U+PMN5qtkLWT%5;>iV$UCv_^wK*b?AGDdN*wuy!EnK`q4S%y8FJ|! zxKMx^Hp4VPJQ5h&;YN!lc(;{lvhMDpW-oaqES6nB?Ib&u>J;hkWR>F>YS(>8_ej4OC)y}VB&_Q{ z{zTbrZz~>cT-GP+dpfYbaeJ*Uj-EbiPiuRusVKee^&^sY_M01$GDFy%5gzfG%=~*? zpC}?UWJ?qpP3ij|fX5iBj)$${Zk5tcZ*;CLmLG;9-+y#5T`oL?yr5-MEir^jOyzG+I_cF%N6}5h=hk%b$ybbiB%l zS8jr22)Yy@iR);|Th-{0)b~r-2XkJ+FNY%xG(TVzPX#HZcP`yQoW%1W=@WHfEH;#2PKmx!hJitsi!Po%Otbq;UR03bzqG3#<7xL z%o=|HG?IEY-?nH24oC?idJtD+MlSVs4r+`SH73y;+$ zjrk7gQ9cN}QQunf3IN`Z=-x#DvHgC@6J##B9ftfYy}agHB${dj%KQ|xAw}*A%*Sf6 z9i*gphi!G?4VZ}f(8nJQy+j;7fD=Kl7^1MY!p%?9*2m*W>VN1P6afq)#A0wPr;`(L zb-Q(U8k8%EYjsPZCO8ShK-0*8^lpMxnwjDTG0Qm>tt*x<9cr3DHM(F`=w>IX?t{)7 zh2mjv{AEX;nQeuSza;pAT8OY6*jl~)yt|V+c>QD7Gr=TLH9S1!t1Ja%r;Lw;FP7l@T>WjFNcNM40bx68PiAQ*N+51Ha^JR6#oZ zvZd|fC)|ZGjqIDFR58GElS@+Cor6@v&P||)UIS~XGWb;4yrIq-#43=9MG)}S_t7)& zFjFa_dwM3m*!_Opb9-yjgFBinAHIH}O*02_V6^Dma!Vh#a+!SHVusH?a1${_`$PT> z*eh+{eDQ~xH-ETG*3C5PG-WWP?DU^*g6ft z@ybJ|iHTreqU2J>a*Vb#LN|NmZJUR$*L`)1M ze8gMHO@_OStf!?Y{EXWcT-5WjXhl7=^GF_oK&u9s ztankbe7Ule2DSlo8qujz&8Yr@GTu4&d%rT#WwCT_&JoTGqcD_VlO-j3>)m;R(3a9v z5sQ1SSR_Pm_Iw3f7K0OB_`znM8F}?%^^Jg=y(gHTHTz0PX&n<d8{cmAxEf)&mp zOOwn5{km|x+8wY4mqLdePOXr+AlY_inB5vpeC}Y@V}Oj^9%coom1uJ zKw*CdwcB3M2st(Gq?nJk$O}8cmaArXt430!UMhU6S&JKnhXSHJbj)A)8b<&SQw!8t`i3ztAAi-`o4WE|3*^<=y{O7YYl${X_$@ zvco+5Kl1^BSHQoYXy7X^|7Y4SvH}93uq%x}^Ko2-d;eS)1iVtIpJ*V^uY52k`WHS3 z=dXMmoWIBg#Cdgi^5;I-Kvx@tpJ;5bOPfE_V6nbmY3#qqkBt-h3m=&E3S#!>KEP~O zyP=%{9%MOPAGGBJculfv|?yr2DR~Hli+y@l)&HR-HyGZymAB5vqKK84F z%AeZfyxKMXna07&`iuNHuA<;S^RZp+=6|ZoezkA>i3SD!B0o;ns~z&6`8ZiQe&u81 z{FM&^{bhWx|5)SadB9c(*dFxHeSlbjS640m)Gr7K`bB;qFzYYR5(EbRQJ)>{^qs+>EZ)Qx-)V8`#}um=;{cI3%o%Z2;Gc{Z*tE7ID?Lcf4MEL0}L&8#*<$ Jh@2?;{|B>{r@R0F literal 0 HcmV?d00001 diff --git a/docs/src/assets/loads.svg b/docs/src/assets/loads.svg new file mode 100644 index 000000000..63d13178b --- /dev/null +++ b/docs/src/assets/loads.svg @@ -0,0 +1,100 @@ + +image/svg+xmlsd[1] +1 +2 + \ No newline at end of file diff --git a/docs/src/assets/loads_connection_example.pdf b/docs/src/assets/loads_connection_example.pdf new file mode 100644 index 0000000000000000000000000000000000000000..95f2955324aac8698b472c3ee58ed2954357214e GIT binary patch literal 23572 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 k&@(SBUjbZb1Oam~u)C00R07V!Mn +image/svg+xmla +bus +b +c +n +1 +2 +3 +sd[1] +sd[2] +load +connections=[“a”,“c”,“n”] +terminals=[“a”,“b”,“c”,“n”] + \ No newline at end of file diff --git a/docs/src/assets/loads_delta_3ph.pdf b/docs/src/assets/loads_delta_3ph.pdf new file mode 100644 index 0000000000000000000000000000000000000000..aa80be184bed938505b2c521906de8e14602a2bb GIT binary patch literal 16982 zcmd_R1yEee(P6N01y*v03RQ-9N6B% z#S#brvTjHLy-ffLtz+zxih#6Qw0L9tG z32bbK;xQBBf~b@>JI08OtPrrofjKuH+}Jn^1CJK?kPyk81~W+1LD2yf0s14FBeQ(A z)V@r^y^(RXcxCX>Tm0jJ>)G}~vs?MX30n%!t_p_|{WCsXIE#(-C2ZKfH&)Khu=VvG z+|VD(=9X)_gXD)rdo(X!%t1V~(1>g105mj&5W!2czyrxNB3w8nH)jNgW4L-mv3f-D z`g#~XJ-v=^a3aR!FxpwXpOCV8V3oo|gvtm{KuQ+Ogr9~#Fwtz$LtrKX9K4^6uS8A?Nd`QaAVLfBzLC674fN)k(+)`-@9YeQ-&}nU8#*1y zS_RA3=HMN`(T9iNd~X`Y4+rTDe7cZ7aHTEl-sViO{i zLwn3em}mzcTtbr4cv;J6LDz^HH>hn$=U4VHbC2h~C0+4exs$Q1DuO=D5N)rbg?I## zxxwhZr9jRiD}N7r7E%QpfSI}|l$A?=0wmphfWQk^N&@T8a}RFybu~HD&m;+YT7BIBTmhNU(XvV}31FOH1Nch!pe(0Oose$sJAHE# z1RWeZhVj7Ll1c|$x7B9#`V$J23Atm!JaC4;xVZk(=C4CIHwW|RE&F35#7cNkmRtRs zQS{220l&urXuJ1Wmc1rOI0JUh76lE>=nKds|Uf85(uN1Y@Cm<2kEc1X8<0_ zv&_xhGS^(MOyZ}af9D)iff+qI3G-fvR~-|)`oDGP=KsWVvujt?EL++U`WwryR;U2r)l)bpdA zK-ws<6zcDt#NCj}mCPHx?FHZF)<~v&tNhmV81RgykI2-X$eNL8Tj|k^mo}sLetQe| zkyDW0_!2Q|GoTgnAoKv%h*#e*Zdc^q%#a zhXz{f4dDVg>56(rou=x7>{nc@yyo z?oYOb(YqsDZXTebwwcjqVP!;T+3!Z?9=+=iP?9(@2#V=SNeJ7(XVF3LD8i4ng!4fE zLXWvNY@xgLdeZQcxb{II?s3PWfB+12Ma=FQ+#_Be`)$1!NJ!q(%pIz!%=d_prg*Q> z1f))+lsx)aZ%0$vKkwGl6&uns3ctv(N+u?B)4nacJ*rGr$X}iYHs?|`2-EA` zXwy^x-pZY-59k-#0s~KB&f0OaKD{i5ojP6pb9M;di6f=$+!`7IQ=qQZ004N z7o+1fq7K`mtI^#~-c#Y5F*4)%j#gW@mTz?Qi5xW7upe7_iVh-@NIV+z=?PQLF00&( z6dH>qQi9aj#;&4I3z=Ms3GOwF?XxMMSJWdlFqcn-&n_-{y?svCKgNnbRCxO48Z-JJ zoi+72<2HHegCt5X-v!J*)!BT<{*Zv@7q(lBzUz~4N2t-Yfk-zi-XJR{;`xDVf`wva zeb&XWDXW&)^6?8UjhB8)JN5_>N3GJLZci<;UTI^<3F>?HpHOcva1=RekBAo8XXH?u zRuWWKE()HAU3QOi7?f>%rnrg<=)mq71b-x-+`t%*oPMy_Tt!)4X!^)dqakIINl=gg zmEPNG#y&))Nj4m3KMazyI1CwM&vOeE-K_irX-V5#JJczRPw zr`BS2BfE;J_b9y+vz7xl2LwQDD$lbYB9VACQWn#Fi9lT=8)M_(eABAKt;iX9R;eqZ zW8akrBkP4a;Jurnuv>X}Z<(2*n;v@}kxp7{a*)zcl6g`tZr5oE4-K1Y2O`-xVh?!J z;7wu|saxvuj$=C?6!S2Y;+Uv4Uy>>s&4aJdJw(q^&8|hgEK&6AnzR0td+|iJHK~NV zQ6OtsW|QT8u$SQ|v0jY+3F4T$rq4rCz=@Ww!3r?=gp_`v6Hk^{bvb9`-cUW+wC~nYIzc`i{r{T7jh zR(HYp!%?`XollNLPEIZU#Y=(JF^(aI*mRDMG%JA_$%+e!S}&gxZcBc~I!n*6TGKGz zu@*>c3|}=c4nUg5PW!UaFJc7yHF7E@(q63BF&o05P3+tEfk{Y*VB8Tup|%UI$t;RK zMd-{CqS9OCZ{m2)1u3lkX;;S(nxSS&(`&x8U7*D53jQ8>~r^>Um zGaiTQ)A@6v$3)Y`MztDb*_BpB&u+>NSQ^uk^O-fNtkyKk)mNEak4x+qy8S0HymtCl zEj6d(*&(N;Zk;>}&)R_Vq>DV-t z3~ld1Pna&iTcyKAfrZCoKlM?AI)`=vT9}eyU0ssNsM-T&@-TwlNa-?*@iQu}x+Gn3 zEBqH#6HmhL8wL(D#66e9E@p=@P&rp67LzbhB;!?U44D>9l@MKPRi=JVw`r@hxhx$8 zInjsh^ocYqbMkPciO0+&?=`8Q70)Qc!;7NldWx%lW$tC|9^*H8=SXu$8M%Tq?kbuvZf6RIz|)LNgN@T; zj}`4|bLOTSX%#`P#!MF@3+1rhf}4~*+Vucw)Yg285H_NIwLS+!_fA?=K2wxk|?{lmiDc;G##(Q0JA?&@1e-rBx!xQ zYFa%d2~rADF`N2&)?sprQt=7o^uqX3@MuqS7O7$Dehs$dYIIhL-TY$jp$Ocj%*u>q zucxOjGh3)d$XSf3UML0TL&hokH*e33kC8OVx246eZpv^re5KJR65iC3X}wxcLXtH` zP4vYX87UmSL2LDJ=mpoJuGjOH(IVlv z$5Nn=GW(Rt7PR^ZJJ`g%yDy93{id~W-)M*7hV6XV@l%6nU_C7rG5x5Jy1*HT2cG-- zDE$ocG2y#_YaJVN+P6_T%;quVY~>>?602CJt4C*wUlt$BzX_>UT&8O=E-59DWSdNQ z+^(LY8#+#4%a>c;BRGE_qvXlDHJ*)5AnnjVas-`F0HIa>&#Tv?u@2m_$M z?|qS{*h@<}+62Oy*>{G$V?es?kF>sOt2>(bv4e(SEk<_{x0}cZ4C=)wgCFn%LcKgA zxKj!UEt7*%k2WO|40z0MdyToJtnf5rmA1A> z*CID3Xtnp*^fY0;uELYm-wld;+74--Wa^Dx%x(h!Nnv$<!uo)Yv}jSWY&cAo?*Pq=Te4oS2R^wZ-N>=&G7N`41~o3@Be<(QUshn-MLNJp zd~H(SHR96fLywa1DT3s9{l%!UUBQzNtNTXLY9Ng(?A1~Aqu+aB~lv@V%QL6^po^&TgsjuWm(h1ZN^0zYc&(G>xbiWxng;`-U zRDGIo=8jcnNsaaV19c2XG>wy#`lfqH^lA~&S)o11KxPD;_Er47?JOrl7=ttgww37> zv{#m)#WTr76%eGWowohvfNL0bd5X}4LZC9;G7S*dTv4VcG6pjur<3vm9_NKd&#OKb zCf1d1U{!+%5t11mG5JwuX(M(Aaxg!EDoZUFPxnS|esR%}gyCzAVP3arP@Y;d=4^&y zxd3c8a5fgw=WU_Ial$UOT28M`A+`Mklo!FDRZV^E1LqVDK*mA<_uo@}S++Bkbo4b1 zjy}(_sYz!_JeEc)(?WL~*MlTG4?#DkXkM|^V5RzWMPIuA)751QBwZt$60zJQbyQ=> z^?Hl4COdE;eQ~e+GE5?O|B}#ou&w{0SNO63QTIsrC{YxKC448Okg-faJbGVjA~p@7 z%U{#pWu&tZ9i;0MD9m|Qjq4t$++#oDi5E zidulH+Bt^g8(5QcqH$>kI<)NN&S=DeSHOt)xXvJ|@^XpCEL7bRlz1_*+Iaw6X>L z_Jp;5lT7+0*Te?5Z0@q=s@hYl$$M8^Ubc7T_dT-B-`$=r!oMNar;*%EY-R zu<~};tH5^PLt5?>BYUI~xw62E(TnQA-YAj+($n?KBHX!iMqE&a!Q_ZzYioO`bIYr) zCc~JAmwq7?@6Q^-8sV_594YAcH=e$40P9-Q*eXhT>dysRQhUB}19RKlmP!k+B|q_h zXq_#&XfTk7PW5siTMbxVs&){CMe?bX@2#^{?oOWFwZpeZEm*tL7&6nxhU68%V-7XN zn&@ZAFp(+_d?ox}$GB=Y1{YHUUx&gEeO;6cm8TZ0=g-=*O++rHd=~cEb*Cr^27QZz zZx;z$&?@AmkL1x)YWFQv+5@kguB4+6+Xqh#Pelt=8eN++%lXxL^_%K;-Qk)?hq>bL za|tJF8=Xw!<2(2EM>{g)_L*r;V_}`M+>QjqWb0(5DM5=gYgZE?9Zglsy=JnLv$yat+xaE`d$t9hNhUa#KJ0d$9 zYaeW%FnSHn-Qz#e_;RS)sNZP3)HBOf$8#}v$X3Aw*J)G!x@YVNm}m1sBhRnV5W@#M zjL$sX(Wg6VtMlR^E6vtAu_;p#z|wA=w>enVza%;*Bm^jY=)1nqG0r9~rN^P->=>E^ z*ux;5cCZ5~kesLMC@xMv-&wzEOV#X<3A4Cgi%9iqrEif`;)}i3{q==M@Av$rJSRsg z8YRXBCdCJGnzAS&#swf^>Y)-9!A_fR1LXS;>=djeA4b>M(jUpHq7vQm-3}b%o;Jzz zz@F(4gY+qyrkL@i=+sq7&m{z|=U(&)5rVI4{6}5!0E)J`4<=}n8Az9>y&*HhwuK|~hhr?8N;(@>cU)E;bGa1t$LSGhK( z_FroEN3ZtEVlL)eOP!gsjo9J56-L7Us5n#}KZo@ABHCu$uCfvJxL?dn24p5T%68(msBafgV&%?Kn=_l&7OXKc|B1JUfpNi) zUU`Hd22$v$Sm+uiCPQ3u@Bn}F(enF4Mego<$802i<|-X0$-z9*{6j^lO1j;zT1nSk zCQ=c3%=O8RMFj;}r-vUU68QGy4=W8y!7xRFN|mR!m3WlX9t%9mwNL(l$Nenrxgh0& zhJTw+k?E(3E_Dix;W?95YnB+Ii5`ntO6FN3(#AI6l`=u(wB9{%S~?xt=VkZau!mM9 z3r{4usDnK;Tm!8_9_Gft6`k#;UAuP8UJl`|j5Ay>-7Z+hEP`HWEi96ER#8wRXfw}p zri`*=w=k2d31e$QE?)&;=hM3CY<~UBe5e}_>l^(j5B9L|tb;*D&rb~JG{0J)D#S9p z$?JHbxfkC4(b22gK2hgTSTCt>l$iO#VJi~^aenW+*3BLac&(W4zPP}0D*g|*re~<8FYivpkA=; zf}9g224^Q*kftN(B6CR6qd>Z4bmY=X8I#FzVL8r{>QS6#)o>mkQ55t!q|eJggYw`j z$`rvV{dE=mnFl0nf@4=8>TRWO(@}}}p!>d&y$ z(E&GwMfHt}z}fIkheC|4bH>z9y%Ot5%y0G5ku*lZtNN50xWhtdvUtxcl@rM7R-d9B zbsD+D08l(q{apFli5sHdt1VNQK762JOp~q+dNMXW@sy-2iB8=JZHs!_po#jH>#LpA z{xb_VIc3$i!SzwwZI+!z0nHjMzWM3mBimMJ-;B|mmM8f~4wEjNJ~%Xx#`*@u#vZud zOrMtXfQ7*Q^dH&f|F}UT_dtz4_3JHDPWq{k2S-63s2hd2YO^SaqQ= zaXpeC#(p33^rLkoq_o=R)L_xpc+8`7UzBB#K_|`T#3~lof<>+?WYddaCDeF3-*EU! z_H#bI8OjFGGpPikcy?a=j>tx+_{C{&VK{?WE{C{@VI;Z$YLM|1k6%GVb5mqjLcI7B z-@bII{>Vl-Ox4D(Dc5P5=_J~^g>*|CAA^7um7{t)408VY zT?A9C^@_)JjSCdrQuo0z6weIja&J4S_&xmB~p@1B*A6NW(@KvjN2cmy>rO~Nm7 zykY!&L6IxV&G1nS_Uu%hZ@lJyacwsjwfu+9mr9u;u=b&E51S3LY}s?3e00xk*FC-uYFGBuHq2NvWnYsa{fi&5*De; z=TQ-X7pAIV(x3A&9xr2jJag9y<6O6`4rw?|jHd3>8J_2=bZG%jkJ9pn#5T_3eHjVOCT);hXW1t@KIe_(WW9NkCpg;O32$@2 zPESsqd%IgBwc9ze)qz?kVlMg6DXx&?%#j3%EY!+uKz)10rh@L9<-4_`vj;t!2Vf($ z<7FMV@{exlJehi{t26^?tUmd@5kP|aL)?mtriaLDMmAss9~dr6l5JKl%?z}5wXYJ@ zMr*3+8(qthnew0>zK`Yopq??^Ov+Z2R%;==uAB`=)`YtHNE@J~fu{sfICs@&xuLCu#7r+PfD3p*ZTkQH`@>aWZ1 zxnx+Qs-q)y3W?%$r%#LH?Y~qX^c4)uHC1ubqDP%|rNLfe7snPS`LXgVvy19fyf#e; zQbO9Yuzr}io*JHXjPNqcS0!w0q7)wa8BN<$Krkm4qCV;7GmR91l$MXdQ$T#)@Gk=N zGWu1YEm~aEyM@COwpW#Kkos~Y)2OHoM?W3D9U^8ilf=xD_nznV+JCR-XXmm(RTK8m zr%<N1~}yg8uxB+!oT%B8+w_Kqw`Cq4Kx@P(Pm>sV&Q#<9v8XCTkXYwJcUbXsXU-{3-K6063VR#irtesbqfdh_0G8vhLJQ%UpG!~Vf zgw3EDj5zEmXg>0fR(?xV9xduF{IUZ#C$naZgKU3(M!>Zf4uQ^fQOvOY4Pfhn>k)GSr$> zx^=(RWp41`$*UjrulMx?d=i=A+#8n^a>4q*tLZ+lwNSooV|aMTgiLLrLUx9+kL_kV z*jFH##G?9TI0WCk#cN3Jyv&MkvD4dO#;99d1CkgG)Gf8z>PKZ4J-CZ*TJ+Etvl;Qo34Tc$K3I&cGmIsJ3bGy07r3TQ zM~uQ>BVO)|2|aB6+F0$$%18nN!FEK?fdfm!s8^%~hBk24*B)Ly(k$d`(#G(Wkwu3bog)+FouRFE;K{#&>8tr97(b<&@9JaKsrgL^UD{jzWkXWP-XgveqI^APn4rTbkr zD8k?WTJ;Lcd#|jY@d3h^p;~In238A!lyS}G!JsyUjb}o1x=-PGfGEG4#GcGUzhc61 z&e;e%nr07j)fZmQ4L0%D75K`+!8T*TT*GR)q~gp)Ex|J9+8HhNS(MZ&Rk( zoPxbF?Z#OTI>|H^Zt@*O4-1uSl;O3gU!M^dzgP3>)4|vfXx_FM_^>W=;Sp-i3V){2 zP%bKopXu;Uy$1VMl+Ix7ag%JicA9zVc7HldM%CP!kI1uhnT$grUW)3^A{Q4Y*Zsrt z@@M>4a=@BzGLRbm3hZlkPIR!lNUCOBQtvhGwh>VUrXrkt1^P>KGj8kdVy}hyMqGA&AtR1~4L+lN6U+N~C7Mkdo;lu}fDXyCe#*@O*oo}~2SerM7NULu0&Ed~zYoHkB^o<^t%;3X zZ)fAT_%<9b8-gom<4TRpabS4{r)Z{WilfMJ#BlV&1B!IEqmx!lJ{p+z=~~^`9*4uj zfBBRmlQVV`?83t1G>1URgj$CjytDvA03otLe-~QUVlqS=9;Xp7*#IN9`K6cNsi#K3 zqA8hqb;a7KriNx+t4y=|bD`4?uiEL%a(zlopNZvO(!MR^vO3x9>hGrL~%>GJSa zsgPQK_;E4~o(gYa5?L^??4ru;>nAFBzR`dz_rBc4wyofJy~&}=Jv+EFm9O<+?@h?U zet`?d81WN#3G8FS6B8X+6_bc3?^I%uJ>q;_3E;lefmU(^{n#J(F%(wlaUxmEJEc9% z?)U7GT{s(TOC^?&>hZ~Qe49FW!Y%uC23DUG7uN)m)Nb#UXZLAB}C{KfhFR-nX=*I90xJf-JloU{~-WSna=c-gNjjQFs zXPCE+DL+aS7?8(;d8R2bXOA3*TiPF+GK!OR22*Hon38>DFo2q_jr+n5FMpu7b#9_+ zKUiNqiw07Gxvm^nXa{mvV70Z$NJA=q2ej~SKMF})Dj_`lhH(npAB_UGH~W#m1%Q^I zxnU?=zjMUYJRQK$Y%hqtix}A1)XB=h1#*}D1+6O>+d*4|RmD~1ROv@4A_DOM=rXdg02o2+EC3)23paq1okbr?wv* zO`MEPZNM(JU~`vWWv4sd%x26GdoZ&zvx}t@7|iVA4x#-qMrb-47;u*ory?NmYxdkf zXRpi7!UFiOejrXxCN99=|2e-y*f>E5R?li7aJ4jornOe96%-@fE{>;@q?NJ#17yFa{O%n3Hwg_lRGQN9R)i(l-p1K zcl~{rJLqnR-@AuaIe>Spe+>9;a{ny-uK!@h#>U3P_LK3CGAqknC*KA0y}KV2KcV0I z`T^l$Wra@m2bbU2?B6k1xq;BBuyR9r>;IBFRDu3VP5iBP&{cOearv%=cR=WJix@kD z@AT^r<@--1y;EWp=qg(|IlJ6x2!IW$H*&^5D?lJSfLYVZ%*E0faJS%pTYn(%_c*@y z15K5b;DYM!Pc&vFCy42tzUeYUlPUqsYG4l+{om>X1pOxeF%2z66Kk+3bi9AfL;~Vu z2c5`|4FZHJI{?JO#LWdw#f08CSOL&Ia)UW`$8HMqoB2rNz)e?>})ygI2wh;u$fpC2+?o3md zOfa^6IFWyzH^93*)1OJH|1uH4|7s>bk1HVCZ}R`?5;y)s;^-wzc|CN|Lj#Uq82^ZdK_-AXp25oa7a9LD-T!_d{#WM+=nn(*KT5_@|CI3# zR_32F2L1oDj-daRb^L!ukG1|O<7=#}f06NTdx*kpZ0lfY40WENRvzl=3IkZ6?i;hI z@%KA)WBq7n{d+sqjsD)w3izx2&j0$09_smgZ)gAAe&-$j)z0|~0b2Nr19Sv`q5K#C z@UHQ<-vjz>g;AJ)7zHI`7wE?#_9)Ch3qSmsyQ-DF%l8ThifUzRWpDAT_QNNFT9@wu z12mM-M`6~2`uz?7Zf+E2?e8Tvl>hL3zK`*ji^Rgj4q#+q;^N?7fqKk;E8H187AUhH zAH4veYW%N$5Ohj^ear&&ew0|)U`#@D)I!;qVDQ!9QT}DEep}N2tJD5##^2}v_o@G% znf7lF;QR6dSy%zzH9+NOl?`wwe}Ky0HRQJm{A(T9SpGK_A~dO0p^F8r=S(2f0tts# zA*}=%`1TNu238~u6Q?}xxZKXAnb&nQ`Ml~lI(4F*9@B@hbxCOn;`mZ)*%WIe>+@0* zf*YoD$d|P(W6wr5r0$aYNeKP%R8l5S5w;&&(|IN!66^9)%W)^adO2#LMEXqH_A`!` zBb)_BU{{2OeE%$c=N4KVvqjKGCPhf3dZHe<23(aLW%MD++Kdv82%An_BiLO-ffYql zZPpM4i;c*pq2GEq=6RggvVhml>bwv#(UQ8sj~9ISFp;Nke3K-NOESI!tY%-^cC2`wZ`pcz11IHD7tovlr^- zy3ILJkdg`V7!xz}mhmHl4;V|{^bHxyp-4XJyyql^5jGm6b_W6@2LkBlMQ#WDDzuX& z?e2JfJ{e^+=PzqIIcwVEq=e@xFD*mkxaAH;nF!CkKu}OFGVi$tB-9Y{@HvLfwK zheiGoO5K6QJzOMJU7(+s0YGi|#^1I}r36xL;*f0Ps7-9h?mS{1ryp zu|s3DUpO3q?{-Y(M^|@eq&(Qn%J_Rc2gQLtHsEFlaIt})=Z&Zn#Nj)f4H{Q++=ac| z0CrZky9WSVTudM!6h+v{<-fpCGIqLqDDr*0f7?*MP4O?OOFKY?{_}_fa&U0`*N5DY zwk%|Y1GD8y6B7<)mxNLl_mnOH22sZab+>S8h{Q6rHuOV+MnYnJ^w&CfDSkN_HFAqY zai*hZ$ByTY=5NT>imQz_GoGubSWIcGns8QsXfUfD9#E6@fas zzk2i8npUn8Z6GZ0)%&k-Oc+4cXCn`zOOL#`tOgv6Gfb$SJBDODJF z2g0t>Ro1D=_jE*^G^bwdF3A@c@E#GeIEhedxwfKtNzIDRWN6!RSV6x})H5Gyvddo> zPVv@U?==||mP!!*074E!8_WYMdakyGt~JNY>djzzS%(&1ABOAMm{_5D9XV>9kVE=p z`vs!CM*&g-526iLhdT1iGP_@G>H6}H9Z8O#RjLol7Q}wCp-f@~Nq@(X&ya=^TqRS^ zj@(~Kqts9KR3Ady2pOMvi*3k5Z~d5nY?`9*Qp%03CHmRCb%v{dZZxak8cI|AeIwK1n=B^z-MWVdi!bpe{IFp0e9<9hs9C8j4 zWMg{yOs)+0x;z|BCe<)(!{4n{JA1lNP*VMYe@)U-RHYB+;$g*W*YeZHd>=@-n@ZHA z;4RII#{3&7_zTuzPqW(4ik8INGON@IrlrYNrISw1S6v(=fq^Gma1;`$rSD2goHLS3 z8_ZIV89u)SiR{WC3GdEF^TC@V@*u)xVXby?jgTh2lXG)=^Ju$LoQxtqQf-)hL4m>G zghI=a0kw?9Sp-@p!Dr_p%P_7u$!nv)emaG6JEWEjp`&coJVq0a+4 zbP|DF9grm@miO?|?~(O0-=wG$U(uceSd2Fn+fK|7-6=mkBZTwL;(hT^t%8o0Pq^b* z2COvoU@>noC%?#%OI^8Z9k($K&MVDGGWAu$d4EuBhViWQZks1CHs8TZ0})g>TmMK! z_W{VxR#-BJvwEhOR6b=CmLA&tsM1j>g}=Es6mB3ie#wn#Z~jx&6EhktPl;~6;mP>7a=i1OyKicUDok4`RP|6 zC;E^tgS?ER(b;XUDoFx-=Ysf4vVCpY z_T1RI^wrt-Q$NYJzUKnP*EAOjS!Y>kYU(DZg@f`US zNvfb&LeG{&;7u3kFzGL(qV75$QMPkpAlhFXC3c#%|GDzBi|^FD_VC9;b73QRaWOqH zIH@w0`yU^rUtDop^4;2-Fsz(F^w2~#@P2c42m&iB|KJv$=w*NdSUvm-EYQTsH zJ{^d5+emxZ@Pyh~#Nw&mW+O>FjSI3Sc`GKnN;w6V+gs+xQbr>Uw<8$DUyT;d`NfC5 z9akGX^xtk}DSSG7;+Bq&5`vERg$9W#URZiTSMFLf;(ohXeJa!ZvoQomX3o(yQB#v= z4|}6x;9bB`PV0xb7@)I$?huLHa`?3 zKUmHG7iR)$KYu(7{AJaYj4i;)CdAmm}RZZoB_H3&=1?J|2K;W`uzmp7qO@@ z^d$ns;?6CFa{0rCvfi2NU)_m9KV!dp!SMuYM**BnASNISEtJp+V&-ZJ{p$Tc!QYXs zAogN+Zpsre9uNzNg@qjm0V83YZU{KfQ?)r=0T?Oi!-(CL!gStI` zz@YN{4*S(F3pXd{uReakprPTNd-orBKo+*Y#_}7Fo%NsmfGp5J?GIXR=&PcC^5f?G z0}lv-I#d6^gG&ETJPzoy|9|3f-noBw*Ke8Lt>2$84yc;_0}uLs<_{R#UHJ14JWk*r zashF1|G|$H$o5CStgPIBjE@!i0_LB6K;NYN!H?rE8vDmsIDqVbgSj{vL*INkq44pc zFsoX5f$#K>Sp@=t`j}8jLEU9$X?t@B;QQvd3*n$av9XiOcU=W?fVhAtR8-=M5-9&4 DN|&YW literal 0 HcmV?d00001 diff --git a/docs/src/assets/loads_delta_3ph.svg b/docs/src/assets/loads_delta_3ph.svg new file mode 100644 index 000000000..96324c607 --- /dev/null +++ b/docs/src/assets/loads_delta_3ph.svg @@ -0,0 +1,183 @@ + +image/svg+xmlsd[2] +sd[1] +sd[3] +1 +2 +3 + \ No newline at end of file diff --git a/docs/src/assets/loads_wye_1ph.pdf b/docs/src/assets/loads_wye_1ph.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2a3b516199189eefe45441c01716c3306bfdf731 GIT binary patch literal 16498 zcmeIZWmKF?voMN#f?I+N5L{<)hv4q+?mk#>4<0-~kYFLWy9NmoAc5eP1PktNcOc2m zK0Etc?^)-ryS^Xi&RQ@}S65Y6b=T8X)l*HWC@RhfWMV_%1h4=cjI9BDe9W?D_7-4E zAOOh5!2)EcT)@s| zMs_G3GtsW_syMb|9Au31%BX>023p#AT4ESfh6aH&toY(Q02xhXULM$8 zG6n4Y{jg=^vUT@1>+sfMWV+R&EVa>Mr_nq)T1z8KWB8#tta_+0W{Z#jG_rr-gi1D) zkdV-G1tB4ftmq-AKu3hz(q0Qzn67y;mcs5^kfMK(@N?b_awI20AvkpZAEa4;}#Q^?SmpcUafpKd{EdqcSr;B3W=6hOHg2Z#_6VY*LVZ6bZCSb>A; z>FM#fX@FhU?Qnd~h=lSHhK#6_$(?g1@D=n0iw+XO*F#b=t~?@4Bn@O=UuI@zfftdy zq-Z;Uu723PKmZZ6Lg$WQ1NGFyy=Fsyjz0J+4KwdETPRqG=a&Z0d|vXuLxn?x-N6~g z&|&#}J=_IrjTj2M!-V*~*a`-$+xf9Qiv1S!1^Vim0}4R8m*wc^b^8!5;Km7Idgh(3 zuCO90(r0KxbSDH@a3cFT#sU%v6q5DnEXMNI!{$Ro&p^O1Ae}czFf|Bj!1)=}N4)^Q zlr?+Qumpt`o>8hk+N@ zj8h`nZYS-L6^D*1oWW1$_&Tg3{L<&%?%kHUKv6)E!@$58e|rj5@fd2$ZnN)}wb&@qC$ic--|4?_f1iMV+E<+3q|uM-Mx4wmT4X?wtJ;$DD|KXEr6bOki8 zQ1;nvjPDzS*;~p*cG8FY2rp6RoY0Xl&aThbj<#a--B=2%N7{#Ok5U$ecHgE3qMRDw z-Ujs#cC|qJw!DLZ(ig^rhT7xo@7{R`eM=~g4|mQReY;YOP#Xr_eLHZnlX{a>xwZ)R z<;;tV3xXi?*zLVGi~rK%lX?sc%!<}am>WNp>)65D=&$X$SM8EFi^#NIT)ejd+&5ot z5z(9jxGt~k93`~TyXT=SJ9m1aE*1-VzqE&{quL_wJDxkJK7ej|498slsEs|}h_mX${R2zD4p?0;sqWM#mYm*M~j(g%2Y``wnnqawV z;LN)%-`JPg&uO6G!h*c_cGJA6!=bMH-K#ATPi{nvp#({BLK|G5=aTTDD#AMV{oGZF zkiOIh1leYExX+G|>_5JeVLpnzDSvo_dY18|eO@r9g3JHbzVZ4oLIl*={4M70=&FQA z=S|;jTzx4bkuPVH-%D4G&+W^CUNC4U2(#Z5VmfUpyRtMwI5XAyK6&n!oyee9mp*F$ zx_q1-%;~9%@iose*&=kfOJP~LNk_^&If=`+UmmD;X*aI1B+C9|-KX)Wz3SOD*zy?l zrW>I4Ug#!y?u{D)-!pyKY4%$iPQP3X>JQJ395!sZ?MY(8!H0b#EsAstSu=5Ct#KyB z&umt59>Hn!k+$rII^mItQP?(eXyvjoOd;tb=rUFLIDCsUpzboAhOL*~C~w?{ty>B~ zx%z+yXOJ0tG3?8R0d^=b%V*Cu>bMe{;63V7@TE}8cs5twU|3-Cu35jxpEmskwOwdR zu!9IoF3|xSp7yNz`ZVu3ZbN(m43fp0O@M(^5yRX3ioojciphSJ5tI_DDBJ;r9Bng$ zBOI9owNru$xmj4Z`|EftPc1hZaCIlYxod56%+8N>dOb>=aGcPat)9a&)IG|(Ms5wl ze<9QViO<|zn>>i@0c!a7E=vxCVx<>*vYrKeZSP>jB+o-g`V9D>m{w4cUh?-^i|pnv z;`Z+gdJrmVSC?PA8U=&rrX#nsrtDr8o|F#N*IZwFYfF$(ZhnxAnM2D@#&2{%-g*|V z_9ZQj<;m^MQ(D|C<8br@^KbEe-NMv10TpS|vl|{-?onceBZn^gr{2xt`Jo2-;VqLU z`qMEh+p~qp>+$H3Lg86cb_^NbX8;GOb8K^Po52Py{Mg26OE^IMChEmibfltg)X*KgeTFzDz?cZ}E6x9bvl#pP;?{NH9O=iaJY<2CwFm zI^3Xg>zUeP2F>w2m)-z3!35J2N}bgiwpEehf*tMLpmf9o@CAZ}C%O;*$C+NgBSuy~ zQf>2N7p7VoEb(#ZCt}}5ff)vZ#v}$0eZAP2hcNM7Oh{EYExuD~@QWFw0!N`U7k4&V zsc$jfNJ;uXE%4L9ePQq}llzTUhny2{^3l$DyhHL9yvoxIaB8#II8hJt+vn(`ctZu> zOAaFxavpH*BTSZws#N)T6fbWG^9p`!jee~ckXRi}*=|BY`fd_OG`Eb+?=<~t zfqPis-4mQiw;(n=d^;`c#EhI`IonT&ViEwzHG&Q z=XOZ43Ppacg3Bs(2KN+1;d^*XkRC3Ms7{Gl$_H8tva-H@@0CAI<7)U}wk0 zB~)m#D$ODa&$m~?)I44_Onu|c|7@$2o)V47j4NIo?bX{HIZSNnh=R#U_zreHf58x@ z#Yc46M#2lqYQ^Nz#i60F^4^vN@9kmi2&WlV=T8C|A3d4~b`9oF9bzq!AAfW}cOsBv zpyXCE-7A3c(&EgqFyFT<$&PNXlCyJ60h~$Y`Qcm11Apidw@u$ca#4Dph}2hRdtItQ z4&p)P)#=qCVcPi;UFbNahE(MTWyo1^ofc_9G~z-0RdR|FhEp`9vZ`_^N#o}SYd!CI z7M^UtADX9?VmcbQZ)vT;>S{+X>`MgNKJ4dMX;XT$(OR?4M^YhF8XHLSXpuX%`{-36 zrcznGK?4f%tPZju+I)oHCUd4=;Ir~LS+hZ@L!U3;PsWR6kCiUIOZ6WNLVw<;#F;f5 zET*+8jbnc=Kl}7G6Ggk9^qQ%yP+f=S$8@Wu%E#3O^RZQCJX7jlP(D5vER)gKt~)q0 z4$}fu_ORivdJQ+Z2zbm8eh8(z=J+hPUdvTabA}dbn~s>Mr6;K5awWf28ArccRU9S9 zs091uU4~?B7vuT?CjSGw%rsaQ8f?6 ziL-3!=IP@S?t;Phvj~d;Tq^)uK9I%oIbyQ9Ybt236`A53&cyS=-<4umCiGkSXRy$u-6?ZXC>s&aP@%$ilI3|lcifm+ zvqjs=bsU|f7t7aVqCVNQ9p?L-#DhtZsh`vKiG+Gu)B8q`D`R3?sEq~B@kz(0J7((y z^F&@`OT1m8q}lvTByIJj)PDVBWwRwQA$NU0N){#U^6RTp%huHoVAqy3pDNNJ4{JyE zEhU#sq2~x+LX!^|1>w*^=UuQPtMftX@K^6MXbiZ=13%f4fxSuFL=K5|FY>kE6o+$h zQe79-?EOb{&suMSv!gXY^^8zT@$JPAe1(=I%cpeMWj+qa4}TzNI1rB4`7lntIGCRN z0W1Z-=&UaKw#31lB0N4yVAi@oKrMe2>~w3vf|xt!v!;FUS}*(fg9&;Y1^UkXid1qW z1!2EWCIGU*lW}-Yr5v0SIecIW;M{qgXQEeQ+ACzX8!#UiUE}HB6+O6l_Q-)E%Ya=a zPehAhgyaHYG!M7&8A%<6IZ(7sUNF^?c{OM-vfVI0cp{Z9kq(Mk5h*WYt847rd&?mC z86}F!N4Vd)yW)4#IVIv>i6k;}RUlJ+tBEy6`9z72hq`4iP^Z6>O`ROCJLJONE9r`} zTEI}B5SBmzoKHvX$+uR;Lc&cfp@{Nb>+HGgQSYGuV$2{Tmh_K#?Hi18Vg}LTLxOZLco0vPb z&iDM9QCb%=3jl&D;{N008Gmfv;#2Jjqw}~6TZF8d$86nL15S-u1+jMNU8tb4CRxAQ zgx!}-0kDtgslV^ycW27K6kpdTS~{iAeN4AvM>^lVjcyan9*b4nTd6VLmz}=ee}Y{s z#{`&KD4#1Z=SatQiFi%o(>ma^6=ptPo_B3EAOLz`eQo>Et-~G${ZXpIT9~%53d=aV z%UdKzZe_Fm;t7{F^tK-6(og1lV~S;|Sqt^xfr?a{5PJmWZB3ixcE3>G2LqfR#L68` zn81h4{Pq5-)VZISmQ=S{B&p)*f`oQneytS;dUm>OmV93 zqVrcTNWg7N#3Dg(!} z{8c>076+F%#{q=nv7Ah2O-gUG8n-WbeS9MLyI$gz7A^Y>KFg`1h=tGch++F?Y+_5o ze)A~SnpGIF)xuYnH*GgCi-%Zj=?i;*R8XuJRb5e(1>I)uEyEsw{4quA(HtR_;K`v0 z7Kw^=GIe#wxwu`Fv-9`s>GQ~WtbETGp5Vgk)UtZV3Tx4a`L|Rt`)akzH7H92@Zq%F zkuR<)`<6eA&rIhO!4K{sx@@r;TKiqZls-R6&Nf$R6M>5b^jFn(}e*yy9gvf(m_ zrgFZ?k~+*t$PPmv;hhUfGRdyJQoLe;$RezWC~F6zkFD@338XT(@Lwiz3p2ad@ga zmbfifx@s6>him`!Q;1AF0^}mqm*$$2ZW99iI_lM83F8_*iFB@!<1ZV$(6^Aqyktq@^NFM#Ds5H6ajXSl zk_Ld6XKYL|L})H5E!AeeyRETFkE~gj;gw%qJI1Bt>@sMJs21V@9;UChJq@UVAF3^MY zncq%&u41W$@TGe`AK?iPdXj0b;pmM;9?yhZrrvM+0x#pKAEF{TU7mP_02QDBR# z;yHt^rc(Y{-+s?2=@o~Qyh}ad$H{PH*fBah@-HW}YMTiM%PjCuWk|iwK2*FzSFwmy z=G)tbwVj`75YzUCNiDa?iZ<8#n8EIVO?Z%)<0rQ4SSI{L72Tz6Lo~a9d!VF)hfI(# zF}iJ`;eoMAzxUCUrqF(oq$o2AHOZTQhHWXcyIc%WV`+zR2SX zfKCLiSJZo$TN8!_G-TO~rBppn<@};)z3J@S&$Ud>8+Cg`x}_;$*GkN>G(DuEzQjHz zYtVYWt2guB0K-=anIS}6M~`j*uEE%)e42u|n^=pGDPs0hR{xGwhK_>fO`*mot~i`? z|BT)7#^aTRh(~jF3T2vo`b^-3M4WH?zy?>h{Ot-WONUFuRN7a|)Is0MvBQJUXnDn7 zy@7vO?f--uZvC23f0>>HWjG%J1JvH!>0~7|=bhKLgs0(`A+Adi$MFisEL#Z%WTdz6 z{9XQ{1lznV_$#r@+63Xr!dm7di4D6Y7c9&n@;vQoJ30S$3KP~%8-pH7=dOSW4SfUI zrN;C!!8EC(-O^lygIU&q#fLVpf~Y!&z!~2sNGz%d(64rV=FD@5(!&=DeP@lhDW&TN zZ-*_uk3Oo&l2M-$BQVilAQdsbUa~14Z9Xm4N7t^X6OIC(y7){2!qT!Gt*iXk_;EMq zt5SAN)M00WTEZt!q6AI8=9NFqoik{|^moQQ$4ufC1k&&GN64*{MJBz~@oVW?9w_EM zD%`xLT+IxvMeAs4dRaQo!|vdIqEpX%AQiNB%1oS$r@`ESMf<+&_!(Ygt=$$y!<)zL z!y)ZKm0x-k@Cd)LUpyq{w&ghrP}greC{!ONluogx;B4GWEfw)4>TdXA-TmE}s}a}N znEb5dILWbcW45g&YQL*KnK8&d%}`Tq^>J55ZpY;t`0(%`W6e|%0lVmlQ;~^OEgcqk0QO~q9_Y=L-Z6a=HTc*llU{ji5lBQh$ zHmhrDPhxBGRXJ+{pTas5WXAA5f3ai6OF*t~@B90_xzG!r9dw&diyQ4<;^pF`Zl%YE z^~`XOOGf<+4W%!>ePxqNHJ>SU^!v1Pk$_eMeUh*}R(A+6v*xA19RiZS(Wry!N!t>>>&Z2vj2Oqk~;yo-_;IdYE0Lp#WU+=$zwn`ZWm~- z&G9@29Ww5c)}_e3@n@g-dRy2sE>x&m|rUZLVswOGtgI;|(0ot#@* z&z|aW#4n(4>VuGpf$OKzfUMaUJF?Z|WSn1BTNCRtLTs?(g9+VsMP1hO*TD6NEkHHr^ zvBB$Us7f0#;yelcK|`|RuDs9%icGJS$9C}x%@f@d8Og$ zm65Q|Iht7wMT`!$u&{Y9Z`6x=eGW_mSQRk_qtyJ*1`aGJCJ#I-QCN77Tu)<>9(cr# z5|vI+$I`w)s*{LmV#BK)K;EW|+TO%Q%E_}bh)wmYD$bB>QXZ*!c`n+cDeWg|*32)4 z?d>YR6?;v*f6VPzNFicR?29d;HNmIqCNf*RGd+;oxf~ngu1O4hVa#^Ap+y#qIPN^X>l6pOa#%#xW%=m)txwq&8g@nXv6_xDj z)!2u~9cw-hh47DS&vv{e=e&p$-jZGU7c+|nrMP)dGzj^ran_Z~K79L*BsAygkglTN zqpMHFHPl?d!Xo;Kr+UK;CQZh3Lpu3$d%s3 z64`6H@;%7h+D2Fy3|Hp?y>x}z)2){dT1(>dOg8Iyjy8SqJyTd7$xp$wZdvQ|-lra)D9b&x1(;i`kjF(Lmrh|}N+`Ij(J#O(*^ z7oZL^Zs6~b?;S14+-YDucZ_iajM4ys|AU`#Gk(J~;6{qFn3c_PXKi_lhYl}K9(E)Rh zVNt_fm5@n1xJK_%i~KrG&#)Scz22a8gSi(T*Rsk8;ltbgLVCw+K+LG387F2Lr^Bnz zF;-vQ`8d02k76qg{n{`5IC^=O8$%@CTCujRM)HW!fw(r5+WmCqCobBPYv)V-FUaD# z8JB3n8^>Qn+EBeVcEK?1mI&(PPvvq?*A1}wzVOW&+_v{n9IYM7#N9_$7!|8DcWjCv zt$MW9FI#qh54t(L0~K@df~tHpBcE(6DyYRc*NJ+)98?CHuGrc6;=Z$ zwA6lMS${*HnVSe;5LQH+(A&*P1>*R$=mwNoY&^q6znD(&S_U*^V_U;Bz@AQ4*xB1}JSWb~6*RCF3!V~7(iMd@p zUPdjAG-G1Cp>213qg8u(ed?rUm3x_SUiVo=edZ7|d`FZqa%ZxlIwr}4%U z5of`@)rkFBggEnZ?21hMuNQ9AJ-KyS1C-3&RQvm8LpO1f&QV@tykYluhmP#GG31*T zCxyPHr^F^r(N$Y(jMvM_8YgLBCjXLIt}Tsg6}&aD1)Ski@CC1l%BM_-RaQmbrd?&z zG-kSuB%jV9*$+ur!R>kVyMW2pb9!?R=T!2BojY7dG%eo@Ky&4(sTNa2Id`z0rX%f# zw&^mfMWQ^@d86RmXR-zoiy>eW-W2R}{+=OuSyJ0dQ-7r_YZEY0Eaqzz21h`3^s-#h zIx(2}=Fkq`9OODoP`mT3EP8_Pgq!Z0%?fa)RGfk{wIY?vU(HG887qxbMy2=5D6)6- zRtA>9PVK{UXJYZlCl>RWooG*oeFM2@o{0)`NKb`UcUGTijkY-MhTO2u=gLb3ol2ndeL%v) z?-3?l<5FXl4MzW*Jk)EPRuUZu2a`=xe~9)$UvLm(nvji$f*t{$m=NzNtTMWbLZIRQ4dE5fTAqJ!hNgx`eL-1{iD=4+uzoCIkhO^W)FzzYTmwcUWWof2Q{ixY2t)GAMn z%T;F<>`P70I(>$X;A3J>Sre5pMJWCT7e>%k8sTYlL=E0}xUuxS?RzR45z05wp0;_1 zq`C+qAWgI*;=zp5-onI;Q(8eE{j?CQ50|4lym0QRUpQBYgDG1bmDKx0!K3A!EWGwy zSG?gy>F;gI8u-WF00}QRB+j0^kYH99sanh-S$y$!I@h-GdXv3cep=4Jt~6!|Ni2Ua zwGo6BmKG`N7g>X~WHmugXd2A2?63Ozi&GvR4dbWCvgnLB< z_sn>E`SsO74^+2w;3s;&r_bRqy`_^atjx*#4*Mu^AASWf=-7)aEB@k9-gBf$_Qja+d%p$3>-|YCLJw=@m z&#}I9WXM|}miEB39P$fIe-I1QCzPH?i7|p}!$I=6(11#bS!*|r$0Pa@#dNRg(ecZQ zC~6ak4mTDCk!_Vo5Xg3KrEXMuATBw+uVQcAVlTp;SXmpiDYc+k0lB)5DlEreE_jR5#InI?i~xZL<@aT7#rWl zsS>n!xLtiW)daE1sfRLlP56)#3JniEryVvUE?LUPB4`(lfj$9gZ*$_8NqQ*v zNzxIjH#uQho#t;s*$&D>9L%tCy*d~tcM|njk?6tjz&3wPb%9*4?>Am)uCJ{vQ0xpYBIN)?1gZrG;}t=s(C^UV>j zi{D9~;p3qcK59(xOxC#42il`hf%vTF7sZjYj1z`EDU187^;`T$kJ|7oUc5=uoXkOO zanktYtMhDh;zovpat0xTJ}O<&C82y9u3#tocJPkss(+ zRMr!4zh%9tYF~Vv4|En;K_tXfk=6jixOD@?<}43S)`2qbA{qA8<6WU6_{bd!6{j~G zVaG~n65}5+hkIW{M$f+=aUt)At=sB*irf5nT3k|J@`#r_!b~dk>}+-Pt6Fyqvec!y zM#|{uM=J$aJ7nUkxUjBUsJ2|=0u(cQ(;qcW0LT-hTndHjciEJxr=uC90LsB0ENbRr z;%wyzcDO5ug0$t0>>xd=O3KS_BO42V5yZ{{ z0J5-f131}P^Z?AlE+%I7U;qa<1XIMwQPRxH!V=QtVg)eYjdIt=2)yf*0vp*{nF!lk z*qYrHXQ_bA?9?G-0A|gfG;FMp@+}cdBWD#eFo1^H*xAU$#tdw0W)A-K?0mC~u0BjzK;W0&+&}f!VP|0h{HH$iNua05Ah_Wy+4)Bfbn%5g`*&JN-BlmFd#_cIQ<6Y+j{NRtD2$NGoBcgp?q>391F zGd4ChCbpl9e>}6Y+zoO+G55p$p!f;BAL|E%i<=!#vDxo2Sh;}^Ram(py!C#~ zJH&MUs?+;zaFF$OH3r|?-yIOL&`*tA%|OVllEt zKN~@K-GO>XGkXPfb|DG12 zuAC9r*$VOs-AM&`fA0Q6FqF+)99*4E%v|oO8vjMg|C5maR?YZVy(1^b-wPXA6>0@zcn#5p&vaQ2@{x38Bw@Tyx$&CNuasU6F zSRer#vx(9DH)L1)(a-w#en`lC-_Hv8tN$+I`imYC>)iLV|L(tw2mk8l{DlB{_=^KX zg1=CH2mrk6{2l3letTgQ<{vIX(FhE=D`Ahq{PW>Q5OdeGvIpNcKoC?bTPu5uU#%Ze z5X9i_lLJWFpohY&35oF?0o>du%v$$PY$*RF;JKIaS6sxx#13F&VdCQ8V1dNQe|xxd ze=HDYKkj@0?yh+J%T~aO;_j1J|#wO6NeJN+v*D@x-tQakg=t87l#xPsB z3gEawB}K{s#kp8b&zw_qIPqPu({&P?IQ_!gBr{ws1xKK*Lrb`aj#Zo4YgHGQF~{(X zMw(07u(LK8cH)vY-cHyQotGqtC%&tSa&Q`X6LIr^Z{MF<;N6(%UXI%yKlyVV^~DDs zITU=aJ0oX0PoFD)B+=in+k?{-gc;aggv+wfRB!OoMJ8ErAl3{G8hSk`VZc>?DqeS* zsrXq<&32bjNvYh?kB7}rmIV0#XVX}2w6g#9>^g2ARWQcjs%QTaM*ie#;$JN9cYyHQ z@+{0ym_;C|4ukSK*H!Bk}Wr?SHcQ4Kz=bt^GJM*|_@XMjz?JnYWkh@lQ z?%*yHvjaiyUHy7y1px0U?%-?y;IC}Wjva9CqVMrJ0Qas$`NvRq8;b%fj!*iZ^y?`SyL>Pl^O0ye- zw;SWY+)$Eo`$lWt=EL+hOe!*ByM_=P7wsZN0{Q6ezn-fcuz^9#8vvXRcbppi6>44RbHJ% z7XX3*%K`fg_|h}wQS~rzTx4Bxve^w+{>p)m8lTULA^6hz!lJ<|=rrg}j>@13U8L#) zLoF3(Xz+1d>}dF7z#?iF1~OrjUQ5QDEG2ssiyh8Xeo6VqgNBu3`hJ>?$%F1wbH&rA zE@knqZ~2*35=Re=nwD3TD^7903%g2HTW6%umXb9V>qw|QFZeu2%!LrqLLj9!Z-Bs_ zS4w6#BxbGN^6c!ZHq#@|d9>Cx%PqOo-H6u+gtYKV#Q+{PL;OEY>k}lM-`irLAmD7S<*Xjm_ z3?3sv^4GelQa=a|WW{!Xb8SziZ?fKdNX0V^wiGdWzoIBuk4*|5D9Hmd2^Wb=J%}r^ zY>{~}TlcEAJa=46FJpq;Bdli4*77u5R$liC?b33Xm89UF2cP&-ot zOrAq3p!V!TB{SG0X&29#)UU@o?m-nX>FoY7i5~UQO4vAlA^d{+M)D_qi~OJy)vata zsn_z$65VMNJ~m&zCQM}b_eh7a`J3lO+|CoP!!vT0Ad0yu!lMRoy;nWl%0@=Pk>(cQ zA(~UN1-LZyMF7Q7aZOVrTWr&1>Ud(v(dl8k3rzPgyu6eQI~lU&f}kv+xLL>!Bfs*YJSm!kVg3HTBHai{6d`5YTvNO!pTBxkCLa#-ODSz~_TXfz@#cd~rnMw{ z_?Se8@GWlC(A+IgCe2dUmtoe-Nv!~)FB*PJfOMJ4wSurl?S$z;A;a zA$d~XNZ~{JS5q_#bG5wqCZC<;&*~Yr%yo&mAL+I2j?pQqO3n{by{-5Op$?lS0-#YO zUMq^f@d#u=>Rh{sIv%^6*m_H%m%5vt1Ap4z?enPT`eKr_-G)o8=1U!KhI|r3VJyPN z64JmN*A4`we)y1Q&?dUFI1ZqLZqnSSkx!C;-X6@UU_XOe_tjCgLBp?|z4n&txfy+7 z@M7D5I1FO0>HwFKRq=zgHv1@xsAYYG#ok5^N&~Ag>cYZe-DliMdkuwZL>ve^G|pQ56X%XE;+b%zJx>fsyhm4?;%%@Kb$*QLEEUOUKsulo!7@v!99{2gLk6 zelqNawsnlt6P26|<25`cA<`_3R31e5YQ+u*ZZG zhtf!6@zhJ{P4`K1L{x1%78+B0B98I;=PJl!7E}3O*4BkrJ)boeXrIr2?CNY(5ZyMc zz+-a(epm9L`P|lQn4|Fc+mMA&v)KU>%x;N91+0TA*{pZckaxmczit-Kard3T^tYe6 z=y36%8DeMgr5`wL4$@2Zw5lFMTNkqLeXR(~_{4{1i0e$jr#PwmIacCbGqRNmZ^jFc z39813FP|CG)N!p+_I7e#j`wz)di#+`g_EQreloKU-E)3oogRAlt@>=^)cRi?v@TI+gO=%8MyZBB{CGFW0XE^I5H5fCI@aI$ z@tspOGrBwJr-Ar8$k`-_3CKbVA#`>ybv1$9i2kqOdt@sId(pdaf<}}F!~$YrVF!YM ztZdw@Y!Es*2h%_L*g))ToV0(F*u&Y(90hU!4n%=uA3r~U`)`0b;0FxI%Eb){bM8I> z`(H4~F*8Kh{{p*fK+>vv*e}|<1|$Ue0|tqn|A0ZllRsd8jKR&$`U~%uEG*oRgZ!Vg zzhFQXAmqaN4}QO4Ant$CLW1f)^Z>FzejfOPmJ@P-{SSVSgbiW>_cH%A29O035B(Fy z3W-?$31egX6OWVi4?TdKf6hIKh5HZPKtT3ibN`ntpu0=={}>m<@#nZ8F0Mc1V+FGQ zp)V_t>km6%Wxop>LC4UW4*qkqC6KOp@D13Mc*eh6|#;p0PLRzsRUyi%{29hxvIfL(Q6%zh&bD~gEiYbVr{6CsDrbz$* literal 0 HcmV?d00001 diff --git a/docs/src/assets/loads_wye_1ph.svg b/docs/src/assets/loads_wye_1ph.svg new file mode 100644 index 000000000..bc40a154f --- /dev/null +++ b/docs/src/assets/loads_wye_1ph.svg @@ -0,0 +1,176 @@ + +image/svg+xml1 +2 +sd[1] +1 +2 +sd[1] + \ No newline at end of file diff --git a/docs/src/assets/loads_wye_2ph.pdf b/docs/src/assets/loads_wye_2ph.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c2de6424eb89a756b452ff1401b7d38115f0b080 GIT binary patch literal 16407 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 +image/svg+xml1 +2 +3 +sd[1] +sd[2] + \ No newline at end of file diff --git a/docs/src/assets/loads_wye_3ph.pdf b/docs/src/assets/loads_wye_3ph.pdf new file mode 100644 index 0000000000000000000000000000000000000000..48bdeea7bbaf4267909c0622492c63e0a754e7cb GIT binary patch literal 17510 zcmd^n1yEf{uqf^XOV9uphY;jm+=IIXcfCk(3ogN(03kp~aDq$F;2t1&aCZyt4*xE&(S>5Dv*m(ImIGZwoewSkGc;wf6Av%RltIJoz}$31bXmd$x(ZFFF5bcdFkt=W<@w<;uaL7h5a4rMtD$^gpi)aX z6-3@gVKnu3(3~)(63IMHg*pfvhh=qzdSR_lLPK3vmsCrNwzv-GJ-MnU4V6U914TYo@(kim_|#n!MCjJ*n|!#WOq|C%jD6jkOG~Y=_Q%V1 zeq2(Y9H2b_wlqE@<2Y!C2ve{(m$& z73NC;)uo>3z{z~;cfDczofa8Cqp_aHh%r#LsC8u z(s#sH+%S&;jQT#6F$fsbczy6tg4?%Hf{e-5^5*~>%r*-EPZlijt|HNI)K@;w6AbkFqlYqO0`m#!~OKBQUm5BPYa zAtDXWE?^+LGdb%D)3&vu7Ub<=EO!N*lcrGMKfqjETEfA?f%4&l(%0jmnH5+;znX6L zhJWQfEsy34Gd(j6{Xm|wNe~lpZwSN0ho+MV&8M}lNf7#$Wp56b8WX7`XuKJ^QePbD z;i14t%Cvq^<92p;ke~)B$4z~yM@)P@(Qx~&z+U4{yqe&|aGOlxq0N`u z$gZyHa~PP<@aIs|S3(|8^Q6%sybQiKwc!tt4&;e$DbyZ)pGUyFtewg>x=`zwy+!ib zPFc`LymDq>sG=G1g|@uKs)kR&Oqm(N+q>2H?wxgYOLrw7cbn9C>ntp#%gE>*FmV%l z>)irS3l5tT@Sz!WYfeFUHneSymU>I{0`11#jIH?ulWFXhSS7$O89@;D`})DIk-y6m zL67DWZp_aghUQ+JPuZNAe$@sjltd=cEcche%zM?myM5$N$iTCfo!xpU8RH$$Bp-V? zr}ac=dwT0O9KZ23GZdWPt1IRd#`7+07&xdDlR=WL%lKgEI$i~iGbNPy5M9isX7s@G zHL-RdC|xc5&OU;xqYJL4Te#a3)T^o6Q}i49y=+O{OYbD1D=1w=4yN%&VqZh3+3c|$ z0hd%vpRq%Mv+V16Ip#fYDES_j1%7MqiCfX$K2!wvcRP)3r)`&dCwlV8_*RG6e!TI*R(Jl?r>oH6h?H<53}j-l_Av7Xdy?KzFxOq4H>T*WbnR-z_S z)m-|tj(#x(TIJSkSG{r`?Q)Cd*-k*k$%I{M4=N?SFlWxGMmK&5yUPa?(egf83rip$ zxaRH>viSWWw$Awx-KE!hu{KFa^3>o=A&zQCP5%2uIc5(qmMVLzwtf7*8zbwGJW%_B!olCq@2yHK!bCDTeL3rH{0ZmthZKZh<19kJq@1VT zpo}HD(ExR>MO7q?ZzG;9!qAp8cW2?tEB5a%U7`i+DcO$~u$cXfzB}Siej~P>TK~j- z1@^fFac6A5_ipU+(Xo-WA_L>K@L(6OHCTf;j2gBAn?H0IjHXKUWli%y4yRri zUQ`mnKjrI;0t`pk`B{&MqC1v&jyKW@VsgWSJoAg(k!qkY^X{Us8Noh2S;4W{CD+ng zzaa_@v}n?nS*7pO_MLcmi;#jEZ1nk$iNR8{DMW0UyeN;n90}=?giy2nWT<@ezSi>` zRD_kxq}QB#j#p4(2Q)jU|&xLDC9J?#7@Spr$>icX}f)6DP{Gc z?zX`e8Z1ZAR@&)aDQqlIx&nlRg3(4ECz3u<>Myn=;1#r+-_?IqY>~X;jDL4&I3ysx z8p!x*#kDY7w3To_z*u?2A&gN;Rlu+t2qCXp+@^)5VCmH6iu+W_;}2FFcrxQ2^oPAif>p7PgA3o zalPf6q)PUg$-?pj)jIrY#>W7~?18BW#UlC}=$L}i*j`ax_$I7_JcCZ0JlP=WI5(1h zt$lbk^Vz{5LCJO5^!{A0r%L)WMhQtp%j2`^2)Lt}vU}oPk2L{$q5!wm4C71ZvDh!G zn*m!D2H7bV=h2&NbHam3@4j!AWd_k%X7-DI!;t(`Abz0OJH*{D>ghxY|Ei32t$$%j z?Fo{!3nz9vkEw-A^pB1f$(yBk?wrdJg<*+q=@ZM<~&avyB^kmtZS}EL;D!! zwZ1D!>n=VVZL%JDV;3+1q_U==NF#u6vb##Ibdyd36&lA8tJHVxFDboL)e}gR-A6dn zUEIrI^+u7tAg{G;+5YnSV@&{0oMmfW*p)2;FZW|_Hi;0qG=5NjOaIo{m&!hY)#q0J zEygIXEnzO+T5s4-x791} zW!TqkW)~u}w#ye1JJmgxdK?``9&|Gw;jM4RWID!h;{DQkJlRrcf+Rf}3O$#q9P_ty z5QjDAr1QBw+Ycw1UOQZ;`HNA{!nKL&NmPj!;~(HOpzUgE!Vb_byb+JFcouNf@1g$W zGe-92rhVLX@z?eTohcZfK3bMVOgrbya+%Mt_Q6|P;K10MbJ3~ znqaE3Vqcz8Ya$5q_!)=nQ;YQK4KyR)W5n;v@|<2%?=GWS(p1iO3;A_?=c^spL6c3b zFtd;0vXp8#Na3%{2w1AN_E_20Xx?1*?x^)f>*SM8U3KWu1*95a)+p<$0grMdM|>4= z!TQ{(u zg?J&4*1wl7eQO86!A(`x7=w0|zfi)Dj8Pw3i@?T6P=Pk*dQydmgS3gw#)W7#Bb-46 zqe?H%-15Q^Ug9(&oDvs>Tg)ii&JM|$xLfC zokOof@Nj&#L3W1~#eKVFby0olLzyv;N^FVLT&W&c6947M;?y;pgaV#~ssJ`x4))X$ z^AmGAB0A_y7fmiwJh#clKHtR)8s`jwe7Z?`+?0&b&g1M7581sk?7&BA&(#SQ3bO_X zigTZmf62^)cGZ`R)3MFGKybo35e`>ul~Dd>%^j=UG0rtiz%c5?KremM&cn@SZ1pwk zHMn@=tD|W{w38iLvx~VQjc-=|YbF`uy=Q_4k$Aq)8t)HUN_w-lqB|ZEZym;~&?WZb zJZn2z;$rDPwVKfmfCbmTc;Zkp1MYC`fe$;vJG1hfwVi~Xf7C8e-t=Z!ZWx6EJzxDA zE%B(lc}fNQJ^2o9JAcDN|Bp#u6IAnsLMt-pFLNpVhh0bM3pc*X#?N_qVSqE@+FzPe zzq^7i*T6bn%d60A4}in{n;*tV5qR(nr{xzHF&qsl(Tsn6Fv>7(B*v9 z(c|@nyJ^3fjVrBdtaCTrJVYN9)EqM?kieVKGjmx$L=!~K&2@Mx{eckTp9CuF4>1L0Opmnc zX*Fvn_`ocLTk8)Q6ms^dso~+c8J*fz+ar6hdD{&!HLNOMnAt_@OMe+#)U=U`4CKvb zLYQM6>PqDj(Kpo^#4i^6w1;|usrvcMXdBeKN0frec2*#U{@6Thf**IA*(`n)%aed@ zXGMX$TziF~Vn5#_>ym`x~#cx*;B^jnC}stirSdaSzXy8_k8-n`CQIu;PKY!!#P2SHVYS2ttb#KN(oDnrz<22fJpf zQ08PD*>ke?C|-}3$*Ts8HT4ya^0L<;ZK%6@s2%b3MA{*KP=Y5fciPMfg`c6-c#Sxh z+xNN()5YZSGaU~0*!Fa?jT?f168QcAB%TsB&U{73+d$LLOxG0It>dYUdezQwGaQ=; zT(^@v#fek?t#JA5Bhi*SB>0cF$G1B(jS(!7bR?T1D7ND!;boy2a82szG0RD zUmY>{$!8qA23Zv8OfRoCsAU@tWUaXj9qR0IbkNl!cj)Rgo-3w(egKL>cmDis{}Gx> z$X6Oz^Sy^{+0FV_oPtxR>wH5$w$ndPY=iCBsQfAzUhSO zi4^MW+|+f&;+>06i;Gw4J!+t=(f({pcQ`3KIH(Nnzj@1S$IV9#lr}JYJ5Hq5sQFR? zNA&&a-YFhmV?_57+r`mxf!WdWGsYAQPXYC{{-o7l4kYupBWVS@H|o^rEmJLV5aSI88Wi%Atn3P{!=ZCfMDD!ggfw-GlJgX&T9* zO5c?d;b|owJtdyI7{hX;DnKO8R=TuCWy}xwTKEc9Hmd)%ScNV{MQNnwOG-I&ls+nA zi8m4@X@x1AjB4>=8kclvJD212LTd5TzI4YXb1P*sGhqEKE(k8Hlj7(CFJMR z<2(1|=-6+>>Pr?Iy_wc>Q}afUGjvXvRz2u1H7Bur%Q*eKLiqKdnt^1zhsd_X`74-{ zK=NShfEC&RB`r&W`r?P{0*E7>-!9LcEJIS{9I)3W2l38tL}W3lh{rq<+|MJ%$Rzan z_%`<}Hs*2t>_xc+dr9*BKCUg75pd%ewO*#y&dK&NeDst>R#i`9IqYE)L)=i|mS3z; zAsGx3*BjE%^A3+6k21&SL6w|Uw%BKEvO+4;HfS@}^iU0{>UpwDNlg8Qjv!awOMb}f zs-odinYtp77jcO=rv~^YiBpNajgj%9HY^Efm_a?IK~9L4Rvm6PVd4ezf^g4pLJTkf z-<9-n^kam|2-PjW@e<#)+>%and%0YHe1;E4fLW-MjeK%cwckl$Nek}#2NfTpt?b`< z8(UTD&FGH>yPX#@H}`#0>mRCPCidOpb>j-Cx%S=IRbw!9u+Ax}iL^zpeu2reaK2TG zX%|;^CAXc^0It-uD0w;VOh}c{hZu#-`aJhV@pS~hHF$sACfoA{t6EXxi3x5`NMOe- zst!^)C(ezQ>N1hu3iefka0ME>7C#CvO)(qS^y?j4Dpl!f@|9%V;JVCLmc99%1(X3? z;o{RgRqOkYO?0N+%K{(N9AK74ez!TMEsuIyb2c*=9hMn`YnY_l)8(DB;e0)7eEdmn zLb_9vYjZ+nh`i(o7o+d;1;VBQg@N%Q&1&F$_h}QMS;29kukvdi!nGpdtc(O4oL*U1 zf%L4NkkyJ_-x1~f(hD2pv?t|;Db1}f-&m<6bc9$|`}kR&)zCq=<#U&tI@O+T4J~&K zK1`!SyXA>!X}8&L^QUs3G#DtCtJqUh;Qh0NIqg=RjO@7D}-dI+QCAm6z$Orc}mWd1hge#ccQ!HH&pKeFOrjjH`a$Yll=N%yVDqo z0OK9q)9xyJ2CX34zLm(nicW0~gmC|MkZ|~e+b)L=*bc>klTFHEv8<~unJ!)OSfa*} zw_m>_XA(s|$Rz8Si}L+Oef5@4(s$*_t{7_BYX7W;le~+W&kSMd93e>xqLJR)%uJbJ z3F1!}4^vF@p2gZnPkyy97R7Hsq!rneJUD3GWxGW5)^Xo?6FAEqmiz8X%8oX#Gg)NT z<8fo-3Z>>#Hbvei(jBCWTxVi)Y=hlRX_W;`zEc&(Qd&WB^-*pIh6F)@;1`N7_0W^b zUT~Z^SnzN!U@shoITrd7Qyc6ecuK)4`NoHLFINx}MEM$l1x_-{Fu!Pclj@x42GK@O z5Ybum*xBtwyTvHe3p}(@->+15VLeGlyA^YE8;Mf$^t2Mw+9u6afR1m*{S1fG(EW94 zO#5}TTQj5imT_6(1Qz`Y!J5j>msEIEgs|Gbj7!f%UGky&5WQRSV zQQS@I&4Cz=1PoiGL5=YBDeIDYsh}52(YKX|c9NCrtPzh&zjSj z9*ehIh@B}`-FhuDSVR^zy)N`;GU~SAe&xV@jkW(3uM)1KyNAKHV%fCrxgJV6skiY^ zQ}}3$8?6V!rq&>L<~WD^K~2(AVIBLT=zwYN{oYd)>Nk{o)#d76H9`Q7>~ME09BWnj zl}*IFWe1!jiGoSHpVEGKy=~5C$T!5Xp}XSFq|rOOQ*OW;gnJ@6kftJ;HbG~XXu9AB zja?hZ|DBS<>ftcWdNL~gs7$_Sw6PG$zS<1cTRK%=k|&?L8ckPEG@Av;{>J*hB|r znMk*DDxdpGWG6kvhryK?kcUHyze1D?s;`VIC4McA8lfO2#ru>+tg`7$V*=z(*-49Z9D(o%^?7K!iN+?_U+6^-;bDh0gs$KC zBq$_y5Ack|UpL#$&AWGeuZ*(_uoK{(1Zlw{;>^ckXdaL7^GHtT4;RwX8#1`5%NFN) zhZsw;q7pn678=!6N&Xth!TuB}_7WaSaeQtuznfsT(^I>$20+zemcY%?ewAkzauX?c zWmagJ1+ow!HJ|&6J`og>i`9;E6M+1PING|Dd(vpgYRO;+A*<+|@qzG^DY6g^a%X!j z##MHis)a$uuCMKf=SRow*6+M}t#%S$5}fp>1r2Q5ntHGWhk7!g;}xyoV|ex9sSIOW zx6?jcY$Q}*0`${^lcac%>7SBNT*NpJfAOpx5EflG!GWtAHCI@ZilO-W3FTch`E-)b z>%zSv{4`2qapkfGpqEMrEya$zbAg} z1wUS~%}l;$KTDVtPjHVO63Jk!d}Y&<`HkwlPr?km8J~Tf-ZSdi3Sl3WA)B=!M&bH7 z4TD7dUlYDt^DJoV37cXpb{TXsi8RJ8h@Yue5thHmNe>tzmDAU%@prefm_%xm$(i~T zI%=0s$`Z(5bgPM$)TTGY8i}Phgl$gw^7$x zqg&dvFeIcz`a?h?%xYM`=ex4^mFLVNX{Ndne;< z(t0Ik7qmv-5bmAcM@LH4bRXUmJr zA(en^U=CCLvO&61xdkNZnk6u1$A^RLP1Q(u5E0HcHLa;TGtx9*Nl}#(9}lxq8~(x@ zHsHBku$ZAx%FCod7FQMpw`KTmpL^+o6aXz+xcMXg`(H6vDc(D+eV)@Pj4y2cKDIUX zWaGVh&NIYEQba{W$x)?l0a;shIg$=)vuHw?gPsPrpH&tR7HRuOE->jw4GZM_9eXi) zn$(bti71zoLz??OHP~n%bw;`);ir=3*_ovq@6Dp$Qa&~({9YMi-8E$$bwrpRBcWKL zQRlRm$dkFQaoy( zMw=w#mA2h4iS{y%ew1YSkF=4%9-@Q(JyG}+0`aaEl7|Q zJy3l{Fdxttf)(bzJ%J>1Z7YMP$~Wa8i2GsEJeh4F-$^);ZNCVXs+a4chCmiBDRHh_ zx$A;GcjmJdaSA!Ih1ZM{Vf4-044y9{v>Yp^eZ^G|jqPk07>V*Q@+HF;BKIf#2*s#N zo&inzQLDqL8ix{7x8qAX13N6rI6rWP>auf>xWS~pTT@f=d&TwXQ!u1w# zkKY2x3ah+IT&$36j2WDx_?wuz&P?dryjF5wg)NAsrHG4Tr~6KFKK?^B{Tu3 z;ye*ihR##+DrfOUp3KU(%2vn~uL;%dJ0$KINs^_aoswgxHgEy-7>0nD#=mR4+4|VM z+Vckcz~KYAT!2wNO)W?~dI)pX|+ zA+eO3kGCS{qaTbJSlX=zn5mh|#0RlQ0ryUIi9moZg^u>t%ydaL1Md=Jr_&(s~* zwA30KC*SG57YLf2!h|uIPL+!s0Wenhq}LH<>@6}>U|`7@H>CO>)LQezu^7kdEt5Ya zT~@|lietZM8~#ktS)IrMG&Rx1S&5W%D|(=Yyiwn)T{^syo%xWux;;!s5}lzBk|1}E{kEV zeJREf;&(l(!yxqiTdo+`Zdh-F^V5|}rZMlVJ-^5sU>6!w5j`yYizr2%cf%8|v=z4I zf@?h+CYvA(gbFtC; z+w%i!PswuBG=$J!Z&u`!pPua3^t{v!4pxXdS6q73&63A2xRANx z@^ApU;9_>wVK?0F)Z$Uio07r&E@7#ase8&qDj3CMptQr@m6Odh=cCNWuR!M6S%k{Ue*1F8)5t4rb-g$S zrtQd-_N7!3YgY3LQdhxiS{{63u0skHXC^?U025Iteh=;!+a7ICoJt?uoC)(KPtVtM z#f1BLz6sfa?9zmY1UAXC7Nv+TQx|?iqAJ^WpU=>l;qH#@P?TSkw3bEd9 z9wxXyx3JiPHNPst2yuxzZS@n}xLLm$4Fc|G%BcpvOoXu%BF+a&g@LU<8wft)*1mpx z;T!RB_=;lWyel49cFDesZZYsE1G~MfBY#L98?~_|hVvlqMKQK1PVdXeLX}R9LQi4ewVs4(wp1n z1T^AG3}TyOr}a;JoOzjTx1>wxS!YfKEQm)&65Tr0$U*~UI7K8(j0cNxQc73y-h!o; zJ@m=Z4<^2!q3>CsY}-*|46%_4Y5*U+cK=Y>$t0X)Uat4C%jV)}&g(d{6K1AE7hODF zC&s$M-=_&!At+v%|M9#$X02MC(%J>Sn}(J1CVpRjm!0=|T);?oL+P2fKAwYetbcK| zjf0?KL>6sX7m2Pn&XDZ7>gP6$(ob(1*-dS16m0t9abm*Ngr_^7mQPD=6bXq*>M$gu z$f2F><#>h;EPT}^|ER?Cs_}-(u7VAr3R_X z8CXGDM8q}JHO1+LWuF5fDQ%GKHB$!w>kkAWAsZKf76U6YfC0qD3;;4Sa{)Nmm~{Y5 zf(}MtYexV(7X(w-z*YimZfXXpau3bhe@UF;PhF9T;$zzxSM<|ECDJ{t$r{8#6QDKl=l5a4>QL{`$*t4`Jm1F@gX; zet$q9`~x_cIT=|19H6^44t5qsE(qt$jO+k*HV}jg2PZ2d$DN!2EbKr=Ab<^chw-B~ zJBSUy1!VuD{SVlE-#@6cu;2AyV}nrpgZ|xk_reF=P4RwsNR=IUNBYNv@0j~%=~w*+ zF;-SqM%F(F|0uIC-wkpvnET;=^!NjMKh_TjCkqRN*&kGXVYA(1uy6q(tgvuFXzTox zJ4B2A%%l9Jdyu7fGIYFG#XBHm&4mmcz;{~sn+E<}Q|~nSIb@a1?HwHN)C9l^Q63qC zKPo^VBqLMJ+}P2~0dTkAe_4MZ@Yg)<$ARQ^ig7}e_zyHD1$!H#JEhZNf@FXKn3TaT zjyk{82MGGr{RbQB@`f+KMv(db$%&Yay%mI!AG-ty(RctGBNrzSz`@800zh`jUHg4s z2)nWdj`rq|-E=oA$X2@hgJ2SzgSk)N<>i$1a#uERO@g`Qv zzsQ*FmyrHDGG_nP{ePg0)&D8uE3DLik?~)0zyCWj{_VK^A7v{<{vqQdbgZ<0k@3$1 z`QI(we`YH{za1<86&Zt=?+r2#a${%te`u09IsQ|VOu+|hjp|EI(if0KA8 zPRPGV{QvAc1pT+p!~Z`eZugtSCvigmMdH6sM-(OlOItGoNR$F`rXVqeAb=U-A~P8o z+}|Pl??*e!U)v$^%6&Tv;Lr9uANSAx5byH7o$Xiqod^DBJI7B1Na0TkkQw}m@?!$P zyT)I>CFs|gjKcI|Ybh8wLcSidMq&D+a1RCERn4s(?<*h(s=1}Pwdv2=55E(#NADvu zNX({#!lVufuxtTbTqsN$_a#=8zqy$AbNuOvGBdIP7?>G3+1Z&PQORElcOqqm5c}~J z6aZpI{?*-tu=MBGO%NwjfteM`Fd$tyfRzynR|OX3U)Ji^Df|DF?LRrb=l`#)|KDNz zmv?Z#yg+6az`X`M|D(zZiQXV0fBshu`E^+SxehFxka+rEzB0R8)IS#@;!U$$I{;Jk z)T6yX2LS8nQwU_7K1Y`wP8h>iyJ=u%epX@CqjmJHx|MH;|HV83CjT%V{`P_v0kM@2 z2iOQs3YUEaxBL4?lAYo1G$7oIFX=_${Gq^n`V2P1p6_k6uUaH-+2mS-N4{i&Ul%z^ zk$@iP2_v0b6IbQ1UHY;Ix&@SfsF@7)RAJg8(czyfynLkM0ZB)Q6RPpIQ7MMMhDs*2 z9&>j|flD5Nn|l&IUOV}s>9Qo~lya|t`b;(Vp+VrIAXfOCCN741FDplx?A^Rp0G(U>-iMYkzi-D6l4C6PRM1^=AGQsSs1he*@66LBBIu3y#7kv0Avf%d zEnvyqkc}(c1gHkVZ6ch?6SXX@=>?s(8ekAhv@S$GeuGup8n!e>#$XfZ!IjxpR-^K$ zJKi_HU^b0d2iexFyqQ;$gn-Jp8p)25ERxbc+`ohyecu`-EesQfs!ashPE|<#L*P6p zz@P8{R!o{?^mmC8#e2ldV<=I+t+gn__oefF#n>H_Uq!CS^WIZQlsxHxj*K=ugJ2~SdHrb ztgWZgK7^>N({@av;*>Zi%%;#hq*a9x#D#1Zg& zm8m+JB|_iY8lVWYg?;^Wv{7p30hZdxb(VtAB=)zrJ2G{L&q;w=W0O$ z4~@`ysUEr%!r0_IW72%{1`$EiY&KTg(41zy8ms6ad$&|I``hwCn|#fBYylS2^HsX) zH3HusRe}VG#O&psy}|~P#RK=&(^ADA)NHwN3%EC36p~k&Vf-%K}Bj(hd6 z_91_BN>m%^*?7+^o2RGgJsR*@$R*>OSK$6D^0)hDcQtHn67B0`X9CS=tN@ zh~M^v*qVgvWG0B6!V1AS=g6NTHXBAi?WLYH>3@nLLr>kv zmAcu8JbKFLT#o4KrT~u$&C{VwGogq*tPSvAik)dD7DaqsdsvR2#i90Wl&ibDvL-W0 ziRr2YB(x=k5<5N>_88p+-U%D-8AWF_=&gX1=Z1^@ILETVVMI6_w<4F4qZXOox_HT= z0FoHQq=+Bb6-iRn%=pOR4nC`bK?jyV;B8tkQo~Q?< zH!W$YVV+Z%i7qu~s>^knBah9$2vi~cS{%fiYfN84BX>F* z_%19j*dSlU$4H8r>lgUliX*uxz8KVBPG^u42n+PZ{n=hMe=@Jj&>)sB5Ncr|XI0)SvYBO-upiIb0}*X`DD&bh^r%O%7>;0J*alf0ZJKPaRg`jYN^Xb zDQ1FtSCEdVi%t$&k@}?@JgD_F<;9xniomIU(~`8ygP8J|2xMp4Nx3iWr{3|8#L^$? z#E#7K=YE!t{XBPbbBKXVQ%X2}0~iWXuFN~No=sVf8UCDr3hi!@6P zXY#QO%G)vQD@}Gk9OMTY-BeY5z;3RP!_6~zC|}R)2@IJo?3kp8j6?Qmx-R< zZ>usd(n++;T)ib$6a6zZbx5mY6-EKiy#h^*F>G{ zSMq7~qI}`C#VWMgFjw-li;_`|q`^z@Fo}5k+__QLPp`>s4ha480ov#-N|-geF!Ew> z=$Uf!q2-%V1L13ElnC}l3G-Om@Mg_MkNK#R+%OC~w!db^Bvr>2zMX-s`E0s%Mv6;n zUNzWOFx~-!WO^-5nU`0D-@eK1s-UpAINMaSxC0aW#VL`uB1oq}jVu=by27M=@z~Qj zgKPUk73$-_&Zx8euuX$#L%&l*bUV&iP9#a`9wnAwo!i0$&x0pDrgS2$^IQT?WX&Iiymgkp|RfS_%9k$k# zL6y_$RGrFfaj(=mohyu6Nsf)*orYh5_s=s+!N3&kaOX5QL5?^`SRid~>;TXL+7BO;q4L`a2{{3O^$>>qyMntHM6{4o67n1y#0X@j zgLJgFF?KS7{C9)DgYS{eZLCG^+!0z4ZV)qwnVAg;0{d>z^=owmV+$F2GONT?Gi{&>w77*)i^jKK$!lJ+P!NSh*8^0`E zcMrP$fyc%18*i+vciZLfc +image/svg+xml1 +2 +3 +4 +sd[1] +sd[2] +sd[3] + \ No newline at end of file diff --git a/docs/src/connections.md b/docs/src/connections.md new file mode 100644 index 000000000..1ebda2ae4 --- /dev/null +++ b/docs/src/connections.md @@ -0,0 +1,45 @@ +# Connecting Components + +One of the main goals of a network model, is specifying how constituent components +are connected together. The patterns explained here, are equally applicable +to the engineering and mathematical data model. + +The available connections of each component connect to bus terminals. Take for +example a bus with four terminals, `terminals=["a","b","c","n"]`. + +# Node objects + +Node objects always connect to a single bus (and perhaps also the universal ground, as can be the case for shunts). Therefore, they always have at least two key fields controlling the connectivity: `bus` and `connections`. Most node objects also have a `configuration` field, which affects the interpretation of the values supplied in `connections`. We will illustrate this for loads below. + +## Loads +A multi-phase load consists of several individual loads, the number of which is implied by the length of properties such as `pd_nom`, `qd_nom` etc. The table below illustrates +how the *length* of the field `connections` and the *value* of the field `configuration` +determines the layout of the load. + +| `|connections|` | `configuration==WYE` | `configuration==DELTA` | +| - | - | - | +| 2 | ![](https://lanl-ansi.github.io/PowerModelsDistribution.jl/dev/assets/loads_wye_1ph.svg) | ![](https://lanl-ansi.github.io/PowerModelsDistribution.jl/dev/assets/loads_wye_1ph.svg) | +| 3 | ![](https://lanl-ansi.github.io/PowerModelsDistribution.jl/dev/assets/loads_wye_2ph.svg) | ![](https://lanl-ansi.github.io/PowerModelsDistribution.jl/dev/assets/loads_delta_3ph.svg) | +| 4 | ![](https://lanl-ansi.github.io/PowerModelsDistribution.jl/dev/assets/loads_wye_3ph.svg) | undefined | + +For example, we wish to connect a wye-connected load consisting of 2 individual loads (`|connections|=3` and `configuration=WYE`) to our example bus with four available terminals. If we specify `connections=["a","c","n"]`, this leads to + +

.

+ +## Edge objects + +Edge objects connect two buses (except for generic `transformers`, which can connect `N` buses). Therefore, they have the fields +- `f_bus` and `f_connections`, specifying the from-side bus and how the object connects to it; +- `t_bus` and `t_connections`, specifying the same for the to-side. + +### Lines +A line can have a variable number of conductors, which is implied by the size of the fields `rs`, `xs`, `g_fr`, `b_fr`, `g_to` and `b_to`. The fields `f_connections` and `t_connections` should specify for each conductor, to which terminals it connects. The figure below illustrates this for a line with 2 conuctors, +

.

+ +### Transformers + +Transformers also have a `configuration` field. For +- generic transformers, this is specified per winding, and `configuration` is therefore a vector of `PMD.ConnConfig` enums (`WYE` or `DELTA`); +- AL2W transformers however are always two-winding, and the secondary is always wye-connected. Therefore, `configuration` is a scalar, specifying the configuration of the from-side winding. + +Generic transformers have a field `buses`, a Vector containing the buses to which each winding connects respectively (these do not have to be unique; a split-phase transformer is typically represented by having two windings connect to the same bus). The AL2W transformer however, since it is always two-winding, follows the `f_connections`/`t_connections` pattern. diff --git a/docs/src/eng2math.md b/docs/src/eng2math.md index 05cd99e86..0296bdae0 100644 --- a/docs/src/eng2math.md +++ b/docs/src/eng2math.md @@ -4,7 +4,19 @@ In this document we define the mapping from the engineering data model down to t ## `bus` -Buses are parsed into `bus` and potentially `shunt` objects +Buses are parsed into `bus` and potentially `shunt` objects. + +The mathematical bus model contains only lossless connections to ground. All other connections to grounds are converted to equivalent shunts at that bus. For example, take a bus defined as + +`bus_eng = Dict("grounded"=>[4, 5], "rg"=>[1.0, 0.0], "xg"=>[2.0, 0.0],...)`. + +This is equivalent to a shunt `g+im*b = 1/(1.0+im*2.0)` connected to terminal `4`, and a lossless grounding at terminal `5` (since `rg[2]==xg[2]==0.0`). This is mapped to + +`bus_math = Dict("grounded"=>[5], ...)`, + +`shunt_math = Dict("connections"=>[4], "b"=>[b], "g"=>[g]...)`. + +This simplifies the mathematical model, as the modeller does no longer have to consider lossy groundings explicitly. ## `line` @@ -16,7 +28,16 @@ Switches are parsed into `switch`. If there are loss parameters provided (_i.e._ ## `transformer` -Transformers are parsed into asymmetric lossless 2-winding transformers. When parsing n-winding transformers with n>2 additionally virtual branches and buses are created to connect the new 2-winding transformers. Furthermore, if the loss parameters are non-zero, additional virtual buses and branches to model the transformer impedances +A transformer can have N windings, each with its own configuration (`delta` or `wye` are supported). This is decomposed to a network of N lossless, two-winding transformers which connect to an internal loss model. The to-winding is always wye-connected, hence we refer to these transformers as 'asymmetric'. + +The internal loss model is a function of +- the winding resistance `rw`, +- the short-circuit reactance `xsc`, +- the no-load loss properties `noloadloss` (resistive) and magnetizing current `imag` (reactive). + +If all of these are non-zero, this leads to an internal loss model consisting of `N` virtual buses, `(N^2+N)/2` virtual branches, and `1` shunt. These virtual buses and branches are automatically merged and simplified whenever possible; e.g., when all these loss parameters are zero, this simplifies to a single virtual bus, to which all two-winding transformers connect. + +For more detail, please refer to [upcoming technical paper]. #TODO add link to paper ## `shunt` @@ -24,16 +45,16 @@ Shunts are parsed directly into `shunt` objects. ## `load` -Loads are parsed into `load` objects, with a specialized model that can be found in Load Model documentation on the sidebar +Loads are parsed into `load` objects. See the discussion under the Load Model documentation on the sidebar, for a detailed discussion of the various load models. ## `generator` -Generators are parsed into `gen` objects +Generators are parsed into `gen` objects. ## `solar` -Solar objects (photovoltaic systems) are parsed into `gen` objects +Solar objects (photovoltaic systems) are parsed into `gen` objects. ## `voltage_source` -Voltage sources are parsed into `gen` objects. If loss parameters are specified (_i.e._ `rs` and/or `xs`) then a virtual bus and branch are created to model the internal impedance +Voltage sources are parsed into `gen` objects. If loss parameters are specified (_i.e._ `rs` and/or `xs`) then a virtual bus and branch are created to model the internal impedance. diff --git a/docs/src/load-model.md b/docs/src/load-model.md index 3873133ea..df181f816 100644 --- a/docs/src/load-model.md +++ b/docs/src/load-model.md @@ -24,7 +24,7 @@ The general, exponential load model is defined as $$P^d_i = P^{d,0}_i \left(\frac{V^d_i}{V^{d,0}_i}\right)^{\alpha_i} = a_i \left(V^d_i\right)^{\alpha_i}$$ $$Q^d_i = Q^{d,0}_i \left(\frac{V^d_i}{V^{d,0}_i}\right)^{\beta_i} = b_i \left(V^d_i\right)^{\beta_i}.$$ -This might seem overly complicated, but occurs in distribution network data due to experimental model fitting of loads. There are a few cases which get a special name: constant power ($\alpha=\beta=0$), constant current ($\alpha=\beta=1$), and constant impedance ($\alpha=\beta=2$). +There are a few cases which get a special name: constant power ($\alpha=\beta=0$), constant current ($\alpha=\beta=1$), and constant impedance ($\alpha=\beta=2$). ## Wye-connected Loads A wye-connected load connects between a set of phases $\mathcal{P}$ and a neutral conductor $n$. The voltage as seen by each individual load is then From 07dc49dc4702214304e2fad7596511dee8fb9918 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Mon, 11 May 2020 23:25:42 -0600 Subject: [PATCH 220/224] ADD: instantiate_mc_model Adds helper function to easily instantiate an engineering model and documentation to explain how it works Unit tests added and updates changelog --- CHANGELOG.md | 3 +- docs/src/engineering_model.md | 80 ++++++++++++++++++----- examples/engineering_model.ipynb | 105 ++++++++++++++++++++++++++----- src/PowerModelsDistribution.jl | 1 + src/core/base.jl | 9 +++ test/data_model.jl | 12 ++++ 6 files changed, 181 insertions(+), 29 deletions(-) create mode 100644 src/core/base.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index bada2af61..fc260735d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## v0.9.0 +- Add `instantiate_mc_model` to aid in building JuMP model from ENGINEERING data model - SDP and SOC relaxations were broken but are fixed again (unit tests added) - Remove `run_mc_opf_iv`, `run_mc_opf_bf`, `run_mc_opf_bf_lm`, `run_mc_pf_bf`, `run_mc_pf_iv`, these can be accessed by using the correct formulation with `run_mc_opf` and `run_mc_pf` - Add support for Memento 1.1 @@ -14,7 +15,7 @@ - Updates JSON parser to handle enum (`"data_model"` values) - Adds some commonly used InfrastructureModels and PowerModels functions as exports - Adds model building functions `add_{component}!` to aid in building simple models for testing (experimental) -- Add run_mc_model (adds `ref_add_arcs_transformer!` to ref_extensions, and sets `multiconductor=true` by default) (breaking) +- Add `run_mc_model` (adds `ref_add_arcs_transformer!` to ref_extensions, and sets `multiconductor=true` by default) (breaking) - Rename `ref_add_arcs_trans` -> `ref_add_arcs_transformer` (breaking) - Update `count_nodes`, now counts source nodes as well, excludes \_virtual objects - Change \_PMs and \_IMs to \_PM, \_IM, respectively diff --git a/docs/src/engineering_model.md b/docs/src/engineering_model.md index 83be799e9..474a75efb 100644 --- a/docs/src/engineering_model.md +++ b/docs/src/engineering_model.md @@ -12,10 +12,6 @@ All commands in this document with no package namespace specified are directly e using PowerModelsDistribution ``` - ┌ Info: Precompiling PowerModelsDistribution [d7431456-977f-11e9-2de3-97ff7677985e] - └ @ Base loading.jl:1260 - - In these examples we will use the following optimization solvers, specified using `optimizer_with_attributes` from JuMP v0.21 @@ -110,11 +106,11 @@ eng["bus"] -We can see there are three buses in this system, identified by ids `"primary"`, `"sourcebus"`, and `"loadbus"`. +We can see there are three buses in this system, identified by ids `"primary"`, `"sourcebus"`, and `"loadbus"`. -__NOTE__: In Julia, order of Dictionary keys is not fixed, nor does it retain the order in which it was parsed like _e.g._ `Vectors`. +__NOTE__: In Julia, order of Dictionary keys is not fixed, nor does it retain the order in which it was parsed like _e.g._ `Vectors`. -Identifying components by non-integer names is a new feature of the `ENGINEERING` model, and makes network debugging more straightforward. +Identifying components by non-integer names is a new feature of the `ENGINEERING` model, and makes network debugging more straightforward. __NOTE__: all names are converted to lowercase on parse from the originating dss file. @@ -338,7 +334,7 @@ eng_ts["load"]["l1"]["time_series"] -You can see that under the actual component, in this case a `"load"`, that there is a `"time_series"` dictionary that contains `ENGINEERING` model variable names and references to the identifiers of a root-level `time_series` object, +You can see that under the actual component, in this case a `"load"`, that there is a `"time_series"` dictionary that contains `ENGINEERING` model variable names and references to the identifiers of a root-level `time_series` object, ```julia @@ -377,20 +373,20 @@ result = run_mc_opf(eng, ACPPowerModel, ipopt_solver) ``` [warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0] - + ****************************************************************************** This program contains Ipopt, a library for large-scale nonlinear optimization. Ipopt is released as open source code under the Eclipse Public License (EPL). For more information visit http://projects.coin-or.org/Ipopt ****************************************************************************** - + Dict{String,Any} with 8 entries: - "solve_time" => 4.97333 + "solve_time" => 3.23448 "optimizer" => "Ipopt" "termination_status" => LOCALLY_SOLVED "dual_status" => FEASIBLE_POINT @@ -474,7 +470,7 @@ result_bf = run_mc_opf(eng, SOCNLPUBFPowerModel, ipopt_solver) Dict{String,Any} with 8 entries: - "solve_time" => 0.246656 + "solve_time" => 0.172447 "optimizer" => "Ipopt" "termination_status" => LOCALLY_SOLVED "dual_status" => FEASIBLE_POINT @@ -500,7 +496,7 @@ result_mn = PowerModelsDistribution._run_mc_mn_opb(eng_ts, NFAPowerModel, ipopt_ Dict{String,Any} with 8 entries: - "solve_time" => 0.334904 + "solve_time" => 0.262751 "optimizer" => "Ipopt" "termination_status" => LOCALLY_SOLVED "dual_status" => FEASIBLE_POINT @@ -780,7 +776,7 @@ math = parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model=MATHEM In this subsection we cover parsing into a multinetwork data structure, which is a structure that only exists in the `MATHEMATICAL` model -For those unfamiliar, the InfrastructureModels family of packages has a feature called multinetworks, which is useful for, among other things, running optimization problems on time series type problems. +For those unfamiliar, the InfrastructureModels family of packages has a feature called multinetworks, which is useful for, among other things, running optimization problems on time series type problems. Multinetwork data structures are formatted like so mn = Dict{String,Any}( @@ -925,6 +921,62 @@ sol_eng_mn["nw"]["1"] +## Building the JuMP Model + +In some cases the user will want to directly build the JuMP model, which would traditionally be done with `instantiate_model` from PowerModels. In order to facilitate using the `ENGINEERING` model we have introduced `instantiate_mc_model` to aid in the generation of the JuMP model. `instantiate_mc_model` will automatically convert the data model to MATHEMATICAL if necessary (notifying the user of the conversion), and pass the MATHEMATICAL model off to PowerModels' `instantiate_model` with `ref_add_arcs_transformer!` in `ref_extensions`, which is a required ref extension for PowerModelsDistribution. + + +```julia +pm_eng = instantiate_mc_model(eng, NFAPowerModel, build_mc_opf) + +print(pm_eng.model) +``` + + [info | PowerModels]: Converting ENGINEERING data model to MATHEMATICAL first to build JuMP model + [warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0] + Min 0.5 0_pg_1[1] + 0.5 0_pg_1[2] + 0.5 0_pg_1[3] + Subject to + 0_(3,4,2)_p[1] - 0_pg_1[1] = 0.0 + 0_(3,4,2)_p[2] - 0_pg_1[2] = 0.0 + 0_(3,4,2)_p[3] - 0_pg_1[3] = 0.0 + 0_(2,2,1)_p[1] - 0_(3,4,2)_p[1] = 0.0 + 0_(2,2,1)_p[2] - 0_(3,4,2)_p[2] = 0.0 + 0_(2,2,1)_p[3] - 0_(3,4,2)_p[3] = 0.0 + -0_(1,1,3)_p[1] = -0.018000000000000002 + -0_(1,1,3)_p[2] = -0.012 + -0_(1,1,3)_p[3] = -0.012 + 0_(1,1,3)_p[1] - 0_(2,2,1)_p[1] = 0.0 + 0_(1,1,3)_p[2] - 0_(2,2,1)_p[2] = 0.0 + 0_(1,1,3)_p[3] - 0_(2,2,1)_p[3] = 0.0 + + +This is equivalent to + + +```julia +import PowerModels + +pm_math = PowerModels.instantiate_model(math, NFAPowerModel, build_mc_opf; ref_extensions=[ref_add_arcs_transformer!]) + +print(pm_math.model) +``` + + Min 0.5 0_pg_1[1] + 0.5 0_pg_1[2] + 0.5 0_pg_1[3] + Subject to + 0_(3,4,2)_p[1] - 0_pg_1[1] = 0.0 + 0_(3,4,2)_p[2] - 0_pg_1[2] = 0.0 + 0_(3,4,2)_p[3] - 0_pg_1[3] = 0.0 + 0_(2,2,1)_p[1] - 0_(3,4,2)_p[1] = 0.0 + 0_(2,2,1)_p[2] - 0_(3,4,2)_p[2] = 0.0 + 0_(2,2,1)_p[3] - 0_(3,4,2)_p[3] = 0.0 + -0_(1,1,3)_p[1] = -0.018000000000000002 + -0_(1,1,3)_p[2] = -0.012 + -0_(1,1,3)_p[3] = -0.012 + 0_(1,1,3)_p[1] - 0_(2,2,1)_p[1] = 0.0 + 0_(1,1,3)_p[2] - 0_(2,2,1)_p[2] = 0.0 + 0_(1,1,3)_p[3] - 0_(2,2,1)_p[3] = 0.0 + + ## Conclusion This concludes the introduction to the `ENGINEERING` data model and conversion to the `MATHEMATICAL` model. We hope that you will find this new data model abstraction easy to use and simple to understand diff --git a/examples/engineering_model.ipynb b/examples/engineering_model.ipynb index b73a0fbf8..3749d51f4 100644 --- a/examples/engineering_model.ipynb +++ b/examples/engineering_model.ipynb @@ -22,16 +22,7 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "┌ Info: Precompiling PowerModelsDistribution [d7431456-977f-11e9-2de3-97ff7677985e]\n", - "└ @ Base loading.jl:1260\n" - ] - } - ], + "outputs": [], "source": [ "using PowerModelsDistribution" ] @@ -618,7 +609,7 @@ "data": { "text/plain": [ "Dict{String,Any} with 8 entries:\n", - " \"solve_time\" => 4.97333\n", + " \"solve_time\" => 3.23448\n", " \"optimizer\" => \"Ipopt\"\n", " \"termination_status\" => LOCALLY_SOLVED\n", " \"dual_status\" => FEASIBLE_POINT\n", @@ -762,7 +753,7 @@ "data": { "text/plain": [ "Dict{String,Any} with 8 entries:\n", - " \"solve_time\" => 0.246656\n", + " \"solve_time\" => 0.172447\n", " \"optimizer\" => \"Ipopt\"\n", " \"termination_status\" => LOCALLY_SOLVED\n", " \"dual_status\" => FEASIBLE_POINT\n", @@ -801,7 +792,7 @@ "data": { "text/plain": [ "Dict{String,Any} with 8 entries:\n", - " \"solve_time\" => 0.334904\n", + " \"solve_time\" => 0.262751\n", " \"optimizer\" => \"Ipopt\"\n", " \"termination_status\" => LOCALLY_SOLVED\n", " \"dual_status\" => FEASIBLE_POINT\n", @@ -1452,6 +1443,92 @@ "sol_eng_mn[\"nw\"][\"1\"]" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Building the JuMP Model\n", + "\n", + "In some cases the user will want to directly build the JuMP model, which would traditionally be done with `instantiate_model` from PowerModels. In order to facilitate using the `ENGINEERING` model we have introduced `instantiate_mc_model` to aid in the generation of the JuMP model. `instantiate_mc_model` will automatically convert the data model to MATHEMATICAL if necessary (notifying the user of the conversion), and pass the MATHEMATICAL model off to PowerModels' `instantiate_model` with `ref_add_arcs_transformer!` in `ref_extensions`, which is a required ref extension for PowerModelsDistribution." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m[info | PowerModels]: Converting ENGINEERING data model to MATHEMATICAL first to build JuMP model\u001b[39m\n", + "\u001b[35m[warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0]\u001b[39m\n", + "Min 0.5 0_pg_1[1] + 0.5 0_pg_1[2] + 0.5 0_pg_1[3]\n", + "Subject to\n", + " 0_(3,4,2)_p[1] - 0_pg_1[1] = 0.0\n", + " 0_(3,4,2)_p[2] - 0_pg_1[2] = 0.0\n", + " 0_(3,4,2)_p[3] - 0_pg_1[3] = 0.0\n", + " 0_(2,2,1)_p[1] - 0_(3,4,2)_p[1] = 0.0\n", + " 0_(2,2,1)_p[2] - 0_(3,4,2)_p[2] = 0.0\n", + " 0_(2,2,1)_p[3] - 0_(3,4,2)_p[3] = 0.0\n", + " -0_(1,1,3)_p[1] = -0.018000000000000002\n", + " -0_(1,1,3)_p[2] = -0.012\n", + " -0_(1,1,3)_p[3] = -0.012\n", + " 0_(1,1,3)_p[1] - 0_(2,2,1)_p[1] = 0.0\n", + " 0_(1,1,3)_p[2] - 0_(2,2,1)_p[2] = 0.0\n", + " 0_(1,1,3)_p[3] - 0_(2,2,1)_p[3] = 0.0\n" + ] + } + ], + "source": [ + "pm_eng = instantiate_mc_model(eng, NFAPowerModel, build_mc_opf)\n", + "\n", + "print(pm_eng.model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is equivalent to" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Min 0.5 0_pg_1[1] + 0.5 0_pg_1[2] + 0.5 0_pg_1[3]\n", + "Subject to\n", + " 0_(3,4,2)_p[1] - 0_pg_1[1] = 0.0\n", + " 0_(3,4,2)_p[2] - 0_pg_1[2] = 0.0\n", + " 0_(3,4,2)_p[3] - 0_pg_1[3] = 0.0\n", + " 0_(2,2,1)_p[1] - 0_(3,4,2)_p[1] = 0.0\n", + " 0_(2,2,1)_p[2] - 0_(3,4,2)_p[2] = 0.0\n", + " 0_(2,2,1)_p[3] - 0_(3,4,2)_p[3] = 0.0\n", + " -0_(1,1,3)_p[1] = -0.018000000000000002\n", + " -0_(1,1,3)_p[2] = -0.012\n", + " -0_(1,1,3)_p[3] = -0.012\n", + " 0_(1,1,3)_p[1] - 0_(2,2,1)_p[1] = 0.0\n", + " 0_(1,1,3)_p[2] - 0_(2,2,1)_p[2] = 0.0\n", + " 0_(1,1,3)_p[3] - 0_(2,2,1)_p[3] = 0.0\n" + ] + } + ], + "source": [ + "import PowerModels\n", + "\n", + "pm_math = PowerModels.instantiate_model(math, NFAPowerModel, build_mc_opf; ref_extensions=[ref_add_arcs_transformer!])\n", + "\n", + "print(pm_math.model)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1506,4 +1583,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/src/PowerModelsDistribution.jl b/src/PowerModelsDistribution.jl index c580ad6c2..ff6839dce 100644 --- a/src/PowerModelsDistribution.jl +++ b/src/PowerModelsDistribution.jl @@ -20,6 +20,7 @@ module PowerModelsDistribution end include("core/types.jl") + include("core/base.jl") include("core/data.jl") include("core/ref.jl") include("core/variable.jl") diff --git a/src/core/base.jl b/src/core/base.jl new file mode 100644 index 000000000..4807f19fd --- /dev/null +++ b/src/core/base.jl @@ -0,0 +1,9 @@ +"multiconductor version of instantiate model from PowerModels" +function instantiate_mc_model(data::Dict{String,<:Any}, model_type::Type, build_method::Function; ref_extensions::Vector{<:Function}=Vector{Function}([]), kwargs...) + if get(data, "data_model", MATHEMATICAL) == ENGINEERING + Memento.info(_LOGGER, "Converting ENGINEERING data model to MATHEMATICAL first to build JuMP model") + data = transform_data_model(data) + end + + return _PM.instantiate_model(data, model_type, build_method; ref_extensions=[ref_extensions..., ref_add_arcs_transformer!], kwargs...) +end diff --git a/test/data_model.jl b/test/data_model.jl index d3af1bf55..aa38c9063 100644 --- a/test/data_model.jl +++ b/test/data_model.jl @@ -64,4 +64,16 @@ @test length(math["bus"]) == 5 @test all(all(isapprox.(bus["vmin"], 0.95)) && all(isapprox.(bus["vmax"], 1.05)) for (_,bus) in math["bus"] if bus["name"] != "sourcebus" && !startswith(bus["name"], "_virtual")) end + + @testset "jump model from engineering data model" begin + eng = parse_file("../test/data/opendss/case3_balanced.dss") + + pm_eng = instantiate_mc_model(eng, ACPPowerModel, build_mc_opf) + + math = transform_data_model(eng) + + pm_math = PowerModels.instantiate_model(math, ACPPowerModel, build_mc_opf; ref_extensions=[ref_add_arcs_transformer!]) + + @test sprint(print, pm_eng) == sprint(print, pm_math) + end end From a4e4f88f8c6e32644cce7427da86695c2e7542a7 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 12 May 2020 09:24:10 -0600 Subject: [PATCH 221/224] DOC: cleanup Changed to relative image references and built docs to check they are working Updated markdown file syntax Changed PMD to PowerModelsDistribution in all docstrings Change PMs to PowerModels in all docstrings Removed PDF files (don't seem to be linked anywhere) --- CHANGELOG.md | 8 +-- README.md | 2 +- docs/src/assets/line_connection_example.pdf | Bin 23921 -> 0 bytes docs/src/assets/loads.pdf | Bin 25684 -> 0 bytes docs/src/assets/loads_connection_example.pdf | Bin 23572 -> 0 bytes docs/src/assets/loads_delta_3ph.pdf | Bin 16982 -> 0 bytes docs/src/assets/loads_wye_1ph.pdf | Bin 16498 -> 0 bytes docs/src/assets/loads_wye_2ph.pdf | Bin 16407 -> 0 bytes docs/src/assets/loads_wye_3ph.pdf | Bin 17510 -> 0 bytes docs/src/connections.md | 36 +++++----- docs/src/eng2math.md | 1 + docs/src/formulation-details.md | 32 ++++++--- docs/src/specifications.md | 26 +++---- src/core/constraint_template.jl | 2 +- src/core/objective.jl | 57 +++++++++++++++ src/core/ref.jl | 2 +- src/form/bf.jl | 2 +- src/form/ivr.jl | 69 ++----------------- src/io/json.jl | 8 +-- src/io/utils.jl | 2 +- src/prob/common.jl | 4 +- test/delta_gens.jl | 8 +-- 22 files changed, 136 insertions(+), 123 deletions(-) delete mode 100644 docs/src/assets/line_connection_example.pdf delete mode 100644 docs/src/assets/loads.pdf delete mode 100644 docs/src/assets/loads_connection_example.pdf delete mode 100644 docs/src/assets/loads_delta_3ph.pdf delete mode 100644 docs/src/assets/loads_wye_1ph.pdf delete mode 100644 docs/src/assets/loads_wye_2ph.pdf delete mode 100644 docs/src/assets/loads_wye_3ph.pdf diff --git a/CHANGELOG.md b/CHANGELOG.md index fc260735d..d58cce1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ - Rename `ref_add_arcs_trans` -> `ref_add_arcs_transformer` (breaking) - Update `count_nodes`, now counts source nodes as well, excludes \_virtual objects - Change \_PMs and \_IMs to \_PM, \_IM, respectively -- Add example for PMD usage (see Jupyter notebooks in `/examples`) +- Add example for PowerModelsDistribution usage (see Jupyter notebooks in `/examples`) - Update transformer mathematical model - Introduce new data models: ENGINEERING, MATHEMATICAL (see data model documentation) (breaking) - Update DSS parser to be more robust, and parse into new format (breaking) @@ -35,7 +35,7 @@ - Update to support JuMP v0.21 - Makes bounds optional, turned on by default (#250) - Updated transformer data model in the mathematical model (#250) -- Add automatic parsing of lon,lat from buscoords file into PMD data structure (#245, #249) +- Add automatic parsing of lon,lat from buscoords file into PowerModelsDistribution data structure (#245, #249) - Updates virtual_sourcebus, which is intended to represent a voltage source, to have a fixed voltage magnitude (#246,#248) - Add parsing of series data files into array fields in OpenDSS parser - Add LoadShape parsing to OpenDSS parser (#247) @@ -48,9 +48,9 @@ ## v0.8.0 -- Update solution building infrastructure (PMs #77) (breaking). The reported solution is now consistent with the variable space of the formulation. +- Update solution building infrastructure (PowerModels #77) (breaking). The reported solution is now consistent with the variable space of the formulation. - Moved multi-conductor support from PowerModels into PowerModelsDistribution. (breaking) -- PMs.var no longer takes conductor as an argument +- PowerModels.var no longer takes conductor as an argument - Constraints have been (partially) re-written to use vectorized JuMP syntax where possible. - Bugfixes: generator on-off and storage on-off constraints were incorrect - Removal of SOCWRPowerModel diff --git a/README.md b/README.md index bfef1c1ff..1e96ac35f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PowerModelsDistribution.jl -PowerModelsDistribution logo +![PowerModelsDistribution logo](docs/src/assets/logo.svg) Release: [![docs](https://img.shields.io/badge/docs-stable-blue.svg)](https://lanl-ansi.github.io/PowerModelsDistribution.jl/stable/) diff --git a/docs/src/assets/line_connection_example.pdf b/docs/src/assets/line_connection_example.pdf deleted file mode 100644 index 21c82a3338fc5631b3c1856c14e8ba8020b1019f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23921 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≈U8BH^n3ETFq^~Vc)Fu~=xb;sEO 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 tXrJ$|2}Cv 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 Jh@2?;{|B>{r@R0F diff --git a/docs/src/assets/loads_connection_example.pdf b/docs/src/assets/loads_connection_example.pdf deleted file mode 100644 index 95f2955324aac8698b472c3ee58ed2954357214e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23572 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 DN|&YW diff --git a/docs/src/assets/loads_wye_1ph.pdf b/docs/src/assets/loads_wye_1ph.pdf deleted file mode 100644 index 2a3b516199189eefe45441c01716c3306bfdf731..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16498 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 diff --git a/docs/src/assets/loads_wye_2ph.pdf b/docs/src/assets/loads_wye_2ph.pdf deleted file mode 100644 index c2de6424eb89a756b452ff1401b7d38115f0b080..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16407 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.

+![loads connection example](assets/loads_connection_example.svg). ## Edge objects Edge objects connect two buses (except for generic `transformers`, which can connect `N` buses). Therefore, they have the fields + - `f_bus` and `f_connections`, specifying the from-side bus and how the object connects to it; - `t_bus` and `t_connections`, specifying the same for the to-side. ### Lines + A line can have a variable number of conductors, which is implied by the size of the fields `rs`, `xs`, `g_fr`, `b_fr`, `g_to` and `b_to`. The fields `f_connections` and `t_connections` should specify for each conductor, to which terminals it connects. The figure below illustrates this for a line with 2 conuctors, -

.

+ +![line connection example](assets/line_connection_example.svg). ### Transformers Transformers also have a `configuration` field. For -- generic transformers, this is specified per winding, and `configuration` is therefore a vector of `PMD.ConnConfig` enums (`WYE` or `DELTA`); + +- generic transformers, this is specified per winding, and `configuration` is therefore a vector of `ConnConfig` enums (`WYE` or `DELTA`); - AL2W transformers however are always two-winding, and the secondary is always wye-connected. Therefore, `configuration` is a scalar, specifying the configuration of the from-side winding. Generic transformers have a field `buses`, a Vector containing the buses to which each winding connects respectively (these do not have to be unique; a split-phase transformer is typically represented by having two windings connect to the same bus). The AL2W transformer however, since it is always two-winding, follows the `f_connections`/`t_connections` pattern. diff --git a/docs/src/eng2math.md b/docs/src/eng2math.md index 0296bdae0..3004ae573 100644 --- a/docs/src/eng2math.md +++ b/docs/src/eng2math.md @@ -31,6 +31,7 @@ Switches are parsed into `switch`. If there are loss parameters provided (_i.e._ A transformer can have N windings, each with its own configuration (`delta` or `wye` are supported). This is decomposed to a network of N lossless, two-winding transformers which connect to an internal loss model. The to-winding is always wye-connected, hence we refer to these transformers as 'asymmetric'. The internal loss model is a function of + - the winding resistance `rw`, - the short-circuit reactance `xsc`, - the no-load loss properties `noloadloss` (resistive) and magnetizing current `imag` (reactive). diff --git a/docs/src/formulation-details.md b/docs/src/formulation-details.md index 7ae886f66..72036c9b7 100644 --- a/docs/src/formulation-details.md +++ b/docs/src/formulation-details.md @@ -1,46 +1,58 @@ # Three-phase formulation details - ## `AbstractACPModel` -Real-valued formulation from: -- Formulation without shunts: Mahdad, B., Bouktir, T., & Srairi, K. (2006). A three-phase power flow modelization: a tool for optimal location and control of FACTS devices in unbalanced power systems. In IEEE Industrial Electronics IECON (pp. 2238–2243). +Real-valued formulation from: +- Formulation without shunts: Mahdad, B., Bouktir, T., & Srairi, K. (2006). A three-phase power flow modelization: a tool for optimal location and control of FACTS devices in unbalanced power systems. In IEEE Industrial Electronics IECON (pp. 2238–2243). ## `AbstractDCPModel` + Applying all of the standard DC linearization tricks to the `AbstractACPModel` ## `SOCWRModel` + Applying the standard BIM voltage cross-product (sine and cosine) substitution tricks to `AbstractACPModel` results immediately in a SOC formulation. ## `SDPUBFModel` + The BFM SDP relaxation as described in: -- Gan, L., & Low, S. H. (2014). Convex relaxations and linear approximation for optimal power flow in multiphase radial networks. In PSSC (pp. 1–9). Wroclaw, Poland. https://doi.org/10.1109/PSCC.2014.7038399 + +- Gan, L., & Low, S. H. (2014). Convex relaxations and linear approximation for optimal power flow in multiphase radial networks. In PSSC (pp. 1–9). Wroclaw, Poland. [doi:10.1109/PSCC.2014.7038399](https://doi.org/10.1109/PSCC.2014.7038399) + Note that this formulation is complex-valued and additional steps are needed to implement this in JuMP. ## `SOCNLPUBFModel` + The starting point is `SDPUBFModel`. The SDP constraint can be relaxed to a set of SOC constraints, starting from either the real or complex form of the matrix on which the PSD-ness constraint is applied. -- Kim, S., Kojima, M., & Yamashita, M. (2003). Second order cone programming relaxation of a positive semidefinite constraint. Optimization Methods and Software, 18(5), 535–541. https://doi.org/10.1080/1055678031000148696 -- Andersen, M. S., Hansson, A., & Vandenberghe, L. (2014). Reduced-complexity semidefinite relaxations of optimal power flow problems. IEEE Trans. Power Syst., 29(4), 1855–1863. +- Kim, S., Kojima, M., & Yamashita, M. (2003). Second order cone programming relaxation of a positive semidefinite constraint. Optimization Methods and Software, 18(5), 535–541. [doi:10.1080/1055678031000148696](https://doi.org/10.1080/1055678031000148696) +- Andersen, M. S., Hansson, A., & Vandenberghe, L. (2014). Reduced-complexity semidefinite relaxations of optimal power flow problems. IEEE Trans. Power Syst., 29(4), 1855–1863. ## `SOCConicUBFModel` -See `SOCNLPUBFModel` +See `SOCNLPUBFModel` ## `LPUBFFullModel` + Matrix formulation that generalizes `simplified DistFlow equations`, as introduced in : -- Gan, L., & Low, S. H. (2014). Convex relaxations and linear approximation for optimal power flow in multiphase radial networks. In PSSC (pp. 1–9). Wroclaw, Poland. https://doi.org/10.1109/PSCC.2014.7038399 + +- Gan, L., & Low, S. H. (2014). Convex relaxations and linear approximation for optimal power flow in multiphase radial networks. In PSSC (pp. 1–9). Wroclaw, Poland. [doi:10.1109/PSCC.2014.7038399](https://doi.org/10.1109/PSCC.2014.7038399) Note that this formulation is complex-valued and additional steps are needed to implement this in JuMP. ## `LPUBFDiagModel` + This formulation has originally been developed by Sankur et al. -- Sankur, M. D., Dobbe, R., Stewart, E., Callaway, D. S., & Arnold, D. B. (2016). A linearized power flow model for optimization in unbalanced distribution systems. https://arxiv.org/abs/1606.04492v2 + +- Sankur, M. D., Dobbe, R., Stewart, E., Callaway, D. S., & Arnold, D. B. (2016). A linearized power flow model for optimization in unbalanced distribution systems. [arXiv:1606.04492v2](https://arxiv.org/abs/1606.04492v2) This formulation is here cast as only considering the diagonal elements defined in `LPUBFFullModel`, which furthermore leads to the imaginary part of the lifted node voltage variable W being redundant and substituted out. ## `LPLinUBFModel` + Scalar reformulation of: -- Sankur, M. D., Dobbe, R., Stewart, E., Callaway, D. S., & Arnold, D. B. (2016). A linearized power flow model for optimization in unbalanced distribution systems. https://arxiv.org/abs/1606.04492v2 + +- Sankur, M. D., Dobbe, R., Stewart, E., Callaway, D. S., & Arnold, D. B. (2016). A linearized power flow model for optimization in unbalanced distribution systems. [arXiv:1606.04492v2](https://arxiv.org/abs/1606.04492v2) + This formulation was already derived in real variables and parameters. diff --git a/docs/src/specifications.md b/docs/src/specifications.md index d8ebf799d..288f1876b 100644 --- a/docs/src/specifications.md +++ b/docs/src/specifications.md @@ -18,9 +18,9 @@ objective_min_fuel_cost(pm) variable_mc_voltage(pm) variable_mc_branch_flow(pm) -for c in PMs.conductor_ids(pm) - PMs.variable_generation(pm, cnd=c) - PMs.variable_dcline_flow(pm, cnd=c) +for c in conductor_ids(pm) + PowerModels.variable_generation(pm, cnd=c) + PowerModels.variable_dcline_flow(pm, cnd=c) end variable_mc_transformer_flow(pm) variable_mc_oltc_tap(pm) @@ -31,31 +31,31 @@ variable_mc_oltc_tap(pm) ```julia constraint_mc_model_voltage(pm) -for i in PMs.ids(pm, :ref_buses) +for i in ids(pm, :ref_buses) constraint_mc_theta_ref(pm, i) end -for i in PMs.ids(pm, :bus), c in PMs.conductor_ids(pm) +for i in ids(pm, :bus), c in conductor_ids(pm) constraint_mc_power_balance(pm, i, cnd=c) end -for i in PMs.ids(pm, :branch) +for i in ids(pm, :branch) constraint_mc_ohms_yt_from(pm, i) constraint_mc_ohms_yt_to(pm, i) - for c in PMs.conductor_ids(pm) - PMs.constraint_voltage_angle_difference(pm, i, cnd=c) + for c in conductor_ids(pm) + PowerModels.constraint_voltage_angle_difference(pm, i, cnd=c) - PMs.constraint_thermal_limit_from(pm, i, cnd=c) - PMs.constraint_thermal_limit_to(pm, i, cnd=c) + PowerModels.constraint_thermal_limit_from(pm, i, cnd=c) + PowerModels.constraint_thermal_limit_to(pm, i, cnd=c) end end -for i in PMs.ids(pm, :dcline), c in PMs.conductor_ids(pm) - PMs.constraint_dcline(pm, i, cnd=c) +for i in ids(pm, :dcline), c in conductor_ids(pm) + PowerModels.constraint_dcline(pm, i, cnd=c) end -for i in PMs.ids(pm, :transformer) +for i in ids(pm, :transformer) constraint_mc_oltc(pm, i) end ``` diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index edc6e7be9..66786eef4 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -358,7 +358,7 @@ function constraint_mc_bus_voltage_magnitude_sqr_on_off(pm::_PM.AbstractPowerMod end -"This is duplicated at PMD level to correctly handle the indexing of the shunts." +"This is duplicated at PowerModelsDistribution level to correctly handle the indexing of the shunts." function constraint_mc_voltage_angle_difference(pm::_PM.AbstractPowerModel, i::Int; nw::Int=pm.cnw) branch = ref(pm, nw, :branch, i) f_bus = branch["f_bus"] diff --git a/src/core/objective.jl b/src/core/objective.jl index 71b7b27f6..8fcf4bdb5 100644 --- a/src/core/objective.jl +++ b/src/core/objective.jl @@ -70,3 +70,60 @@ function objective_mc_max_load_setpoint(pm::_PM.AbstractPowerModel) for (n, nw_ref) in nws(pm)) ) end + + +"Multiconductor adaptation of min fuel cost polynomial linquad objective" +function _PM._objective_min_fuel_cost_polynomial_linquad(pm::_PM.AbstractIVRModel; report::Bool=true) + gen_cost = Dict() + dcline_cost = Dict() + + for (n, nw_ref) in nws(pm) + for (i,gen) in nw_ref[:gen] + bus = gen["gen_bus"] + + #to avoid function calls inside of @NLconstraint: + pg = var(pm, n, :pg, i) + nc = length(conductor_ids(pm, n)) + if length(gen["cost"]) == 1 + gen_cost[(n,i)] = gen["cost"][1] + elseif length(gen["cost"]) == 2 + gen_cost[(n,i)] = JuMP.@NLexpression(pm.model, gen["cost"][1]*sum(pg[c] for c in 1:nc) + gen["cost"][2]) + elseif length(gen["cost"]) == 3 + gen_cost[(n,i)] = JuMP.@NLexpression(pm.model, gen["cost"][1]*sum(pg[c] for c in 1:nc)^2 + gen["cost"][2]*sum(pg[c] for c in 1:nc) + gen["cost"][3]) + else + gen_cost[(n,i)] = 0.0 + end + end + end + + return JuMP.@NLobjective(pm.model, Min, + sum( + sum( gen_cost[(n,i)] for (i,gen) in nw_ref[:gen] ) + + sum( dcline_cost[(n,i)] for (i,dcline) in nw_ref[:dcline] ) + for (n, nw_ref) in nws(pm)) + ) +end + + +"adds pg_cost variables and constraints" +function objective_variable_pg_cost(pm::_PM.AbstractIVRModel; report::Bool=true) + for (n, nw_ref) in nws(pm) + gen_lines = calc_cost_pwl_lines(nw_ref[:gen]) + + #to avoid function calls inside of @NLconstraint + pg_cost = var(pm, n)[:pg_cost] = JuMP.@variable(pm.model, + [i in ids(pm, n, :gen)], base_name="$(n)_pg_cost", + ) + report && _IM.sol_component_value(pm, n, :gen, :pg_cost, ids(pm, n, :gen), pg_cost) + + nc = length(conductor_ids(pm, n)) + + # gen pwl cost + for (i, gen) in nw_ref[:gen] + pg = var(pm, n, :pg, i) + for line in gen_lines[i] + JuMP.@NLconstraint(pm.model, pg_cost[i] >= line.slope*sum(pg[c] for c in 1:nc) + line.intercept) + end + end + end +end diff --git a/src/core/ref.jl b/src/core/ref.jl index c5ae43ce9..4bc62fd0c 100644 --- a/src/core/ref.jl +++ b/src/core/ref.jl @@ -41,7 +41,7 @@ function _find_ref_buses(pm::_PM.AbstractPowerModel, nw) end -"Adds arcs for PMD transformers; for dclines and branches this is done in PMs" +"Adds arcs for PowerModelsDistribution transformers; for dclines and branches this is done in PowerModels" function ref_add_arcs_transformer!(ref::Dict{Symbol,<:Any}, data::Dict{String,<:Any}) if _IM.ismultinetwork(data) nws_data = data["nw"] diff --git a/src/form/bf.jl b/src/form/bf.jl index 7182c334c..e018052e2 100644 --- a/src/form/bf.jl +++ b/src/form/bf.jl @@ -1,4 +1,4 @@ -"This is duplicated at PMD level to correctly handle the indexing of the shunts." +"This is duplicated at PowerModelsDistribution level to correctly handle the indexing of the shunts." function constraint_mc_voltage_angle_difference(pm::_PM.AbstractBFModel, n::Int, f_idx, angmin, angmax) i, f_bus, t_bus = f_idx t_idx = (i, t_bus, f_bus) diff --git a/src/form/ivr.jl b/src/form/ivr.jl index e8ae56bc6..8d8f14e7b 100644 --- a/src/form/ivr.jl +++ b/src/form/ivr.jl @@ -374,64 +374,7 @@ function constraint_mc_regen_setpoint_active(pm::_PM.AbstractIVRModel, n::Int, i end -"" -function _PM._objective_min_fuel_cost_polynomial_linquad(pm::_PM.AbstractIVRModel; report::Bool=true) - gen_cost = Dict() - dcline_cost = Dict() - - for (n, nw_ref) in nws(pm) - for (i,gen) in nw_ref[:gen] - bus = gen["gen_bus"] - - #to avoid function calls inside of @NLconstraint: - pg = var(pm, n, :pg, i) - nc = length(conductor_ids(pm, n)) - if length(gen["cost"]) == 1 - gen_cost[(n,i)] = gen["cost"][1] - elseif length(gen["cost"]) == 2 - gen_cost[(n,i)] = JuMP.@NLexpression(pm.model, gen["cost"][1]*sum(pg[c] for c in 1:nc) + gen["cost"][2]) - elseif length(gen["cost"]) == 3 - gen_cost[(n,i)] = JuMP.@NLexpression(pm.model, gen["cost"][1]*sum(pg[c] for c in 1:nc)^2 + gen["cost"][2]*sum(pg[c] for c in 1:nc) + gen["cost"][3]) - else - gen_cost[(n,i)] = 0.0 - end - end - end - - return JuMP.@NLobjective(pm.model, Min, - sum( - sum( gen_cost[(n,i)] for (i,gen) in nw_ref[:gen] ) - + sum( dcline_cost[(n,i)] for (i,dcline) in nw_ref[:dcline] ) - for (n, nw_ref) in nws(pm)) - ) -end - - -"adds pg_cost variables and constraints" -function objective_variable_pg_cost(pm::_PM.AbstractIVRModel; report::Bool=true) - for (n, nw_ref) in nws(pm) - gen_lines = calc_cost_pwl_lines(nw_ref[:gen]) - - #to avoid function calls inside of @NLconstraint - pg_cost = var(pm, n)[:pg_cost] = JuMP.@variable(pm.model, - [i in ids(pm, n, :gen)], base_name="$(n)_pg_cost", - ) - report && _IM.sol_component_value(pm, n, :gen, :pg_cost, ids(pm, n, :gen), pg_cost) - - nc = length(conductor_ids(pm, n)) - - # gen pwl cost - for (i, gen) in nw_ref[:gen] - pg = var(pm, n, :pg, i) - for line in gen_lines[i] - JuMP.@NLconstraint(pm.model, pg_cost[i] >= line.slope*sum(pg[c] for c in 1:nc) + line.intercept) - end - end - end -end - - -"" +"wye-wye transformer power constraint for IVR formulation" function constraint_mc_transformer_power_yy(pm::_PM.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) vr_fr_P = var(pm, nw, :vr, f_bus)[f_cnd] vi_fr_P = var(pm, nw, :vi, f_bus)[f_cnd] @@ -458,7 +401,7 @@ function constraint_mc_transformer_power_yy(pm::_PM.AbstractIVRModel, nw::Int, t end -"" +"delta-wye transformer power constraint for IVR formulation" function constraint_mc_transformer_power_dy(pm::_PM.AbstractIVRModel, nw::Int, trans_id::Int, f_bus::Int, t_bus::Int, f_idx, t_idx, f_cnd, t_cnd, pol, tm_set, tm_fixed, tm_scale) vr_fr_P = var(pm, nw, :vr, f_bus)[f_cnd] vi_fr_P = var(pm, nw, :vi, f_bus)[f_cnd] @@ -487,7 +430,7 @@ function constraint_mc_transformer_power_dy(pm::_PM.AbstractIVRModel, nw::Int, t end -"" +"wye connected load setpoint constraint for IVR formulation" function constraint_mc_load_setpoint_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) @@ -524,7 +467,7 @@ function constraint_mc_load_setpoint_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int end -"" +"delta connected load setpoint constraint for IVR formulation" function constraint_mc_load_setpoint_delta(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, a::Vector{<:Real}, alpha::Vector{<:Real}, b::Vector{<:Real}, beta::Vector{<:Real}; report::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) @@ -566,7 +509,7 @@ function constraint_mc_load_setpoint_delta(pm::_PM.IVRPowerModel, nw::Int, id::I end -"" +"wye connected generator setpoint constraint for IVR formulation" function constraint_mc_gen_setpoint_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) @@ -610,7 +553,7 @@ function constraint_mc_gen_setpoint_wye(pm::_PM.IVRPowerModel, nw::Int, id::Int, end -"" +"delta connected generator setpoint constraint for IVR formulation" function constraint_mc_gen_setpoint_delta(pm::_PM.IVRPowerModel, nw::Int, id::Int, bus_id::Int, pmin::Vector, pmax::Vector, qmin::Vector, qmax::Vector; report::Bool=true, bounded::Bool=true) vr = var(pm, nw, :vr, bus_id) vi = var(pm, nw, :vi, bus_id) diff --git a/src/io/json.jl b/src/io/json.jl index 73f1b12c0..ff8652d06 100644 --- a/src/io/json.jl +++ b/src/io/json.jl @@ -100,7 +100,7 @@ function _parse_enums!(data::Dict{String,<:Any}) end -"Parses a JSON file into a PMD data structure" +"Parses a JSON file into a PowerModelsDistribution data structure" function parse_json(file::String; validate::Bool=false) data = open(file) do io parse_json(io; filetype=split(lowercase(file), '.')[end], validate=validate) @@ -109,7 +109,7 @@ function parse_json(file::String; validate::Bool=false) end -"Parses a JSON file into a PMD data structure" +"Parses a JSON file into a PowerModelsDistribution data structure" function parse_json(io::IO; validate::Bool=false)::Dict{String,Any} data = JSON.parse(io) @@ -127,7 +127,7 @@ function parse_json(io::IO; validate::Bool=false)::Dict{String,Any} end -"prints a PMD data structure into a JSON file" +"prints a PowerModelsDistribution data structure into a JSON file" function print_file(path::String, data::Dict{String,<:Any}; indent::Int=2) open(path, "w") do io print_file(io, data; indent=indent) @@ -135,7 +135,7 @@ function print_file(path::String, data::Dict{String,<:Any}; indent::Int=2) end -"prints a PMD data structure into a JSON file" +"prints a PowerModelsDistribution data structure into a JSON file" function print_file(io::IO, data::Dict{String,<:Any}; indent::Int=2) if indent == 0 JSON.print(io, JSON.parse(sprint(show_json, PMDSerialization(), data))) diff --git a/src/io/utils.jl b/src/io/utils.jl index a7712daf2..47a5b5eb6 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -847,7 +847,7 @@ function _guess_dtype(value::AbstractString)::Type end -"converts dss load model to supported PMD LoadModel enum" +"converts dss load model to supported PowerModelsDistribution LoadModel enum" function _parse_dss_load_model!(eng_obj::Dict{String,<:Any}, id::Any) model = eng_obj["model"] diff --git a/src/prob/common.jl b/src/prob/common.jl index a89501f70..1f4357722 100644 --- a/src/prob/common.jl +++ b/src/prob/common.jl @@ -1,4 +1,4 @@ -"alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" +"alias to run_model in PowerModels with multiconductor=true, and transformer ref extensions added by default" function run_mc_model(data::Dict{String,<:Any}, model_type::Type, solver, build_mc::Function; ref_extensions::Vector{<:Function}=Vector{Function}([]), make_si=!get(data, "per_unit", false), multinetwork::Bool=false, kwargs...)::Dict{String,Any} if get(data, "data_model", MATHEMATICAL) == ENGINEERING data_math = transform_data_model(data; build_multinetwork=multinetwork) @@ -14,7 +14,7 @@ function run_mc_model(data::Dict{String,<:Any}, model_type::Type, solver, build_ end -"alias to run_model in PMs with multiconductor=true, and transformer ref extensions added by default" +"alias to run_model in PowerModels with multiconductor=true, and transformer ref extensions added by default" function run_mc_model(file::String, model_type::Type, solver, build_mc::Function; ref_extensions::Vector{<:Function}=Vector{Function}([]), kwargs...)::Dict{String,Any} return run_mc_model(parse_file(file), model_type, solver, build_mc; ref_extensions=ref_extensions, kwargs...) end diff --git a/test/delta_gens.jl b/test/delta_gens.jl index 2bba50e4f..c0d5544f1 100644 --- a/test/delta_gens.jl +++ b/test/delta_gens.jl @@ -77,12 +77,12 @@ # gen["model"] = 2 # end # - # pm_ivr = PMs.instantiate_model(pmd, PMs.IVRPowerModel, PMD.build_mc_opf, ref_extensions=[PMD.ref_add_arcs_transformer!]) - # sol_ivr = PMs.optimize_model!(pm_ivr, optimizer=ipopt_solver) + # pm_ivr = PowerModels.instantiate_model(pmd, PowerModels.IVRPowerModel, PMD.build_mc_opf, ref_extensions=[PMD.ref_add_arcs_transformer!]) + # sol_ivr = PowerModels.optimize_model!(pm_ivr, optimizer=ipopt_solver) # @assert(sol_1["termination_status"]==LOCALLY_SOLVED) # - # pm_acr = PMs.instantiate_model(pmd, PMs.ACRPowerModel, PMD.build_mc_opf, ref_extensions=[PMD.ref_add_arcs_transformer!]) - # sol_acr = PMs.optimize_model!(pm_acr, optimizer=ipopt_solver) + # pm_acr = PowerModels.instantiate_model(pmd, PowerModels.ACRPowerModel, PMD.build_mc_opf, ref_extensions=[PMD.ref_add_arcs_transformer!]) + # sol_acr = PowerModels.optimize_model!(pm_acr, optimizer=ipopt_solver) # @assert(sol_2["termination_status"]==LOCALLY_SOLVED) # # end From cd01fc456b2a07031aa22a81bf1484878ad2a074 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 12 May 2020 09:32:25 -0600 Subject: [PATCH 222/224] FIX: readme logo size [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e96ac35f..bfef1c1ff 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PowerModelsDistribution.jl -![PowerModelsDistribution logo](docs/src/assets/logo.svg) +PowerModelsDistribution logo Release: [![docs](https://img.shields.io/badge/docs-stable-blue.svg)](https://lanl-ansi.github.io/PowerModelsDistribution.jl/stable/) From 7dfa94bea8b96e842c307b80944d60614ae5a838 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 13 May 2020 07:09:57 -0600 Subject: [PATCH 223/224] FIX: constraint_mc_load_setpoint not passing report constraint_mc_load_setpoint in constraint_template was not passing the report kwarg to the wye and delta variants, fixed Closes #274 --- src/core/constraint_template.jl | 4 ++-- src/form/bf_mx.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/constraint_template.jl b/src/core/constraint_template.jl index 66786eef4..2b2239516 100644 --- a/src/core/constraint_template.jl +++ b/src/core/constraint_template.jl @@ -278,9 +278,9 @@ function constraint_mc_load_setpoint(pm::_PM.AbstractPowerModel, id::Int; nw::In a, alpha, b, beta = _load_expmodel_params(load, bus) if conn==WYE - constraint_mc_load_setpoint_wye(pm, nw, id, load["load_bus"], a, alpha, b, beta) + constraint_mc_load_setpoint_wye(pm, nw, id, load["load_bus"], a, alpha, b, beta; report=report) else - constraint_mc_load_setpoint_delta(pm, nw, id, load["load_bus"], a, alpha, b, beta) + constraint_mc_load_setpoint_delta(pm, nw, id, load["load_bus"], a, alpha, b, beta; report=report) end end diff --git a/src/form/bf_mx.jl b/src/form/bf_mx.jl index 8fe03bb85..73218ad9c 100644 --- a/src/form/bf_mx.jl +++ b/src/form/bf_mx.jl @@ -705,7 +705,7 @@ end Creates the constraints modelling the (relaxed) voltage-dependent loads for the matrix KCL formulation. """ -function constraint_mc_load_setpoint(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int=pm.cnw) +function constraint_mc_load_setpoint(pm::SDPUBFKCLMXModel, load_id::Int; nw::Int=pm.cnw, report::Bool=true) # shared variables and parameters load = ref(pm, nw, :load, load_id) pd0 = load["pd"] From e339de7ab0884e15b0412476eaeaffa4ea35e5fb Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Wed, 13 May 2020 07:19:11 -0600 Subject: [PATCH 224/224] REF: `imag` -> `cmag` on transformers Addresses review comments --- docs/src/eng-data-model.md | 2 +- docs/src/engineering_model.md | 22 +++++++++++----------- src/data_model/components.jl | 2 +- src/data_model/eng2math.jl | 2 +- src/data_model/transformations.jl | 2 +- src/io/opendss.jl | 4 ++-- src/io/utils.jl | 2 +- test/data_model.jl | 2 +- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/src/eng-data-model.md b/docs/src/eng-data-model.md index 157346349..23b31b9c2 100644 --- a/docs/src/eng-data-model.md +++ b/docs/src/eng-data-model.md @@ -173,7 +173,7 @@ These are n-winding (`nwinding`), n-phase (`nphase`), lossy transformers. Note t | `xfmrcode` | | `Any` | | always | id of | | `xsc` | `zeros(nwindings*(nwindings-1)/2)` | `Vector{Real}` | `sm_nom[1]` | always | List of short-circuit reactances between each pair of windings, relative to the VA rating of the first winding; enter as a list of the upper-triangle elements | | `rw` | `zeros(nwindings)` | `Vector{Real}` | `sm_nom[1]` | always | Active power lost due to resistance of each winding, relative to the VA rating of each winding winding | -| `imag` | `0.0` | `Real` | `sm_nom[1]` | always | Total no-load reactive power drawn by the transformer, relative to VA rating of the first winding | +| `cmag` | `0.0` | `Real` | `sm_nom[1]` | always | Total no-load reactive power drawn by the transformer, relative to VA rating of the first winding (magnetizing current) | | `noloadloss` | `0.0` | `Real` | `sm_nom[1]` | always | Total no-load active power drawn by the transformer, relative to VA rating of the first winding | | `tm_nom` | `ones(nwindings)` | `Vector{Real}` | | always | Nominal tap ratio for the transformer, `size=nwindings` (multiplier) | | `tm_ub` | | `Vector{Vector{Real}}` | | opf | Maximum tap ratio for each winding and phase, `size=((nphases),nwindings)` (base=`tm_nom`) | diff --git a/docs/src/engineering_model.md b/docs/src/engineering_model.md index 474a75efb..f3963813b 100644 --- a/docs/src/engineering_model.md +++ b/docs/src/engineering_model.md @@ -106,11 +106,11 @@ eng["bus"] -We can see there are three buses in this system, identified by ids `"primary"`, `"sourcebus"`, and `"loadbus"`. +We can see there are three buses in this system, identified by ids `"primary"`, `"sourcebus"`, and `"loadbus"`. -__NOTE__: In Julia, order of Dictionary keys is not fixed, nor does it retain the order in which it was parsed like _e.g._ `Vectors`. +__NOTE__: In Julia, order of Dictionary keys is not fixed, nor does it retain the order in which it was parsed like _e.g._ `Vectors`. -Identifying components by non-integer names is a new feature of the `ENGINEERING` model, and makes network debugging more straightforward. +Identifying components by non-integer names is a new feature of the `ENGINEERING` model, and makes network debugging more straightforward. __NOTE__: all names are converted to lowercase on parse from the originating dss file. @@ -334,7 +334,7 @@ eng_ts["load"]["l1"]["time_series"] -You can see that under the actual component, in this case a `"load"`, that there is a `"time_series"` dictionary that contains `ENGINEERING` model variable names and references to the identifiers of a root-level `time_series` object, +You can see that under the actual component, in this case a `"load"`, that there is a `"time_series"` dictionary that contains `ENGINEERING` model variable names and references to the identifiers of a root-level `time_series` object, ```julia @@ -373,13 +373,13 @@ result = run_mc_opf(eng, ACPPowerModel, ipopt_solver) ``` [warn | PowerModels]: Updated generator 1 cost function with order 3 to a function of order 2: [0.5, 0.0] - + ****************************************************************************** This program contains Ipopt, a library for large-scale nonlinear optimization. Ipopt is released as open source code under the Eclipse Public License (EPL). For more information visit http://projects.coin-or.org/Ipopt ****************************************************************************** - + @@ -544,11 +544,11 @@ eng_ut["transformer"]["tx1"] "tm_fix" => Array{Bool,1}[[1, 1, 1], [1, 1, 1]] "vm_nom" => [11.0, 4.0] "tm_ub" => [[1.1, 1.1, 1.1], [1.1, 1.1, 1.1]] - "imag" => 0.11 + "cmag" => 0.11 -We can see that `"noloadloss"`, `"rw"`, and `"imag"` are non-zero, but if we apply the `make_lossless!` function we can see these parameters are set to zero, effectively eliminating the losses +We can see that `"noloadloss"`, `"rw"`, and `"cmag"` are non-zero, but if we apply the `make_lossless!` function we can see these parameters are set to zero, effectively eliminating the losses ```julia @@ -576,7 +576,7 @@ eng_ut["transformer"]["tx1"] "tm_fix" => Array{Bool,1}[[1, 1, 1], [1, 1, 1]] "vm_nom" => [11.0, 4.0] "tm_ub" => [[1.1, 1.1, 1.1], [1.1, 1.1, 1.1]] - "imag" => 0.0 + "cmag" => 0.0 @@ -613,7 +613,7 @@ eng_ut["transformer"]["tx1"] "tm_fix" => Array{Bool,1}[[1, 1, 1], [1, 1, 1]] "vm_nom" => [11.0, 4.0] "tm_ub" => [[1.1, 1.1, 1.1], [1.1, 1.1, 1.1]] - "imag" => 0.0 + "cmag" => 0.0 @@ -776,7 +776,7 @@ math = parse_file("../test/data/opendss/case3_unbalanced.dss"; data_model=MATHEM In this subsection we cover parsing into a multinetwork data structure, which is a structure that only exists in the `MATHEMATICAL` model -For those unfamiliar, the InfrastructureModels family of packages has a feature called multinetworks, which is useful for, among other things, running optimization problems on time series type problems. +For those unfamiliar, the InfrastructureModels family of packages has a feature called multinetworks, which is useful for, among other things, running optimization problems on time series type problems. Multinetwork data structures are formatted like so mn = Dict{String,Any}( diff --git a/src/data_model/components.jl b/src/data_model/components.jl index 2fff014af..b73120016 100644 --- a/src/data_model/components.jl +++ b/src/data_model/components.jl @@ -425,7 +425,7 @@ function create_transformer(buses::Vector{Any}, connections::Vector{Union{Vector "configurations" => !ismissing(configurations) ? configurations : fill(WYE, n_windings), "xsc" => !ismissing(xsc) ? xsc : zeros(Int(n_windings * (n_windings-1)//2)), "rw" => !ismissing(rw) ? rw : zeros(n_windings), - "imag" => imag, + "cmag" => imag, "noloadloss" => noloadloss, "tm_nom" => !ismissing(tm_nom) ? tm_nom : ones(n_windings), "tm_set" => !ismissing(tm_set) ? tm_set : fill(fill(1.0, n_conductors), n_windings), diff --git a/src/data_model/eng2math.jl b/src/data_model/eng2math.jl index df7cdd992..2c8cd1085 100644 --- a/src/data_model/eng2math.jl +++ b/src/data_model/eng2math.jl @@ -330,7 +330,7 @@ function _map_eng2math_transformer!(data_math::Dict{String,<:Any}, data_eng::Dic r_s = eng_obj["rw"] .* zbase g_sh = (eng_obj["noloadloss"]*snom[1])/vnom[1]^2 - b_sh = -(eng_obj["imag"]*snom[1])/vnom[1]^2 + b_sh = -(eng_obj["cmag"]*snom[1])/vnom[1]^2 # data is measured externally, but we now refer it to the internal side ratios = vnom/data_eng["settings"]["voltage_scale_factor"] diff --git a/src/data_model/transformations.jl b/src/data_model/transformations.jl index bc9c4bc0d..5e8f0004b 100644 --- a/src/data_model/transformations.jl +++ b/src/data_model/transformations.jl @@ -3,7 +3,7 @@ const _loss_model_objects = Dict{String,Vector{String}}( "switch" => Vector{String}(["linecode", "rs", "xs"]), "voltage_source" => Vector{String}(["rs", "xs"]), - "transformer" => Vector{String}(["rw", "xsc", "imag", "noloadloss"]) + "transformer" => Vector{String}(["rw", "xsc", "cmag", "noloadloss"]) ) diff --git a/src/io/opendss.jl b/src/io/opendss.jl index 2ec6d6a27..e260e7921 100644 --- a/src/io/opendss.jl +++ b/src/io/opendss.jl @@ -524,7 +524,7 @@ function _dss2eng_xfmrcode!(data_eng::Dict{String,<:Any}, data_dss::Dict{String, "configuration" => Vector{ConnConfig}(defaults["conns"]), "rw" => Vector{Float64}(defaults["%rs"] ./ 100), "noloadloss" => defaults["%noloadloss"] / 100, - "imag" => defaults["%imag"] / 100, + "cmag" => defaults["%imag"] / 100, "xsc" => nrw == 2 ? [defaults["xhl"] / 100] : [defaults["xhl"], defaults["xht"], defaults["xlt"]] ./ 100, "source_id" => "xfmrcode.$id", ) @@ -619,7 +619,7 @@ function _dss2eng_transformer!(data_eng::Dict{String,<:Any}, data_dss::Dict{Stri end # %noloadloss, %imag - for (fr_key, to_key) in zip(["%noloadloss", "%imag"], ["noloadloss", "imag"]) + for (fr_key, to_key) in zip(["%noloadloss", "%imag"], ["noloadloss", "cmag"]) if isempty(defaults["xfmrcode"]) || (haskey(dss_obj, fr_key) && _is_after_xfmrcode(dss_obj["prop_order"], fr_key)) eng_obj[to_key] = defaults[fr_key] / 100 end diff --git a/src/io/utils.jl b/src/io/utils.jl index 47a5b5eb6..0c2223740 100644 --- a/src/io/utils.jl +++ b/src/io/utils.jl @@ -322,7 +322,7 @@ function _bank_transformers!(data_eng::Dict{String,<:Any}) _apply_xfmrcode!(tr, data_eng) end # across-phase properties should be the same to be eligible for banking - props = ["bus", "noloadloss", "xsc", "rw", "imag", "vm_nom", "sm_nom", "polarity", "configuration"] + props = ["bus", "noloadloss", "xsc", "rw", "cmag", "vm_nom", "sm_nom", "polarity", "configuration"] btrans = Dict{String, Any}(prop=>trs[1][prop] for prop in props) if !all(tr[prop]==btrans[prop] for tr in trs, prop in props) Memento.warn(_LOGGER, "Not all across-phase properties match among transfomers identified by bank='$bank', aborting attempt to bank") diff --git a/test/data_model.jl b/test/data_model.jl index aa38c9063..7a7691dfe 100644 --- a/test/data_model.jl +++ b/test/data_model.jl @@ -57,7 +57,7 @@ eng = parse_file("../test/data/opendss/ut_trans_2w_yy.dss"; transformations=[make_lossless!, (apply_voltage_bounds!, "vm_lb"=>0.95, "vm_ub"=>1.05)]) - @test all(all(eng["transformer"]["tx1"][k] .== 0) for k in ["rw", "xsc", "noloadloss", "imag"]) + @test all(all(eng["transformer"]["tx1"][k] .== 0) for k in ["rw", "xsc", "noloadloss", "cmag"]) math = transform_data_model(eng)