From 9caa855727bf746cb2e21d795821a57aa6c82257 Mon Sep 17 00:00:00 2001 From: David M Fobes Date: Tue, 24 Sep 2024 09:34:12 -0600 Subject: [PATCH] FEATURE: Robust partition evaluation (#17) * ADD: robust partition evaluation * FIX: remove extraneous ONM. function calls * FIX: wrong test file path * ADD: Random to extra deps * FIX: function arg types * ADD: settings to case * FIX: input data * Re-enable tests --- CHANGELOG.md | 4 +++ Project.toml | 5 +-- src/PowerModelsONM.jl | 1 + src/prob/robust_evaluation.jl | 63 +++++++++++++++++++++++++++++++++++ test/robust_eval.jl | 47 ++++++++++++++++++++++++++ test/runtests.jl | 5 +++ 6 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/prob/robust_evaluation.jl create mode 100644 test/robust_eval.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index fdab402f..4f399f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - none +## v3.6.0 + +- Added capability to evaluate the optimality of a given partition against a set of load scenarios to judge robustness + ## v3.5.1 - Fixed bug in `constraint_mc_power_balance_shed_block` (`LPUBFDiagPowerModel` version) where `pd_zblock_zdemand` was used instead of `qd_zblock_zdemand` diff --git a/Project.toml b/Project.toml index b79ebc5d..fd3c29f0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PowerModelsONM" uuid = "25264005-a304-4053-a338-565045d392ac" authors = ["David M Fobes "] -version = "3.5.1" +version = "3.6.0" [deps] ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" @@ -60,7 +60,8 @@ Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84" PowerModelsDistribution = "d7431456-977f-11e9-2de3-97ff7677985e" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test", "HiGHS", "Ipopt", "JSON", "Juniper", "PowerModelsDistribution"] +test = ["Test", "HiGHS", "Ipopt", "JSON", "Juniper", "PowerModelsDistribution", "Random"] diff --git a/src/PowerModelsONM.jl b/src/PowerModelsONM.jl index 77c067ca..e966a98c 100644 --- a/src/PowerModelsONM.jl +++ b/src/PowerModelsONM.jl @@ -114,6 +114,7 @@ module PowerModelsONM include("prob/mld_block.jl") include("prob/partitions.jl") include("prob/mld_block_robust.jl") + include("prob/robust_evaluation.jl") include("prob/stability.jl") include("prob/switch.jl") diff --git a/src/prob/robust_evaluation.jl b/src/prob/robust_evaluation.jl new file mode 100644 index 00000000..38717c8d --- /dev/null +++ b/src/prob/robust_evaluation.jl @@ -0,0 +1,63 @@ +""" + evaluate_partition_optimality( + data, + load_scenarios, + model_type, + solver; + save_partial_results, + partial_result_folder, + time_elapsed, + kwargs... + ) + +Function to evaluate the optimality of a specific partition by considering a collection of load scenarios. + +`data` has the partition configuration applied. +""" +function evaluate_partition_optimality( + data::Dict{String,<:Any}, + load_scenarios, + model_type::Type, + solver; + save_partial_results::Bool=false, + partial_result_folder::String=".", + time_elapsed::Union{Missing,Real} = missing, + kwargs...) + + _results = Dict{String,Any}() + + for ls in keys(load_scenarios) + single_load_scenario = Dict{String,Dict{String,Any}}() + single_load_scenario["1"] = load_scenarios[ls] + + eng = deepcopy(data) + + if !ismissing(time_elapsed) + eng["time_elapsed"] = time_elapsed + end + + @debug "starting load scenario evaluation $(ls)/$(length(load_scenarios))" + + result = solve_robust_block_mld(eng, model_type, solver, single_load_scenario; kwargs...) + + if save_partial_results + open("$(partial_result_folder)/result_$(ls).json", "w") do io + JSON.print(io, result) + end + end + + _results[ls] = result + end + + return _results +end + + +""" +retrieve_load_scenario_optimality(results::Dict) + +Returns a Dict of objectives for the different load scenarios considered in the robust partition evaluation. +""" +function retrieve_load_scenario_optimality(results::Dict{String,<:Any})::Dict{String,Real} + return Dict{String,Real}("$i" => results["$i"]["1"]["objective"] for i in 1:length(results)) +end diff --git a/test/robust_eval.jl b/test/robust_eval.jl new file mode 100644 index 00000000..33d75805 --- /dev/null +++ b/test/robust_eval.jl @@ -0,0 +1,47 @@ +function randomize_partition_config( + case, + num_closed_switches) + + if num_closed_switches > length(keys(case["switch"])) + error("Number of closed switches exceeds the total number of switches") + end + + part_config = Dict{String,Any}() + + switch_keys = collect(keys(case["switch"])) + shuffled_keys = Random.shuffle(switch_keys) + closed_switches = shuffled_keys[1:num_closed_switches] + + # Set the status of the selected switches to "CLOSED" and the rest to "OPEN" + for key in switch_keys + if key in closed_switches + part_config[key] = PMD.SwitchState(1) + else + part_config[key] = PMD.SwitchState(0) + end + end + + case["fixed_partition_config"] = part_config + + return case +end + + +@testset "robust partition evaluation" begin + case = parse_file("../test/data/ieee13_feeder.dss") + settings = parse_settings("../test/data/ieee13_settings.json") + PMD.apply_voltage_bounds!(case) + case = randomize_partition_config(case, 2) + + num_load_scenarios = 20 + uncertainty_val = 0.2 + ls = generate_load_scenarios(case, num_load_scenarios, uncertainty_val) + + solver = optimizer_with_attributes(HiGHS.Optimizer, "primal_feasibility_tolerance" => 1e-6, "dual_feasibility_tolerance" => 1e-6, "small_matrix_value" => 1e-12, "allow_unbounded_or_infeasible" => true, "output_flag" => false) + + results_eval_optimality = evaluate_partition_optimality(case, ls, PMD.LPUBFDiagPowerModel, solver) + + optimality = retrieve_load_scenario_optimality(results_eval_optimality) + + @test isapprox(optimality["1"], 5, atol=1e0) +end diff --git a/test/runtests.jl b/test/runtests.jl index 1536f646..4c24664f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,6 +15,9 @@ import Juniper import Ipopt import HiGHS +import Random +Random.seed!(21) + minlp_solver = optimizer_with_attributes( Juniper.Optimizer, "nl_solver" => optimizer_with_attributes(Ipopt.Optimizer, "tol"=>1e-6, "print_level"=>0), @@ -60,6 +63,8 @@ silence!() # problems @info "Running tests in mld.jl" include("mld.jl") + @info "Running tests in robust_eval.jl" + include("robust_eval.jl") @info "Running tests in nlp.jl" include("nlp.jl") @info "Running tests in opf.jl"