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

ADD: new energy storage opfitd problem specification #19

Merged
merged 16 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
04aaa90
ADD: eng2math_passthrough parameter to all instantiate_model and solv…
juanjospina Apr 26, 2022
28b0d25
ADD: unit tests unit tests to opftid_pass.jl that test the correct op…
juanjospina Apr 26, 2022
6edfeb3
ADD: new problem specifications build_opfitd_storage and build_mn_opf…
juanjospina Apr 26, 2022
28795f7
ADD: new objective functions to objective_storage.jl that consider en…
juanjospina Apr 26, 2022
e0e3c19
DOC: correct CHANGELOG.md information for staged changes.
juanjospina Apr 27, 2022
c368b64
DOC: Added comment that explains the units of the storage cost to opf…
juanjospina May 3, 2022
07f018a
ADD: new default costs to storage devices at both transmission and di…
juanjospina Nov 13, 2023
def840c
ADD: option to parse files and then directly run the solve_opfitd_sto…
juanjospina Nov 14, 2023
5ca564e
ADD: option to parse files and use the parsed structure directly into…
juanjospina Nov 14, 2023
7636c3f
ADD: transmission system storage unit test case.
juanjospina Nov 14, 2023
0cb6892
DOC: added documentation of new storage problem formulation and updat…
juanjospina Nov 15, 2023
3feccb2
Prep for pull request to main repo.
juanjospina Nov 15, 2023
fcc1d11
DOC: fix doc error related to Vector Gumbo.HTMLNode
juanjospina Nov 15, 2023
acdea95
FIX: coverage for new storage functions adding more comprehensive tests.
juanjospina Nov 21, 2023
0319ce6
ADD: unit tests coverage to new storage probs and objectives.
juanjospina Nov 22, 2023
2cfe22b
FIX: small issues with tests for increasing testing coverage for stor…
juanjospina Nov 22, 2023
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
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,23 @@

## staged

- none.
- Added `eng2math_passthrough` parameter to all `instantiate_model(..)` and `solve_X(...)` functions.
- Added unit tests to `opftid_storage.jl` that test the correct operation of the `eng2math_passthrough` both in single network and multinetwork applications.
- Added test cases files for the `opftid_storage.jl` unit tests.
- Added new problem specifications `build_opfitd_storage` and `build_mn_opfitd_storage` to `prob/opfitd_storage.jl` that perform opf optimization taking into account storage costs.
- Added new objective functions to `objective_storage.jl` that consider energy storage costs in the objective function for the opf optimization.
- Added unit tests to `opftid_storage.jl` that test the new costs functions with energy storage costs added.
- Added functions `_compute_default_strg_cost_transmission` and `_compute_default_strg_cost_distribution` to `data.jl` that compute default costs for storage devices based on dafault parameters. Users are encouraged to use their own costs.
- Added code to `common.jl` functions `parse_power_distribution_file` and `parse_power_transmission_file` that adds default costs to distribution and transmission system(s) storage devices when parsing their individual files.
- Modified `solve_opfitd_storage` and `solve_mn_opfitd_storage` functions in `opfitd_storage.jl`, so that the `eng2math_passthrough = Dict("storage"=>["cost"])` is done automatically. PMITD assumes that if an user is running the `solve_opfitd_storage(...)` function, then storage costs should be passed from `ENG` to `MATH` models.
- Added the required variables and constraints for Transmission system-related storage devices to all problem specifications in `opfitd_storage.jl`.
- Fixed storage variables and constraints being used in `opfitd` and `opfitd_oltc.jl` where the versions used were the `_mi` mixed integer versions.
- Modified all objectives in `objective_storage.jl` to take into account the cost of discharging the storage devices in the transmission system. Also, added in-situ conversion for the PMD cost that converts the $/kWh -> $/pu cost, so that the user can provide the $/kWh in the `ENG` model, avoiding confusions.
- Added the option to parse files and use the parsed structure solve directly the optimization problem using `solve_opfitd_storage(..)` and `solve_mn_opfitd_storage(..)`. This allows users to parse the data, modify costs (or other parameters) and then run the solve functions without using the `solve_model(..)` function which requires users to explicitly use the `eng2math_passthrough = Dict("storage"=>["cost"])`.
- Added option to parse files and use the parsed structure directly into any `solve_x(..)` functions.
- Added documentation of the new problem formulation that considers storage costs.
- Updated BeginnersGuide.jl Pluto Notebook to latest versions.
- Updated DOCs dependencies and fixed minor issues.

## v0.7.9

Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ InfrastructureModels = "0.7.8"
Ipopt = "0.9, 1.0.2"
JSON = "~0.18, ~0.19, ~0.20, ~0.21"
JuMP = "~0.22, ~0.23, 1"
LinearAlgebra = "1.6"
PowerModels = "0.19.9"
PowerModelsDistribution = "0.15.1"
SCS = "~1.0, ~1.1"
julia = "1.6"
LinearAlgebra = "1.6"

[extras]
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

- Integrated T&D Power Flow (pfitd)
- Integrated T&D Optimal Power Flow (opfitd)
- Integrated T&D Optimal Power Flow with storage costs (opfitd_storage)
- Integrated T&D Optimal Power Flow with on-load tap-changer (opfitd_oltc)
- Integrated T&D Optimal power flow at transmission and minimum load delta at distribution system (opfitd_dmld)

Expand Down
4 changes: 2 additions & 2 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ Gumbo = "708ec375-b3d6-5a57-a7ce-8257bf98657a"
Pluto = "c3e4b0f8-55cb-11ea-2926-15256bba5781"

[compat]
Documenter = "~0.27.7"
Documenter = "~1.1.1"
Gumbo = "~0.8.0"
Pluto = "~0.15.1"
Pluto = "~0.19.1"
16 changes: 10 additions & 6 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ makedocs(
prettyurls=false,
collapselevel=1,
),
strict=false,
sitename = "PowerModelsITD.jl",
authors = "Juan Ospina, David M Fobes, and contributors",
pages = [
Expand All @@ -25,9 +24,10 @@ makedocs(
"Getting Started" => "manual/quickguide.md",
"File Formats" => "manual/fileformat.md",
"Formulations" => "manual/formulations.md",
"Storage" => "manual/storage.md",
],
"Tutorials" => [
"Beginners Guide" => "tutorials/Beginners Guide.md",
"Beginners Guide" => "tutorials/BeginnersGuide.md",
],
"API Reference" => [
"Base" => "reference/base.md",
Expand Down Expand Up @@ -60,12 +60,15 @@ if !_FAST
Pluto.update_run!(ss, nb, nb.cells)
html = Pluto.generate_html(nb)

fileout = "docs/build/tutorials/$(basename(file)).html"
base_name = basename(file)
base_name_splitted = split(base_name, '.')[1]
fileout = "docs/build/tutorials/$(base_name_splitted).html"

open(fileout, "w") do io
write(io, html)
end

doc = open("docs/build/tutorials/$(replace(basename(file), ".jl" => ".html"))", "r") do io
doc = open("docs/build/tutorials/$(base_name_splitted).html", "r") do io
Gumbo.parsehtml(read(io, String))
end

Expand All @@ -81,10 +84,11 @@ if !_FAST
)

# edit existing html to replace :article with :iframe
doc.root[2][1][2][2] = iframe
# doc.root[2][1][2][2] = iframe
doc.root[2][1][1] = iframe

# Overwrite HTML
open("docs/build/tutorials/$(replace(basename(file), ".jl" => ".html"))", "w") do io
open("docs/build/tutorials/$(base_name_splitted).html", "w") do io
Gumbo.prettyprint(io, doc)
end
end
Expand Down
143 changes: 143 additions & 0 deletions docs/src/manual/storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Quick Guide on Storage (with Costs) Problem Specification

In this guide, we will discuss the differences between the two problem specifications currently available in `PowermodelsITD` that consider `storage devices` when performing OPF.

## TL;DR

- `solve_opfitd(...)`: considers the operation (charge and discharge) of storage devices at both transmission and distribution system(s), but, no cost is added to the objective cost function. In other words, cycling/using the storage devices is a `free` operation.

- `solve_opfitd_storage(...)`: considers the operation (charge and discharge) of storage devices at both transmission and distribution system(s), and cost term(s) are added to the objective cost function. In other words, cycling/using the storage devices is `not free` and the specific cost of using the storage device is added to the cost function as $sd \times cost$, where `sd` is the amount of power discharged and `cost` is a scalar value in units \$/kWh or \$/pu.

## Default Costs and Units

By default, the costs of the storage devices are calculated based on the references shown below. However, users are *encouraged* to assign their own costs after parsing files.

- Transmission cost must be in \$/pu units.
(e.g., transformation from \$/MWh -> \$/pu: 200 \$/MWh x 100 MVA base/1 pu = 20,000 \$/pu)
- Distribution cost must be in \$/kWh units.
- Default costs of storage devices (in \$/kWh) are computed based on Eq. (23) from this [publication](https://ieeexplore.ieee.org/document/8805394).
- The total cost of the storage system, $C_{total}^{ES}$, is estimated based on NREL data obtained from this [resource](https://atb.nrel.gov/electricity/2021/residential_battery_storage).

```math
\begin{align}
%
cost = c_{\epsilon, \varepsilon} = \dfrac{C_{total}^{ES}}{Cyc\cdot {E_{_{ES}}^{max}}\cdot DoD\cdot \eta_{r}},
%
\end{align}
```

## Running Integrated Transmission-Distribution Optimal Power Flow with Storage Costs

The snippet below shows how to run a steady-state Integrated Transmission-Distribution (ITD) AC Optimal Power Flow with storage costs.
All of these files can be found in `test` folder of the repository.

```julia
using PowerModelsITD
using Ipopt

pm_file = "case5_withload.m"
pmd_file = "case3_balanced_withBattery.dss"
pmitd_file = "case5_case3_bal_battery.json"
pmitd_type = NLPowerModelITD{ACPPowerModel, ACPUPowerModel}
pmitd_data = parse_files(pm_file, pmd_file, pmitd_file)

# cost to assign to energy storage
# Units $/kWh
strg_cost = 0.025

# add cost to storages in PMD
for (st_name, st_data) in pmitd_data["it"]["pmd"]["storage"]
st_data["cost"] = strg_cost
end

# solve optimization with storage cost problem
pmitd_result_strg = solve_opfitd_storage(pmitd_data, pmitd_type, Ipopt.Optimizer)

```

## Running Integrated Transmission-Distribution Optimal Power Flow with Storage Costs Multinetwork

Running a multinetwork (i.e., multi-timestep) is also very simple. Only slight changes are required (assuming multinetwork data exists in the distribution files) as seen in the snippet shown below.

```julia
using PowerModelsITD
using Ipopt

pm_file = "case5_withload.m"
pmd_file = "case3_balanced_withBattery_mn_diff.dss"
pmitd_file = "case5_case3_bal_battery_mn.json"
pmitd_type = NLPowerModelITD{ACPPowerModel, ACPUPowerModel}
pmitd_data = parse_files(pm_file, pmd_file, pmitd_file; multinetwork=true)

# cost to assign to energy storage
# Units $/kWh
strg_cost = 0.0025

# add cost to storages in PMD
for (nw_id, nw_data) in pmitd_data["it"]["pmd"]["nw"]
for (st_name, st_data) in nw_data["storage"]
st_data["cost"] = strg_cost
end
end

pmitd_result_strg = solve_mn_opfitd_storage(pmitd_data, pmitd_type, Ipopt.Optimizer)

```

## How are the Storage Devices Modeled?

The storage mathematical model (for both transmission and distribution system(s)) are based on the model shown [here](https://lanl-ansi.github.io/PowerModels.jl/stable/storage/).
Given the storage data model and two sequential time points $s$ and $t$, the storage component's mathematical model is given by,

```math
\begin{align}
%
\mbox{data: } & \nonumber \\
& e^u \mbox{ - energy rating} \nonumber \\
& sc^u \mbox{ - charge rating} \nonumber \\
& sd^u \mbox{ - discharge rating} \nonumber \\
& \eta^c \mbox{ - charge efficiency} \nonumber \\
& \eta^d \mbox{ - discharge efficiency} \nonumber \\
& te \mbox{ - time elapsed} \nonumber \\
& S^l \mbox{ - power losses} \nonumber \\
& Z \mbox{ - injection impedance} \nonumber \\
& q^l, q^u \mbox{ - reactive power injection limits} \nonumber \\
& s^u \mbox{ - thermal injection limit} \nonumber \\
& i^u \mbox{ - current injection limit} \nonumber \\
%
\mbox{variables: } & \nonumber \\
& e_i \in (0, e^u) \mbox{ - storage energy at time $i$} \label{var_strg_energy} \\
& sc_i \in (0, sc^u) \mbox{ - charge amount at time $i$} \label{var_strg_charge} \\
& sd_i \in (0, sd^u) \mbox{ - discharge amount at time $i$} \label{var_strg_discharge} \\
& sqc_i \mbox{ - reactive power slack at time $i$} \label{var_strg_qslack} \\
& S_i \mbox{ - complex bus power injection at time $i$} \label{var_strg_power} \\
& I_i \mbox{ - complex bus current injection at time $i$} \label{var_strg_current} \\
%
\mbox{subject to: } & \nonumber \\
& e_t - e_s = te \left(\eta^c sc_t - \frac{sd_t}{\eta^d} \right) \label{eq_strg_energy} \\
& sc_t \cdot sd_t = 0 \label{eq_strg_compl} \\
& S_t + (sd_t - sc_t) = j \cdot sqc_t + S^l + Z |I_t|^2 \label{eq_strg_loss} \\
& q^l \leq \Im(S_t) \leq q^u \label{eq_strg_q_limit} \\
& |S_t| \leq s^u \label{eq_strg_thermal_limit} \\
& |I_t| \leq i^u \label{eq_strg_current_limit}
\end{align}
```

## What is the Cost (Objective) Function?

The cost (objective) function of the `solve_opfitd_storage(...)` and `solve_mn_opfitd_storage(...)` is based on the mathematical model:

```math
\begin{align}
\begin{split}
\text{min} &\bigg(\sum_{k \in G^{^\mathcal{T}}} c_{2k}(P_{g,k}^{\mathcal{T}})^2 + c_{1k}(P_{g,k}^{\mathcal{T}}) + c_{0k} \bigg) +\\
&\bigg(\sum_{\epsilon \in E^{^\mathcal{T}}} c_{\epsilon}(sd_{\epsilon}^{\mathcal{T}})\bigg) +\\
&\bigg(\sum_{m \in G^{^\mathcal{D}}} c_{2m}(\sum_{\varphi \in \Phi} P_{g,m}^{\mathcal{D},\varphi})^2 + c_{1m}(\sum_{\varphi \in \Phi} P_{g,m}^{\mathcal{D},\varphi}) + c_{0m} \bigg) +\\
&\bigg(\sum_{\varepsilon \in E^{^\mathcal{D}}} c_{\varepsilon}(sd_{\varepsilon}^{\mathcal{D}})\bigg)
\end{split}
\end{align}
```

As observed, the costs of discharging the storage devices for both Transmission and Distribution system(s) are added the cost function of the OPFITD. This means that storage devices will only discharge power to the grid when the cost of `charging` + `discharging` energy is less than the cost of *not* using (cycling) the storage device.

This cost function only cosiders cost as a factor for determining when to use the storage devices, however, it is important to note that users are encouraged to create their own cost functions that consider other factors (e.g., voltage deviations) that will make the storage devices operate more often, as needed.
3 changes: 0 additions & 3 deletions docs/src/tutorials/Beginners Guide.md

This file was deleted.

3 changes: 3 additions & 0 deletions docs/src/tutorials/BeginnersGuide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Introduction to PowerModelsITD

Stub for BeginnersGuide.jl Pluto Notebook in the examples/ folder. The Pluto Notebook will get rendered and inserted as an iframe at documentation build time.
Loading