Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

correct matpower parsing #836

Merged
merged 10 commits into from
Nov 19, 2021
Merged
24 changes: 24 additions & 0 deletions src/parsers/pm_io/matpower.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# #
#########################################################################

const MP_FIX_VOLTAGE_BUSES = [2, 3]

"Parses the matpwer data from either a filename or an IO object"
function parse_matpower(io::IO; validate = true)::Dict
mp_data = _parse_matpower_string(read(io, String))
Expand Down Expand Up @@ -209,11 +211,15 @@ function _parse_matpower_string(data_string::String)

if haskey(matlab_data, "mpc.bus")
buses = []
pv_bus_lookup = Dict{Int, Any}()
for bus_row in matlab_data["mpc.bus"]
bus_data = row_to_typed_dict(bus_row, _mp_bus_columns)
bus_data["index"] = check_type(Int, bus_row[1])
bus_data["source_id"] = ["bus", bus_data["index"]]
push!(buses, bus_data)
if bus_data["bus_type"] ∈ MP_FIX_VOLTAGE_BUSES
pv_bus_lookup[bus_data["index"]] = bus_data
end
end
case["bus"] = buses
else
Expand All @@ -226,8 +232,26 @@ function _parse_matpower_string(data_string::String)

if haskey(matlab_data, "mpc.gen")
gens = []
corrected_pv_bus_vm = Dict{Int, Float64}()
for (i, gen_row) in enumerate(matlab_data["mpc.gen"])
gen_data = row_to_typed_dict(gen_row, _mp_gen_columns)
bus_data = get(pv_bus_lookup, gen_data["gen_bus"], nothing)
if bus_data !== nothing
if bus_data["bus_type"] ∈ MP_FIX_VOLTAGE_BUSES &&
bus_data["vm"] != gen_data["vg"]
@info "Correcting vm in bus $(gen_data["gen_bus"]) to $(gen_data["vg"]) to match generator set-point"
if gen_data["gen_bus"] ∈ keys(corrected_pv_bus_vm)
if corrected_pv_bus_vm[gen_data["gen_bus"]] != gen_data["vg"]
@error(
"Generator voltage set-points for bus $(gen_data["gen_bus"]) are inconsistent. This can lead to unexpected results"
)
end
else
bus_data["vm"] = gen_data["vg"]
corrected_pv_bus_vm[gen_data["gen_bus"]] = gen_data["vg"]
end
end
end
gen_data["index"] = i
gen_data["source_id"] = ["gen", i]
push!(gens, gen_data)
Expand Down
81 changes: 47 additions & 34 deletions test/test_busnumberchecks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,56 @@
base_dir = dirname(dirname(pathof(PowerSystems)))

@testset "Check bus index" begin
sys = System(PowerSystems.PowerModelsData(joinpath(MATPOWER_DIR, "case5_re.m")))
@test sort([b.number for b in collect(get_components(Bus, sys))]) == [1, 2, 3, 4, 10]
@test sort(
collect(Set([b.arc.from.number for b in collect(get_components(Branch, sys))])),
) == [1, 2, 3, 4]
@test sort(
collect(Set([b.arc.to.number for b in collect(get_components(Branch, sys))])),
) == [2, 3, 4, 10]

# TODO: add test for loadzones testing MAPPING_BUSNUMBER2INDEX
# This signature is used to capture expected error logs from parsing matpower
test_bus_index =
() -> begin
sys = System(PowerSystems.PowerModelsData(joinpath(MATPOWER_DIR, "case5_re.m")))
@test sort([b.number for b in collect(get_components(Bus, sys))]) == [1, 2, 3, 4, 10]
@test sort(
collect(
Set([b.arc.from.number for b in collect(get_components(Branch, sys))]),
),
) == [1, 2, 3, 4]
@test sort(
collect(
Set([b.arc.to.number for b in collect(get_components(Branch, sys))]),
),
) == [2, 3, 4, 10]

# TODO: add test for loadzones testing MAPPING_BUSNUMBER2INDEX
end
@test_logs min_level = Logging.Error match_mode = :any test_bus_index()
end

@testset "Test unique bus numbers" begin
sys = System(PowerSystems.PowerModelsData(joinpath(MATPOWER_DIR, "case5_re.m")))
number = 100
bus1 = Bus(;
number = number,
name = "bus100",
bustype = BusTypes.PV,
angle = 1.0,
magnitude = 1.0,
voltage_limits = (min = -1.0, max = 1.0),
base_voltage = 1.0,
)
bus2 = Bus(;
number = number,
name = "bus101",
bustype = BusTypes.PV,
angle = 1.0,
magnitude = 1.0,
voltage_limits = (min = -1.0, max = 1.0),
base_voltage = 1.0,
area = nothing,
load_zone = nothing,
)
# This signature is used to capture expected error logs from parsing matpower
test_bus_numbers =
() -> begin
sys = System(PowerSystems.PowerModelsData(joinpath(MATPOWER_DIR, "case5_re.m")))
number = 100
bus1 = Bus(;
number = number,
name = "bus100",
bustype = BusTypes.PV,
angle = 1.0,
magnitude = 1.0,
voltage_limits = (min = -1.0, max = 1.0),
base_voltage = 1.0,
)
bus2 = Bus(;
number = number,
name = "bus101",
bustype = BusTypes.PV,
angle = 1.0,
magnitude = 1.0,
voltage_limits = (min = -1.0, max = 1.0),
base_voltage = 1.0,
area = nothing,
load_zone = nothing,
)

add_component!(sys, bus1)
@test_throws ArgumentError add_component!(sys, bus2)
add_component!(sys, bus1)
@test_throws ArgumentError add_component!(sys, bus2)
end
@test_logs min_level = Logging.Error match_mode = :any test_bus_numbers()
end
35 changes: 33 additions & 2 deletions test/test_parse_matpower.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ POWER_MODELS_KEYS = [
]

badfiles = Dict("case30.m" => PSY.InvalidValue)
voltage_inconsistent_files =
["RTS_GMLC.m", "case5_re.m", "case5_re_intid.m", "case5_re_uc.m", "case5_re_uc_pwl.m"]

@testset "Parse Matpower data files" begin
files = [x for x in readdir(joinpath(MATPOWER_DIR)) if splitext(x)[2] == ".m"]
Expand All @@ -26,7 +28,12 @@ badfiles = Dict("case30.m" => PSY.InvalidValue)
@info "Parsing $f..."
path = joinpath(MATPOWER_DIR, f)

pm_dict = PowerSystems.parse_file(path)
if f in voltage_inconsistent_files
continue
else
pm_dict = PowerSystems.parse_file(path)
end

for key in POWER_MODELS_KEYS
@test haskey(pm_dict, key)
end
Expand Down Expand Up @@ -58,7 +65,12 @@ end
@info "Parsing $f..."
path = joinpath(joinpath(DATA_DIR, "pm_data", "matpower"), f)

pm_dict = PowerSystems.parse_file(path)
if f in voltage_inconsistent_files
continue
else
pm_dict = PowerSystems.parse_file(path)
end

for key in POWER_MODELS_KEYS
@test haskey(pm_dict, key)
end
Expand All @@ -76,3 +88,22 @@ end
end
end
end

@testset "Parse Matpower files with voltage inconsistencies" begin
test_parse = (path) -> begin
pm_dict = PowerSystems.parse_file(path)

for key in POWER_MODELS_KEYS
@test haskey(pm_dict, key)
end
@info "Successfully parsed $path to PowerModels dict"
sys = System(PowerSystems.PowerModelsData(pm_dict))
@info "Successfully parsed $path to System struct"
@test isa(sys, System)
end
for f in voltage_inconsistent_files
@info "Parsing $f..."
path = joinpath(MATPOWER_DIR, f)
@test_logs (:error,) match_mode = :any test_parse(path)
end
end
11 changes: 3 additions & 8 deletions test/test_parse_tamu.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
@testset "TAMU Parsing " begin
files = readdir(TAMU_DIR)
if length(files) == 0
error("No test files in the folder")
end

sys = PSB.build_system(PSB.PSYTestSystems, "tamu_ACTIVSg2000_sys")
@info "Successfully parsed $TAMU_DIR to System struct"

# Added due to inconsistencies in the incoming data. If the data is fixed, this test_logs call will fail
sys = PSB.build_system(PSB.PSYTestSystems, "tamu_ACTIVSg2000_sys"; force_build = true)
@test isa(sys, PSY.System)
# Test bad input
@test_throws PowerSystems.DataFormatError TamuSystem("")
end
Loading