Skip to content

Commit 30e3d39

Browse files
ccoffrinmtanneaumtanneau3hei06j
authored
Release v0.21.2 (#919)
* Improve performance of calc_connected_components (#914) --------- Co-authored-by: mtanneau3 <[email protected]> * Add in-place conversion to basic data format (#915) * Add in-place conversion to basic data format * Add unit test for in-place make_basic_network --------- Co-authored-by: mtanneau3 <[email protected]> * No deepcopy when renumbering components (#916) * No deepcopy when renumbering components * In-place _renumber_components and unit tests * Fix typo --------- Co-authored-by: mtanneau3 <[email protected]> Co-authored-by: Carleton Coffrin <[email protected]> * add notes to changelog and prep for release * Update psse parser for 3winding transformers (#917) * fix psse parser 3 winding tranformers * update readme * add note to changelog and readme * fix quote counting check in psse parser --------- Co-authored-by: Mathieu Tanneau <[email protected]> Co-authored-by: mtanneau3 <[email protected]> Co-authored-by: Rahmat Heidari <[email protected]>
1 parent 094e1b9 commit 30e3d39

14 files changed

+327
-40
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ PowerModels.jl Change Log
44
### Staged
55
- nothing
66

7+
### v0.21.2
8+
- In place building of basic network data (#915,#916)
9+
- Performance improvements to `calc_connected_components` (#914)
10+
- Fix three winding transformer parsing in psse data (#917)
11+
- Fix quote counting check in psse parser (#920)
12+
713
### v0.21.1
814
- Fix bug in `calc_theta_delta_bounds` (#907)
915

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "PowerModels"
22
uuid = "c36e90e8-916a-50a6-bd94-075b64ef4655"
33
authors = ["Carleton Coffrin"]
44
repo = "https://github.com/lanl-ansi/PowerModels.jl"
5-
version = "0.21.1"
5+
version = "0.21.2"
66

77
[deps]
88
InfrastructureModels = "2030c09a-7f63-5d83-885d-db604e0e9cc0"

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,12 @@ The primary developer is Carleton Coffrin (@ccoffrin) with support from the foll
6161
- David Fobes (@pseudocubic) LANL, PSS(R)E v33 data support
6262
- Rory Finnegan (@rofinn) Invenia, Memento Logging
6363
- Frederik Geth (@frederikgeth) CSIRO, storage modeling advise, Branch Flow and current-voltage formulation
64+
- Rahmat Heidari (@hei06j) CSIRO, improved PSS(R)E data support
6465
- Jonas Kersulis (@kersulis) University of Michigan, Sparse SDP formulation
6566
- Miles Lubin (@mlubin) MIT, Julia/JuMP advise
6667
- Yeesian Ng (@yeesian) MIT, Documenter.jl setup
6768
- Kaarthik Sundar (@kaarthiksundar) LANL, OBBT utility
68-
- Mathieu Tanneau (@mtanneau) Georgia Tech, PTDF matrix computation
69+
- Mathieu Tanneau (@mtanneau) Georgia Tech, PTDF matrix computation, performance and memory improvements
6970
- Byron Tasseff (@tasseff) LANL, multi-infrastructure updates
7071

7172

src/core/data.jl

+34-7
Original file line numberDiff line numberDiff line change
@@ -2526,16 +2526,43 @@ function calc_connected_components(data::Dict{String,<:Any}; edges=["branch", "d
25262526
end
25272527
end
25282528

2529-
component_lookup = Dict(i => Set{Int}([i]) for i in active_bus_ids)
2530-
touched = Set{Int}()
2531-
2532-
for i in active_bus_ids
2533-
if !(i in touched)
2534-
_cc_dfs(i, neighbors, component_lookup, touched)
2529+
sorted_bus_ids = sort(collect(active_bus_ids))
2530+
i0 = sorted_bus_ids[1] - 1 # this is to track un-visited buses
2531+
# The two dictionaries below are used to track the connected components
2532+
# `component_lookup` maps each bus to the ID of the connected component it belongs to, which is the smallest bus ID in said components
2533+
# `components` maps the connected component ID to a `Set{Int}` of all bus IDs in that component
2534+
component_lookup = Dict(i => i0 for i in active_bus_ids)
2535+
components = Dict{Int,Set{Int}}()
2536+
2537+
# ⚠️ it is important to iterate over _sorted_ bus IDs to ensure that components are labeled correctly
2538+
for i in sorted_bus_ids
2539+
if component_lookup[i] != i0
2540+
continue # bus already flagged; skip
2541+
end
2542+
2543+
# We have a new connected component!
2544+
component_lookup[i] = i
2545+
components[i] = Set([i])
2546+
V = [i] # list of all bus IDs in this connected component
2547+
while length(V) > 0
2548+
j = pop!(V)
2549+
for k in neighbors[j]
2550+
if component_lookup[k] == i0
2551+
# Bus `k` is connected to a bus `j` that's connected to `i`
2552+
push!(V, k)
2553+
component_lookup[k] = i
2554+
push!(components[i], k)
2555+
else
2556+
# Bus was already visited
2557+
# Check that it's in the right connected component, and move on
2558+
@assert component_lookup[k] == i "Unexpected error with bus $k while computing connected components; please report this as a bug."
2559+
continue
2560+
end
2561+
end
25352562
end
25362563
end
25372564

2538-
ccs = (Set(values(component_lookup)))
2565+
ccs = (Set(values(components)))
25392566

25402567
return ccs
25412568
end

src/core/data_basic.jl

+33-12
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,38 @@ users requiring any of the features listed above for their analysis should use
1717
the non-basic PowerModels routines.
1818
"""
1919
function make_basic_network(data::Dict{String,<:Any})
20+
# These initial checks are redundant with the checks in make_basic_network!
21+
# We keep them here so they run _before_ we create a deepcopy of the data
22+
# The checks are fast so this redundancy has little performance impact
2023
if _IM.ismultiinfrastructure(data)
2124
Memento.error(_LOGGER, "make_basic_network does not support multiinfrastructure data")
2225
end
23-
26+
2427
if _IM.ismultinetwork(data)
2528
Memento.error(_LOGGER, "make_basic_network does not support multinetwork data")
2629
end
2730

28-
# make a copy of data so that modifications do not change the input data
2931
data = deepcopy(data)
3032

33+
make_basic_network!(data)
34+
35+
return data
36+
end
37+
38+
"""
39+
given a powermodels data dict, modifies it in-place to conform to basic network model requirements.
40+
41+
See [`make_basic_network`](@ref) for more information.
42+
"""
43+
function make_basic_network!(data::Dict{String,<:Any})
44+
if _IM.ismultiinfrastructure(data)
45+
Memento.error(_LOGGER, "make_basic_network does not support multiinfrastructure data")
46+
end
47+
48+
if _IM.ismultinetwork(data)
49+
Memento.error(_LOGGER, "make_basic_network does not support multinetwork data")
50+
end
51+
3152
# TODO transform PWL costs into linear costs
3253
for (i,gen) in data["gen"]
3354
if get(gen, "cost_model", 2) != 2
@@ -110,7 +131,7 @@ function make_basic_network(data::Dict{String,<:Any})
110131
# re-number non-bus component ids
111132
for comp_key in keys(pm_component_status)
112133
if comp_key != "bus"
113-
data[comp_key] = _renumber_components(data[comp_key])
134+
data[comp_key] = _renumber_components!(data[comp_key])
114135
end
115136
end
116137

@@ -148,18 +169,18 @@ end
148169
given a component dict returns a new dict where components have been renumbered
149170
from 1-to-n ordered by the increasing values of the orginal component id.
150171
"""
151-
function _renumber_components(comp_dict::Dict{String,<:Any})
152-
renumbered_dict = Dict{String,Any}()
153-
154-
comp_ordered = sort([comp for (i,comp) in comp_dict], by=(x) -> x["index"])
172+
function _renumber_components!(comp_dict::Dict{String,<:Any})
173+
comp_ordered = sort([(comp["index"], comp) for (i, comp) in comp_dict], by=(x) -> x[1])
155174

156-
for (i,comp) in enumerate(comp_ordered)
157-
comp = deepcopy(comp)
158-
comp["index"] = i
159-
renumbered_dict["$i"] = comp
175+
# Delete existing keys
176+
empty!(comp_dict)
177+
# Update component indices and re-build the dict keys
178+
for (i_new, (i_old, comp)) in enumerate(comp_ordered)
179+
comp["index"] = i_new
180+
comp_dict["$(i_new)"] = comp
160181
end
161182

162-
return renumbered_dict
183+
return comp_dict
163184
end
164185

165186

src/io/psse.jl

+58-17
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,13 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool)
434434
else
435435
br_r, br_x = transformer["R1-2"], transformer["X1-2"]
436436
end
437-
br_r *= (transformer["NOMV1"]^2 / _get_bus_value(transformer["I"], "base_kv", pm_data)^2) * (pm_data["baseMVA"] / transformer["SBASE1-2"])
438-
br_x *= (transformer["NOMV1"]^2 / _get_bus_value(transformer["I"], "base_kv", pm_data)^2) * (pm_data["baseMVA"] / transformer["SBASE1-2"])
437+
if isapprox(transformer["NOMV1"], 0.0)
438+
br_r *= (pm_data["baseMVA"] / transformer["SBASE1-2"])
439+
br_x *= (pm_data["baseMVA"] / transformer["SBASE1-2"])
440+
else
441+
br_r *= (transformer["NOMV1"]^2 / _get_bus_value(transformer["I"], "base_kv", pm_data)^2) * (pm_data["baseMVA"] / transformer["SBASE1-2"])
442+
br_x *= (transformer["NOMV1"]^2 / _get_bus_value(transformer["I"], "base_kv", pm_data)^2) * (pm_data["baseMVA"] / transformer["SBASE1-2"])
443+
end
439444
end
440445

441446
# Zeq scaling for tap2 (see eq (4.21b) in PROGRAM APPLICATION GUIDE 1 in PSSE installation folder)
@@ -448,8 +453,13 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool)
448453
br_r *= (transformer["WINDV2"]/_get_bus_value(transformer["J"], "base_kv", pm_data))^2
449454
br_x *= (transformer["WINDV2"]/_get_bus_value(transformer["J"], "base_kv", pm_data))^2
450455
else # "for off-nominal turns ratio in pu of nominal winding voltage, NOMV1, NOMV2 and NOMV3."
451-
br_r *= (transformer["WINDV2"]*(transformer["NOMV2"]/_get_bus_value(transformer["J"], "base_kv", pm_data)))^2
452-
br_x *= (transformer["WINDV2"]*(transformer["NOMV2"]/_get_bus_value(transformer["J"], "base_kv", pm_data)))^2
456+
if isapprox(transformer["NOMV2"], 0.0)
457+
br_r *= transformer["WINDV2"]^2
458+
br_x *= transformer["WINDV2"]^2
459+
else
460+
br_r *= (transformer["WINDV2"]*(transformer["NOMV2"]/_get_bus_value(transformer["J"], "base_kv", pm_data)))^2
461+
br_x *= (transformer["WINDV2"]*(transformer["NOMV2"]/_get_bus_value(transformer["J"], "base_kv", pm_data)))^2
462+
end
453463
end
454464
end
455465

@@ -474,23 +484,31 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool)
474484
if sub_data["rate_c"] == 0.0
475485
delete!(sub_data, "rate_c")
476486
end
477-
487+
478488
if import_all
479489
sub_data["windv1"] = transformer["WINDV1"]
480490
sub_data["windv2"] = transformer["WINDV2"]
481491
sub_data["nomv1"] = transformer["NOMV1"]
482492
sub_data["nomv2"] = transformer["NOMV2"]
483493
end
484494

495+
# Assumes CW = 1, namely, for off-nominal turns ratio in pu of windning bus base voltage
485496
sub_data["tap"] = pop!(transformer, "WINDV1") / pop!(transformer, "WINDV2")
486497
sub_data["shift"] = pop!(transformer, "ANG1")
487498

488499
# Unit Transformations
489-
if transformer["CW"] != 1 # NOT "for off-nominal turns ratio in pu of winding bus base voltage"
500+
if transformer["CW"] == 1 # "for off-nominal turns ratio in pu of winding bus base voltage"
501+
# do nothing
502+
elseif transformer["CW"] == 2 # "for winding voltage in kv"
490503
sub_data["tap"] *= _get_bus_value(transformer["J"], "base_kv", pm_data) / _get_bus_value(transformer["I"], "base_kv", pm_data)
491-
if transformer["CW"] == 3 # "for off-nominal turns ratio in pu of nominal winding voltage, NOMV1, NOMV2 and NOMV3."
492-
sub_data["tap"] *= transformer["NOMV1"] / transformer["NOMV2"]
504+
elseif transformer["CW"] == 3 # "for off-nominal turns ratio in pu of nominal winding voltage, NOMV1, NOMV2 and NOMV3."
505+
if !iszero(transformer["NOMV1"]) && !iszero(transformer["NOMV2"])
506+
sub_data["tap"] *= (transformer["NOMV1"] / transformer["NOMV2"]) * (_get_bus_value(transformer["J"], "base_kv", pm_data) / _get_bus_value(transformer["I"], "base_kv", pm_data))
507+
else
508+
# do nothing
493509
end
510+
else
511+
error(_LOGGER, "psse data parsing error, unsupported value for `CW` on transformer")
494512
end
495513

496514
if import_all
@@ -544,13 +562,29 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool)
544562

545563
# Unit Transformations
546564
if transformer["CZ"] != 1 # NOT "for resistance and reactance in pu on system MVA base and winding voltage base"
547-
br_r12 *= (transformer["NOMV1"] / _get_bus_value(bus_id1, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE1-2"])
548-
br_r23 *= (transformer["NOMV2"] / _get_bus_value(bus_id2, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE2-3"])
549-
br_r31 *= (transformer["NOMV3"] / _get_bus_value(bus_id3, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE3-1"])
565+
if isapprox(transformer["NOMV1"], 0.0)
566+
br_r12 *= (pm_data["baseMVA"] / transformer["SBASE1-2"])
567+
br_x12 *= (pm_data["baseMVA"] / transformer["SBASE1-2"])
568+
else
569+
br_r12 *= (transformer["NOMV1"] / _get_bus_value(bus_id1, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE1-2"])
570+
br_x12 *= (transformer["NOMV1"] / _get_bus_value(bus_id1, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE1-2"])
571+
end
550572

551-
br_x12 *= (transformer["NOMV1"] / _get_bus_value(bus_id1, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE1-2"])
552-
br_x23 *= (transformer["NOMV2"] / _get_bus_value(bus_id2, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE2-3"])
553-
br_x31 *= (transformer["NOMV3"] / _get_bus_value(bus_id3, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE3-1"])
573+
if isapprox(transformer["NOMV2"], 0.0)
574+
br_r23 *= (pm_data["baseMVA"] / transformer["SBASE2-3"])
575+
br_x23 *= (pm_data["baseMVA"] / transformer["SBASE2-3"])
576+
else
577+
br_r23 *= (transformer["NOMV2"] / _get_bus_value(bus_id2, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE2-3"])
578+
br_x23 *= (transformer["NOMV2"] / _get_bus_value(bus_id2, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE2-3"])
579+
end
580+
581+
if isapprox(transformer["NOMV3"], 0.0)
582+
br_r31 *= (pm_data["baseMVA"] / transformer["SBASE3-1"])
583+
br_x31 *= (pm_data["baseMVA"] / transformer["SBASE3-1"])
584+
else
585+
br_r31 *= (transformer["NOMV3"] / _get_bus_value(bus_id3, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE3-1"])
586+
br_x31 *= (transformer["NOMV3"] / _get_bus_value(bus_id3, "base_kv", pm_data))^2 * (pm_data["baseMVA"] / transformer["SBASE3-1"])
587+
end
554588
end
555589

556590
# See "Power System Stability and Control", ISBN: 0-07-035958-X, Eq. 6.72
@@ -600,11 +634,18 @@ function _psse2pm_transformer!(pm_data::Dict, pti_data::Dict, import_all::Bool)
600634
sub_data["shift"] = pop!(transformer, "ANG$m")
601635

602636
# Unit Transformations
603-
if transformer["CW"] != 1 # NOT "for off-nominal turns ratio in pu of winding bus base voltage"
637+
if transformer["CW"] == 1 # "for off-nominal turns ratio in pu of winding bus base voltage"
638+
# do nothing
639+
elseif transformer["CW"] == 2 # "for winding voltage in kv"
604640
sub_data["tap"] /= _get_bus_value(bus_id, "base_kv", pm_data)
605-
if transformer["CW"] == 3 # "for off-nominal turns ratio in pu of nominal winding voltage, NOMV1, NOMV2 and NOMV3."
606-
sub_data["tap"] *= transformer["NOMV$m"]
641+
elseif transformer["CW"] == 3 # "for off-nominal turns ratio in pu of nominal winding voltage, NOMV1, NOMV2 and NOMV3."
642+
if !iszero(transformer["NOMV$m"])
643+
sub_data["tap"] *= transformer["NOMV$m"] / _get_bus_value(bus_id, "base_kv", pm_data)
644+
else
645+
# do nothing
607646
end
647+
else
648+
error(_LOGGER, "psse data parsing error, unsupported value for `CW` on transformer")
608649
end
609650

610651
if import_all

src/io/pti.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -607,8 +607,8 @@ Comments, typically indicated at the end of a line with a `'/'` character,
607607
are also extracted separately, and `Array{Array{String}, String}` is returned.
608608
"""
609609
function _get_line_elements(line::AbstractString)
610-
if count(i->(i=="'"), line) % 2 == 1
611-
throw(Memento.error(_LOGGER, "There are an uneven number of single-quotes in \"{line}\", the line cannot be parsed."))
610+
if count(i->(i=='\''), line) % 2 == 1
611+
throw(Memento.error(_LOGGER, "There are an uneven number of single-quotes in \"$line\", the line cannot be parsed."))
612612
end
613613

614614
line_comment = split(line, _comment_split, limit=2)

test/data-basic.jl

+51
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,57 @@
3535
@test isapprox(result["objective"], 16551.7; atol=1e0)
3636
end
3737

38+
@testset "Re-number components in-place" begin
39+
data = PowerModels.parse_file("../test/data/matpower/case7_tplgy.m")
40+
41+
# Scramble indices before renumbering
42+
v = [data["gen"]["$i"] for i in 1:3]
43+
data["gen"]["1"]["index"] = 2
44+
data["gen"]["2"]["index"] = 3
45+
data["gen"]["3"]["index"] = 1
46+
47+
d = PowerModels._renumber_components!(data["gen"])
48+
@test length(data["gen"]) == 3
49+
# Check index consistency
50+
@test data["gen"]["1"]["index"] == 1
51+
@test data["gen"]["2"]["index"] == 2
52+
@test data["gen"]["3"]["index"] == 3
53+
# Check that modifications were done in-place
54+
@test d === data["gen"]
55+
@test data["gen"]["1"] === v[3]
56+
@test data["gen"]["2"] === v[1]
57+
@test data["gen"]["3"] === v[2]
58+
end
59+
60+
@testset "In-place make_basic_network!" begin
61+
data = PowerModels.parse_file("../test/data/matpower/case7_tplgy.m")
62+
data_ = deepcopy(data)
63+
64+
# Test #1: `make_basic_network` is non-destructive
65+
data_basic = PowerModels.make_basic_network(data)
66+
@test data == data_ # input data not modified
67+
@test data_basic !== data # we created a new dict
68+
69+
# Test #2: `make_basic_network!` is destructive and works in-place
70+
data_basic = PowerModels.make_basic_network!(data)
71+
@test data_basic === data # in-place modification
72+
73+
# Test #3: double-check the output of `make_basic_network!`
74+
# This may be redundant with the above tests, unless `make_basic_network` does not
75+
# call the in-place version under the hood
76+
@test length(data_basic["bus"]) == 4
77+
@test length(data_basic["load"]) == 2
78+
@test length(data_basic["shunt"]) == 2
79+
@test length(data_basic["gen"]) == 1
80+
@test length(data_basic["branch"]) == 2
81+
@test length(data_basic["dcline"]) == 0
82+
@test length(data_basic["switch"]) == 0
83+
@test length(data_basic["storage"]) == 0
84+
85+
result = solve_opf(data_basic, ACPPowerModel, nlp_solver)
86+
@test isapprox(result["objective"], 1036.52; atol=1e0)
87+
end
88+
3889
end
3990

4091

0 commit comments

Comments
 (0)