invenia / OPFSampler.jl

Takes a power grid case and generates OPF samples by changing the input parameters.
Mozilla Public License 2.0
9 stars 3 forks source link

OPFSampler

Stable Dev CI Codecov

Goal

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.

Citing OPFSampler

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}
}

Sampler

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:

Minimal Working Example in Julia

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.

Output Structure

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:

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"]).

Grid Clean-Up Functions

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

Generated Data Samples

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.

How to Download Generated Samples?

The generated samples are hosted in Amazon S3 bucket (s3://invenia-public-datasets/OPFSamples/pglib-opf/). The following folders are available in this directory:

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

How to Use Generated Samples?

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:

DC-OPF Example:

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"])

AC-OPF Example:

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.