The goal of the package is to provide functions that take a power grid as input, vary its parameters and generate feasible DC- and AC-OPF samples along with the corresponding solutions. This helps the user to explore a variety of distinct active sets of constraints of synthetic cases and mimic the time-varying behaviour of the OPF input parameters.
We created a publicly-available database of input samples for different grids for both DC- and AC-OPF that have feasible OPF solution. The samples are collected in the following Amazon S3 bucket: s3://invenia-public-datasets/OPFSamples/pglib-opf/
. There are currently two folders in the main directory corresponding to 10k DC-OPF and 1k AC-OPF samples for different grids.
Further information about how to generate/download/use the samples and the related functions can be found in the rest of this document.
If you find any of the data available in the sample generation database or functions provided in this repo useful in your work, we kindly request that you cite the following:
@misc{opfmeta,
title={Learning an Optimally Reduced Formulation of OPF through Meta-optimization},
author={Alex Robson and Mahdi Jamei and Cozmin Ududec and Letif Mones},
year={2019},
eprint={1911.06784},
archivePrefix={arXiv},
primaryClass={eess.SP}
}
RunDCSampler()
and RunACSampler()
are the two main functions that generate DC- and AC-OPF samples, respectively.
The sampler functions provide the functionality to vary the grid parameters by rescaling them using factors drawn from a uniform distribution of the form . The following parameters can be rescaled for DC-OPF:
In addition to the parameters above, the following can also be changed for AC-OPF:
Setting up the path and activating the package:
cd("path to package directory")
using Pkg
Pkg.activate("./")
using OPFSampler
using PowerModels
using Random
Parsing the power grid:
base_model = PowerModels.parse_file("path to PowerModel case data.");
setting a dictionary of parameters that include the case name, for which the samples are to be generated along with the selected for each parameter that is varied. Here we have chosen 10\% deviation for all the parameters as an example.
# DC Parameters
params_DC = Dict("case_network" => base_model, "dev_load_pd" => 0.1,
"dev_gen_max" => 0.1, "dev_rate_a" => 0.1, "dev_br_x" => 0.1);
# AC Parameters
params_AC = Dict("case_network" => base_model, "dev_load_pd" => 0.1,
"dev_load_qd" => 0.1, "dev_pgen_max" => 0.1, "dev_qgen_max" => 0.1,
"dev_rate_a" => 0.1, "dev_br_x" => 0.1, "dev_br_r" => 0.1);
range = MersenneTwister(12345);
Since, sample generation code uses random numbers, the range that initilizes the random number generator can be passed as a keyword.
Finally, to run the Sampler:
num_of_samples = 5; # number of required OPF samples.
# DC-OPF
samples = RunDCSampler(num_of_samples, params_DC; rng = range);
# AC-OPF
samples = RunACSampler(num_of_samples, params_AC; rng = range);
The sampling function starts by generating the number of required samples and then runs OPF for each of the samples and filter those with feasible OPF solutions. Importing power grid data, grid modifications and solving OPF are all done within PowerModels.jl framework. Since some of the generated samples might not be feasible, the sample generation continues in an iterative manner until the required number of samples with feasible solution is met. Currently, if more than 60\% of the samples lead to infeasible OPF in the first iteration, the algorithm returns an error to indicate the fact that the choice of parameters might not be suitable for the used grid.
The output data samples
is an array of dictionaries where each element of array has the corresponding sample parameter values and the OPF solution.
Each input parameter in the samples[i]
is a vector of data containing the values of that parameter. The original grid data in PowerModels format that we have parsed and from which the samples
are generated is stored in base_model
that is a dictionary, whose keys correspond to different elements of the grid. The relevant elements that are modified through OPFSampler are:
Each grid element has a set of keys corresponding to parts of the grid of the same type. For example, base_model["branch"]["4"]
has the information of branch identified with "4" as its identifier.
For sample i in the dictionary, depending on the type of sampler (AC/DC), all or a subset of the following keys can be found:
base_model["load"][load key]["pd"]
base_model["load"][load key]["qd"]
base_model["gen"][gen key]["pmax"]
base_model["gen"][gen key]["qmax"]
base_model["branch"][branch key]["rate_a"]
base_model["branch"][branch key]["br_x"]
base_model["branch"][branch key]["br_r"]
.We do not keep keys in the output, but make the assumption that the Array order is the same as the lexicographical ordering in PowerModels, i.e. sort(power_model["load"]))
for load.
For example, the k-th element of the vector samples[i]["rate_a"][k]
corresponds to the "rate_a" data of the branch identified by the k-th key in the sorted branch dictionary sort(base_model["branch"])
or the k-th element of the vector samples[i]["qmax"][k]
corresponds to the "qmax" data of the generator identified by the k-th key in the sorted generator dictionary sort(base_model["gen"])
.
In order to avoid creating samples for elements of the grid that are either disabled or not relevant, there are two functions grid_dcopf_cleanup!()
and grid_acopf_cleanup!()
that take the PowerModel parsed grid as input and clean up these irrelevant elements.
For DC-OPF, the function removes all the disabled branches and generators that are either disabled or have . For AC-OPF, the function removes all the disabled branches and generators that are either disabled or have .
These function should be called before running the sampler.
base_model = PowerModels.parse_file("path to PowerModel case data.");
grid_dcopf_cleanup!(base_model) # for DC-OPF
grid_acopf_cleanup!(base_model) # for AC-OPF
Using the sampler code above, we have generated input samples for different grid cases in the pglib-opf library. All the input samples have been tested to make sure they have feasible OPF solution. Table below shows the list of grids and available input sample size for each grid:
Grid | DC Sample Size | AC Sample Size |
---|---|---|
24-ieee-rts | 10k | 1k |
30-ieee | 10k | 1k |
39-epri | 10k | 1k |
57-ieee | 10k | 1k |
73-ieee-rts | 10k | 1k |
118-ieee | 10k | 1k |
162-ieee-dtc | 10k | 1k |
300-ieee | 10k | 1k |
588-sdet | 10k | 1k |
1354-pegase | 10k | 1k |
2853-sdet | 10k | - |
4661-sdet | 10k | - |
9241-pegase | 10k | - |
In the generated samples above, the OPF solution that is generated under "OPF_output" has been removed to keep the size of the data small.
The generated samples are hosted in Amazon S3 bucket (s3://invenia-public-datasets/OPFSamples/pglib-opf/). The following folders are available in this directory:
input_10k_DC_0.15pd_0.1rest
: This folder contains 10k DC-OPF samples for the grids given in the table above. As indicated by the folder name, "dev_load_pd" is chosen to be 0.15 while other deviation parameters in params_DC
are set to 0.1.input_1k_AC_0.15pd_0.1rest
: This folder contains 1k AC-OPF samples for the grids given in the table above. As indicated by the folder name, "dev_load_pd" is chosen to be 0.15 while other deviation parameters in params_AC
are set to 0.1.Assuming that you have AWS Command Line Interface (CLI) configured on your machine (see here for more information), here is an example on how to download 10k DC input grid data for case 39-epri
:
aws s3 cp s3://invenia-public-datasets/OPFSamples/pglib-opf/input_10k_DC_0.15pd_0.1rest/pglib_opf_case39_epri_input.jld2 /destination_directory
There are functions provided in the package to vary the parameters of the original grid by the values generated in the samples
, and run OPF or perform other analysis.
Let's assume that the vector of samples
are created and we want to change the grid parameters to those in the i-th sample:
base_model = PowerModels.parse_file("path to PowerModel case data.");
OPFSampler.set_load_pd!(base_model, samples[i]["pd"])
OPFSampler.set_gen_pmax!(base_model, samples[i]["pmax"])
OPFSampler.set_dc_branch_param!(base_model, br_x = samples[i]["br_x"],
rate_a = samples[i]["rate_a"])
base_model = PowerModels.parse_file("path to PowerModel case data.");
OPFSampler.set_load_pd!(base_model, samples[i]["pd"])
OPFSampler.set_load_qd!(base_model, samples[i]["qd"])
OPFSampler.set_gen_pmax!(base_model, samples[i]["pmax"])
OPFSampler.set_gen_qmax!(base_model, samples[i]["qmax"])
OPFSampler.set_ac_branch_param!(base_model, br_x = samples[i]["br_x"],
br_r = samples[i]["br_r"], rate_a = samples[i]["rate_a"])
Important Note: The samples above are generated on grids after calling the corresponding clean-up functions so the base_model
should be passed through the clean-up functions before varying its parameters by the above set-functions.