Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/pm_io/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,29 @@
import_all = false,
validate = true,
correct_branch_rating = true,
solved_case = false,
)

Parses a Matpower .m `file` or PTI (PSS(R)E-v33) .raw `file` into a
PowerModels data structure. All fields from PTI files will be imported if
`import_all` is true (Default: false).
`import_all` is true (Default: false). Set `solved_case` when a .raw file was written out
after a converged power flow: its switched shunts then take BINIT as their solved
admittance rather than reconstructing one from the engaged blocks.
"""
function parse_file(
file::String;
import_all = false,
validate = true,
correct_branch_rating = true,
solved_case = false,
)
pm_data = open(file) do io
pm_data = parse_file(
io;
import_all = import_all,
validate = validate,
correct_branch_rating = correct_branch_rating,
solved_case = solved_case,
filetype = split(lowercase(file), '.')[end],
)
end
Expand All @@ -34,6 +39,7 @@ function parse_file(
import_all = false,
validate = true,
correct_branch_rating = true,
solved_case = false,
filetype = "json",
)
if filetype == "m"
Expand All @@ -44,6 +50,7 @@ function parse_file(
import_all = import_all,
validate = validate,
correct_branch_rating = correct_branch_rating,
solved_case = solved_case,
)
elseif filetype == "json"
pm_data = parse_json(io; validate = validate)
Expand Down
139 changes: 90 additions & 49 deletions src/pm_io/psse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -828,12 +828,52 @@ function _psse2pm_load!(pm_data::Dict, pti_data::Dict, import_all::Bool, nb)
end

"""
Whether `key` names one of a SWITCHED SHUNT record's up-to-eight admittance-block columns
for `prefix` — `N1`..`N8` (step counts) or `B1`..`B8` (admittances). Exact, so a future
column merely starting with the same letter is not swept in.
The admittance blocks a SWITCHED SHUNT record defines, in the order they switch on.

PSS(R)E describes block `i` by a step count `Ni` and a per-step admittance `Bi` (plus, in
v35, a status `Si`), and reads them as a contiguous run starting at block 1.
"""
function _is_switched_shunt_block(key::AbstractString, prefix::Char)
return ncodeunits(key) == 2 && key[1] == prefix && '1' <= key[2] <= '8'
function _switched_shunt_blocks(switched_shunt::Dict)
step_number = Int[]
y_increment = Float64[]
for i in 1:8
steps = get(switched_shunt, "N$i", 0)
increment = get(switched_shunt, "B$i", 0.0)
# Edge case: per the POM, a zero value for `Ni` or `Bi` terminates the list early.
(steps == 0 || increment == 0.0) && break
push!(step_number, steps)
push!(y_increment, increment)
end
return step_number, y_increment
end

# PSS(R)E MODSW codes that adjust the shunt in discrete steps: 1 (local voltage) and the
# remote-quantity modes 3/4/5. Mode 0 is locked and mode 2 is continuous; both are handled
# separately in `_binit_is_authoritative`.
const _DISCRETE_SWITCHED_SHUNT_MODES = (1, 3, 4, 5)

"""
Whether a SWITCHED SHUNT record's `BINIT` should be used as the device's admittance. Per the
POM, this is the case when one of the following is met:

* the shunt is locked (`MODSW == 0`), or sits on a type 3 (swing) bus;
* the shunt is continuously controlled (`MODSW == 2`);
* the record carries no per-block status (versions before v35);
* the caller declares the case solved.

Otherwise, BINIT is only a starting value, and device admittance is calculated
from the engaged blocks.
"""
function _binit_is_authoritative(
control_mode::Int,
bus_type::Int,
has_block_status::Bool,
solved_case::Bool,
)
solved_case && return true
has_block_status || return true
bus_type == PM_BUS_TYPE_REF && return true
return !(control_mode in _DISCRETE_SWITCHED_SHUNT_MODES)
end

"""
Expand All @@ -842,9 +882,17 @@ end
Parses PSS(R)E-style Fixed and Switched Shunt data into a PowerModels-style
Dict. "source_id" is given by `["I", "ID"]` for Fixed Shunts, and `["I", "SWREM"]`
for Switched Shunts, as given by the PSS(R)E Fixed and Switched Shunts
specifications.
specifications. `solved_case` declares the RAW to have been written out from a converged
power flow, which decides whether each switched shunt keeps its BINIT — see
`_binit_is_authoritative`.
"""
function _psse2pm_shunt!(pm_data::Dict, pti_data::Dict, import_all::Bool, nb)
function _psse2pm_shunt!(
pm_data::Dict,
pti_data::Dict,
import_all::Bool,
nb,
solved_case::Bool,
)
@info "Parsing PSS(R)E Fixed & Switched Shunt data into a PowerModels Dict..."

# bus records may have already contributed shunt entries
Expand Down Expand Up @@ -897,14 +945,11 @@ function _psse2pm_shunt!(pm_data::Dict, pti_data::Dict, import_all::Bool, nb)
get(sub_data, "sw_id", "1"),
)
sub_data["gs"] = 0.0
# A PSS/E switched shunt has no fixed base admittance: the record carries only
# BINIT (the solved/initial total susceptance) and the per-block increments. BINIT
# is therefore NOT a `bs` -- it is the device's solved admittance, and goes to its
# own field so downstream can tell "solved total" from "fixed base + blocks"
# instead of inferring it from a zeroed block-status vector.
# See PowerSystems.jl#1774.
# The device's solved admittance goes to its own `solved_admittance` key
# so downstream can tell "solved total" from "total based on engaged blocks,"
# which differ in continuous mode.
sub_data["bs"] = 0.0
sub_data["solved_admittance"] = pop!(switched_shunt, "BINIT")
binit = pop!(switched_shunt, "BINIT")
sub_data["status"] = _determine_injector_status(
switched_shunt,
pm_data,
Expand All @@ -916,13 +961,10 @@ function _psse2pm_shunt!(pm_data::Dict, pti_data::Dict, import_all::Bool, nb)
(pop!(switched_shunt, "VSWLO"), pop!(switched_shunt, "VSWHI"))

# N1..N8 hold the step count of each admittance block, B1..B8 its admittance.
step_numbers = Dict(
k => v for (k, v) in switched_shunt if _is_switched_shunt_block(k, 'N')
)
step_numbers_sorted =
sort(collect(keys(step_numbers)); by = x -> parse(Int, x[2:end]))
sub_data["step_number"] = [step_numbers[k] for k in step_numbers_sorted]
sub_data["step_number"] = sub_data["step_number"][sub_data["step_number"] .!= 0]
step_number, y_increment = _switched_shunt_blocks(switched_shunt)
sub_data["step_number"] = step_number
# `y_increment` is an admittance: the record's Bi is a susceptance.
sub_data["y_increment"] = complex.(0.0, y_increment)

sub_data["control_mode"] = switched_shunt["MODSW"]
# pti.jl names the regulated-bus column "SWREM" for every source version,
Expand All @@ -935,39 +977,35 @@ function _psse2pm_shunt!(pm_data::Dict, pti_data::Dict, import_all::Bool, nb)
"RMIDNT" => switched_shunt["RMIDNT"],
)

y_increment = Dict(
k => v for
(k, v) in switched_shunt if _is_switched_shunt_block(k, 'B')
)
y_increment_sorted =
sort(collect(keys(y_increment)); by = x -> parse(Int, x[2:end]))
sub_data["y_increment"] = [y_increment[k] for k in y_increment_sorted]im
sub_data["y_increment"] = sub_data["y_increment"][sub_data["y_increment"] .!= 0]

if pm_data["source_version"] == "35"
initial_ss_status = Dict(
k => v for
(k, v) in switched_shunt if startswith(k, "S") && isdigit(last(k))
)
initial_ss_status_sorted =
sort(collect(keys(initial_ss_status)); by = x -> parse(Int, x[2:end]))
sub_data["number_engaged"] =
[initial_ss_status[k] for k in initial_ss_status_sorted]
sub_data["number_engaged"] =
sub_data["number_engaged"][1:length(sub_data["step_number"])]
has_block_status = pm_data["source_version"] == "35"
if has_block_status
# Si are block statuses, not step counts, so 1 => all steps on.
sub_data["number_engaged"] = [
get(switched_shunt, "S$i", 1) != 0 ? steps : 0 for
(i, steps) in enumerate(step_number)
]

sub_data["ext"]["NREG"] = pop!(switched_shunt, "NREG")
elseif pm_data["source_version"] ∈ ("30", "32", "33")
# Pre-v35 SWITCHED SHUNT records carry no per-block status field, so how
# many steps of each block are engaged is simply unknown. The device's
# actual admittance is BINIT, now carried in `solved_admittance` above, so
# nothing has to be reconstructed from the blocks; zeros here record "no
# per-block information", not "every block is out of service".
sub_data["number_engaged"] = zeros(Int, length(sub_data["y_increment"]))
# Pre-v35 SWITCHED SHUNT records carry no per-block status, so initialize to
# all-off and take admittance from BINIT.
sub_data["number_engaged"] = zeros(Int, length(step_number))
else
error("Unsupported PSS(R)E source version: $(pm_data["source_version"])")
end

# only keep BINIT where it states the device's actual admittance. The swing-bus
# rule is about the RAW's own bus I, so it reads `bus_number`: a node-breaker
# `shunt_bus` can be a node-bus still initialized to PQ at this point.
if _binit_is_authoritative(
Int(sub_data["control_mode"]),
pm_data["bus"][bus_number]["bus_type"],
has_block_status,
solved_case,
)
sub_data["solved_admittance"] = binit
end

sub_data["index"] = length(pm_data["switched_shunt"]) + 1
sub_data["source_id"] = ["switched shunt", bus_number, sub_data["index"]]

Expand Down Expand Up @@ -2650,13 +2688,16 @@ end

Converts PSS(R)E-style data parsed from a PTI raw file, passed by `pti_data`
into a format suitable for use internally in PowerModels. Imports all remaining
data from the PTI file if `import_all` is true (Default: false).
data from the PTI file if `import_all` is true (Default: false). Set `solved_case` when the
file was written out after a converged power flow, so that every switched shunt's BINIT is
taken as its solved admittance (see `_binit_is_authoritative`).
"""
function _pti_to_powermodels!(
pti_data::Dict;
import_all = false,
validate = true,
correct_branch_rating = true,
solved_case = false,
)::Dict
pm_data = Dict{String, Any}()

Expand Down Expand Up @@ -2694,7 +2735,7 @@ function _pti_to_powermodels!(
_psse2pm_transformer!(pm_data, pti_data, import_all, nb)
# Injectors need to be parsed after branches and transformers to find topologically connected buses
_psse2pm_load!(pm_data, pti_data, import_all, nb)
_psse2pm_shunt!(pm_data, pti_data, import_all, nb)
_psse2pm_shunt!(pm_data, pti_data, import_all, nb, solved_case)
_psse2pm_generator!(pm_data, pti_data, import_all, nb)
_migrate_node_breaker_gen_bus_type!(pm_data, nb)
_psse2pm_facts!(pm_data, pti_data, import_all)
Expand Down
4 changes: 4 additions & 0 deletions src/power_models_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Currently Supports MATPOWER and PSSE data files parsed by PowerModels.
- `pm_data_corrections::Bool=true`: Run PowerModels data corrections (validation)
- `import_all::Bool=false`: Import all fields from PTI files
- `correct_branch_rating::Bool=true`: Correct branch ratings during parsing
- `solved_case::Bool=false`: Treat a PSS(R)E .raw file as written out from a converged
power flow, so switched shunts take BINIT as their solved admittance

# Example
```julia
Expand All @@ -38,11 +40,13 @@ function PowerModelsData(file::Union{String, IO}; kwargs...)
validate = get(kwargs, :pm_data_corrections, true)
import_all = get(kwargs, :import_all, false)
correct_branch_rating = get(kwargs, :correct_branch_rating, true)
solved_case = get(kwargs, :solved_case, false)
pm_dict = parse_file(
file;
import_all = import_all,
validate = validate,
correct_branch_rating = correct_branch_rating,
solved_case = solved_case,
)
pm_data = PowerModelsData(pm_dict)
correct_pm_transformer_status!(pm_data)
Expand Down
42 changes: 42 additions & 0 deletions test/fixtures/v35_switched_shunt.raw
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@!IC,SBASE,REV,XFRRAT,NXFRAT,BASFRQ
0, 100.00, 35, 0, 1, 60.00
Synthetic fictional v35 case exercising SWITCHED SHUNT MODSW/BINIT handling
Second comment line
0 / END OF SYSTEM-WIDE DATA, BEGIN BUS DATA
@! I,'NAME ', BASKV, IDE,AREA,ZONE,OWNER, VM, VA, NVHI, NVLO, EVHI, EVLO
1,'BUSONE ', 138.0000,3, 1, 1, 1,1.00000, 0.0000,1.10000,0.90000,1.10000,0.90000
2,'BUSTWO ', 138.0000,1, 1, 1, 1,1.00000, 0.0000,1.10000,0.90000,1.10000,0.90000
3,'BUSTHREE ', 138.0000,1, 1, 1, 1,1.00000, 0.0000,1.10000,0.90000,1.10000,0.90000
0 / END OF BUS DATA, BEGIN LOAD DATA
2,'1 ', 1, 1, 1, 100.000, 25.000, 0.000, 0.000, 0.000, 0.000, 1, 1, 0, 20.000, 5.000, 1,' V'
3,'1 ', 1, 1, 1, 50.000, 10.000, 0.000, 0.000, 0.000, 0.000, 1, 1, 0, 15.000, 3.000, 0,' V'
0 / END OF LOAD DATA, BEGIN FIXED SHUNT DATA
0 / END OF FIXED SHUNT DATA, BEGIN GENERATOR DATA
1,'1 ', 80.000, 0.000, 80.000, -80.000,1.00000, 0, 0, 100.000, 0.00000E+0, 1.00000E-1, 0.00000E+0, 0.00000E+0,1.00000,1, 100.0, 120.000, 0.000, 0,1,1.0000
0 / END OF GENERATOR DATA, BEGIN BRANCH DATA
1, 2,'1 ', 1.00000E-02, 1.00000E-01,0.02000,'BRANCH_1_2 ', 500.00, 500.00, 500.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 1.00, 1,1.0000
2, 3,'1 ', 1.00000E-02, 1.00000E-01,0.02000,'BRANCH_2_3 ', 500.00, 500.00, 500.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00000, 0.00000, 0.00000, 0.00000,1,1, 1.00, 1,1.0000
0 / END OF BRANCH DATA, BEGIN SYSTEM SWITCHING DEVICE DATA
0 / END OF SYSTEM SWITCHING DEVICE DATA, BEGIN TRANSFORMER DATA
0 / END OF TRANSFORMER DATA, BEGIN AREA DATA
0 / END OF AREA DATA, BEGIN TWO-TERMINAL DC DATA
0 / END OF TWO-TERMINAL DC DATA, BEGIN VSC DC LINE DATA
0 / END OF VSC DC LINE DATA, BEGIN IMPEDANCE CORRECTION DATA
0 / END OF IMPEDANCE CORRECTION DATA, BEGIN MULTI-TERMINAL DC DATA
0 / END OF MULTI-TERMINAL DC DATA, BEGIN MULTI-SECTION LINE DATA
0 / END OF MULTI-SECTION LINE DATA, BEGIN ZONE DATA
0 / END OF ZONE DATA, BEGIN INTER-AREA TRANSFER DATA
0 / END OF INTER-AREA TRANSFER DATA, BEGIN OWNER DATA
0 / END OF OWNER DATA, BEGIN FACTS DEVICE DATA
0 / END OF FACTS DEVICE DATA, BEGIN SWITCHED SHUNT DATA
@! I,'ID',MODSW,ADJM,STAT, VSWHI, VSWLO, SWREG, NREG, RMPCT,'RMIDNT', BINIT, S1, N1, B1, S2, N2, B2, S3, N3, B3
1,'1 ', 1, 0, 1, 1.05000, 0.95000, 0, 0, 100.0,' ', 25.000, 1, 5, 10.000
2,'1 ', 1, 0, 1, 1.05000, 0.95000, 0, 0, 100.0,' ', 60.000, 1, 3, 10.000, 0, 2, 20.000
2,'2 ', 0, 0, 1, 1.05000, 0.95000, 0, 0, 100.0,' ', 45.000, 1, 2, 15.000
2,'3 ', 2, 0, 1, 1.05000, 0.95000, 0, 0, 100.0,' ', 37.500, 1, 4, 5.000
3,'1 ', 1, 0, 1, 1.05000, 0.95000, 0, 0, 100.0,' ', 16.000, 1, 2, 8.000, 1, 0, 12.000, 1, 3, 6.000
0 / END OF SWITCHED SHUNT DATA, BEGIN GNE DATA
0 / END OF GNE DATA, BEGIN INDUCTION MACHINE DATA
0 / END OF INDUCTION MACHINE DATA, BEGIN SUBSTATION DATA
0 / END OF SUBSTATION DATA
Q
Loading
Loading