diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7ea011e..e53ea5d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@v1 with: - version: '1.6' + version: '1' - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy diff --git a/README.md b/README.md index 8352256..429271f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Main - CI](https://github.com/NREL-Sienna/StorageSystemsSimulations.jl/actions/workflows/main-tests.yml/badge.svg)](https://github.com/NREL-Sienna/StorageSystemsSimulations.jl/actions/workflows/main-tests.yml) [![codecov](https://codecov.io/gh/NREL-Sienna/StorageSystemsSimulations.jl/branch/main/graph/badge.svg?token=sML4kPw4Z9)](https://codecov.io/gh/NREL-Sienna/StorageSystemsSimulations.jl) +[![Documentation](https://github.com/NREL-Sienna/StorageSystemsSimulations.jl/workflows/Documentation/badge.svg)](https://nrel-sienna.github.io/StorageSystemsSimulations.jl/latest) [](https://join.slack.com/t/nrel-sienna/shared_invite/zt-glam9vdu-o8A9TwZTZqqNTKHa7q3BpQ) ## Development diff --git a/docs/Project.toml b/docs/Project.toml index a8bde6c..f6b0acf 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,18 +1,16 @@ [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" -DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" -GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6" +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" -StorageSystemsSimulations = "e2f1a126-19d0-4674-9252-42b2384f8e3c" +PowerSimulations = "e690365d-45e2-57bb-ac84-44ba829e73c4" +PowerSystemCaseBuilder = "f00506e0-b84f-492a-93c2-c0a9afc4364e" PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" -TimeSeries = "9e3dc215-6440-5c97-bce1-76c03772f85e" +StorageSystemsSimulations = "e2f1a126-19d0-4674-9252-42b2384f8e3c" [compat] -Documenter = "0.27" +Documenter = "1" InfrastructureSystems = "1" -julia = "^1.6" diff --git a/docs/make.jl b/docs/make.jl index da0ad62..0b6166f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -6,17 +6,20 @@ using DataStructures pages = OrderedDict( "Welcome Page" => "index.md", "Quick Start Guide" => "quick_start_guide.md", - "Code Base Developer Guide" => - Any["Developer Guide" => "code_base_developer_guide/developer.md",], - "Formulation Library" => Any["Storage" => "formulation_library/Storage.md",], + "Tutorials" => + Any["tutorials/single_stage_model.md", "tutorials/simulation_tutorial.md"], + "Formulation Library" => + Any["StorageDispatchWithReserves" => "formulation_library/StorageDispatchWithReserves.md",], + "Code Base Developer Guide" => "code_base_developer_guide/developer.md", "API Reference" => "api/StorageSystemsSimulations.md", ) makedocs(; modules=[StorageSystemsSimulations], format=Documenter.HTML(; prettyurls=haskey(ENV, "GITHUB_ACTIONS")), + warnonly=[:missing_docs], sitename="StorageSystemsSimulations.jl", - authors="Sourabh Dalvi, Jose Daniel Lara", + authors="Jose Daniel Lara, Rodrigo Henriquez-Auba, Sourabh Dalvi", pages=Any[p for p in pages], ) @@ -24,7 +27,7 @@ deploydocs(; repo="github.com/NREL-Sienna/StorageSystemsSimulations.jl.git", target="build", branch="gh-pages", - devbranch="master", + devbranch="main", devurl="dev", push_preview=true, versions=["stable" => "v^", "v#.#"], diff --git a/docs/src/api/StorageSystemsSimulations.md b/docs/src/api/StorageSystemsSimulations.md index 4b40ee9..f462b45 100644 --- a/docs/src/api/StorageSystemsSimulations.md +++ b/docs/src/api/StorageSystemsSimulations.md @@ -7,24 +7,12 @@ DocTestSetup = quote end ``` -API documentation - -```@contents -Pages = ["StorageSystemsSimulations.md"] -``` - -## Index - -```@index -Pages = ["StorageSystemsSimulations.md"] -``` - ## Exported ```@autodocs Modules = [StorageSystemsSimulations] Private = false -Filter = t -> typeof(t) === DataType ? !(t <: Union{StorageSystemsSimulations.AbstractStorageFormulation}) : true +Filter = t -> typeof(t) === DataType ? !(t <: StorageSystemsSimulations.AbstractStorageFormulation) : true ``` ## Internal diff --git a/docs/src/code_base_developer_guide/developer.md b/docs/src/code_base_developer_guide/developer.md index a4084e4..e98d878 100644 --- a/docs/src/code_base_developer_guide/developer.md +++ b/docs/src/code_base_developer_guide/developer.md @@ -1,11 +1,11 @@ # Guidelines for Developers -In order to contribute to `PowerSystems.jl` repository please read the following sections of +In order to contribute to `StorageSystemsSimulations.jl` repository please read the following sections of [`InfrastructureSystems.jl`](https://github.com/NREL-Sienna/InfrastructureSystems.jl) documentation in detail: 1. [Style Guide](https://nrel-sienna.github.io/InfrastructureSystems.jl/stable/style/) - 2. [Contributing Guidelines](https://github.com/NREL-Sienna/PowerSystems.jl/blob/master/CONTRIBUTING.md) + 2. [Contributing Guidelines](https://github.com/NREL-Sienna/StorageSystemsSimulations.jl/blob/master/CONTRIBUTING.md) Pull requests are always welcome to fix bugs or add additional modeling capabilities. diff --git a/docs/src/formulation_library/README.md b/docs/src/formulation_library/README.md deleted file mode 100644 index 8135ffc..0000000 --- a/docs/src/formulation_library/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Formulation documentation guide - -Formulation documentation should *roughly* follow the template established by RenewableGen.md - -## Auto generated items - - - Valid DeviceModel table: just change the device category in the filter function - - Time Series Parameters: just change the device category and formulation in the `get_defualt_time_series_names` method call - -## Linked items - - - Formulations in the Valid DeviceModel table must have a docstring in src/core/formulations.jl - - The Formulation in the @docs block must have a docstring in src/core/formulations.jl - - The Variables must have docstrings in src/core/variables.jl - - The Time Series Paraemters must have docstrings in src/core/paramters.jl diff --git a/docs/src/formulation_library/Storage.md b/docs/src/formulation_library/Storage.md deleted file mode 100644 index 2bb8e63..0000000 --- a/docs/src/formulation_library/Storage.md +++ /dev/null @@ -1,244 +0,0 @@ -# `PowerSystems.Storage` Formulations - -Valid `DeviceModel`s for subtypes of `Storage` include the following: - -```@eval -using PowerSimulations -using PowerSystems -using DataFrames -using Latexify -combos = PowerSimulations.generate_device_formulation_combinations() -filter!(x -> x["device_type"] <: Storage, combos) -combo_table = DataFrame( - "Valid DeviceModel" => - ["`DeviceModel($(c["device_type"]), $(c["formulation"]))`" for c in combos], - "Device Type" => [ - "[$(c["device_type"])](https://nrel-sienna.github.io/PowerSystems.jl/stable/model_library/generated_$(c["device_type"])/)" - for c in combos - ], - "Formulation" => ["[$(c["formulation"])](@ref)" for c in combos], -) -mdtable(combo_table, latex=false) -``` - -* * * - -## `BookKeeping` - -```@docs -BookKeeping -``` - -**Variables:** - - - [`ActivePowerInVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `-1 * PowerSystems.get_active_power(device)` - - - [`ActivePowerOutVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `PowerSystems.get_active_power(device)` - - [`ReactivePowerVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `PowerSystems.get_reactive_power(device)` - - [`EnergyVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `PowerSystems.get_initial_storage(device)` - - [`ReservationVariable`](@ref): - - + only included if `DeviceModel(HydroPumpedStorage, HydroDispatchPumpedStorage; attributes = Dict(reservation => true))` - + Bounds: {0, 1} - + Default initial value: 1 - -**Static Parameters:** - - - ``Pg^\text{min}`` = `PowerSystems.get_active_power_limits(device).min` - - ``Qg^\text{min}`` = `PowerSystems.get_reactive_power_limits(device).min` - - ``Qg^\text{max}`` = `PowerSystems.get_reactive_power_limits(device).max` - - ``E^\text{max}`` = `PowerSystems.get_storage_capacity(device)` - -**Objective:** - -Creates an objective function term based on the [`VariableCost` Options](@ref) where the quantity term is defined as ``Pg_t``. - -**Expressions:** - -Adds ``Pg`` and ``Qg`` terms to the respective active and reactive power balance expressions created by the selected [Network Formulations](@ref) - -**Constraints:** - -```math -\begin{aligned} -& E_{t+1} = E_t + (Pg^{in}_t - Pg^{out}_t) \cdot \Delta T \\ -& Pg^{in}_t - r * Pg^\text{in, max} \le Pg^\text{in, max} \\ -& Pg^{out}_t + r * Pg^\text{out, max} \le Pg^\text{out, max} \\ -& Qg^\text{min} \le Qg_t \le Qg^\text{max} \\ -& E_t \le E^\text{max} -\end{aligned} -``` - -* * * - -## `EnergyTarget` - -```@docs -EnergyTarget -``` - -**Variables:** - - - [`ActivePowerInVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `-1 * PowerSystems.get_active_power(device)` - - - [`ActivePowerOutVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `PowerSystems.get_active_power(device)` - - [`ReactivePowerVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `PowerSystems.get_reactive_power(device)` - - [`EnergyVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `PowerSystems.get_initial_storage(device)` - - [`StorageEnergyShortageVariable`](@ref): - - + Bounds: [ , 0.0] - + Default initial value: 0.0 - - [`StorageEnergySurplusVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: 0.0 - - [`ReservationVariable`](@ref): - - + only included if `DeviceModel(HydroPumpedStorage, HydroDispatchPumpedStorage; attributes = Dict(reservation => true))` - + Bounds: {0, 1} - + Default initial value: 1 - -**Static Parameters:** - - - ``Pg^\text{min}`` = `PowerSystems.get_active_power_limits(device).min` - - ``Qg^\text{min}`` = `PowerSystems.get_reactive_power_limits(device).min` - - ``Qg^\text{max}`` = `PowerSystems.get_reactive_power_limits(device).max` - - ``E^\text{max}`` = `PowerSystems.get_storage_capacity(device)` - -**Time Series Parameters:** - -```@eval -using PowerSimulations -using PowerSystems -using DataFrames -using Latexify -combos = PowerSimulations.get_default_time_series_names(Storage, EnergyTarget) -combo_table = DataFrame( - "Parameter" => map(x -> "[`$x`](@ref)", collect(keys(combos))), - "Default Time Series Name" => map(x -> "`$x`", collect(values(combos))), -) -mdtable(combo_table, latex=false) -``` - -**Objective:** - -Creates an objective function term based on the [`VariableCost` Options](@ref) where the quantity term is defined as ``Pg_t``, -and objective function terms for [StorageManagementCost](@ref). - -**Expressions:** - -Adds ``Pg`` and ``Qg`` terms to the respective active and reactive power balance expressions created by the selected [Network Formulations](@ref) - -**Constraints:** - -```math -\begin{aligned} -& E_{t+1} = E_t + (Pg^{in}_t - Pg^{out}_t) \cdot \Delta T \\ -& E_t - E^{surplus}_t + E^{shortage}_t = EnergyTargetTimeSeriesParameter_t \\ -& Pg^{in}_t - r * Pg^\text{in, max} \le Pg^\text{in, max} \\ -& Pg^{out}_t + r * Pg^\text{out, max} \le Pg^\text{out, max} \\ -& Qg^\text{min} \le Qg_t \le Qg^\text{max}\\ -& E_t \le E^\text{max} -\end{aligned} -``` - -* * * - -## `BatteryAncillaryServices` - -```@docs -BatteryAncillaryServices -``` - -**Variables:** - - - [`ActivePowerInVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `-1 * PowerSystems.get_active_power(device)` - - - [`ActivePowerOutVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `PowerSystems.get_active_power(device)` - - [`ReactivePowerVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `PowerSystems.get_reactive_power(device)` - - [`EnergyVariable`](@ref): - - + Bounds: [0.0, ] - + Default initial value: `PowerSystems.get_initial_storage(device)` - - [`ReservationVariable`](@ref): - - + only included if `DeviceModel(HydroPumpedStorage, HydroDispatchPumpedStorage; attributes = Dict(reservation => true))` - + Bounds: {0, 1} - + Default initial value: 1 - -**Static Parameters:** - - - ``Pg^\text{min}`` = `PowerSystems.get_active_power_limits(device).min` - - ``Qg^\text{min}`` = `PowerSystems.get_reactive_power_limits(device).min` - - ``Qg^\text{max}`` = `PowerSystems.get_reactive_power_limits(device).max` - - ``E^\text{max}`` = `PowerSystems.get_storage_capacity(device)` - -**Time Series Parameters:** - -```@eval -using PowerSimulations -using PowerSystems -using DataFrames -using Latexify -combos = PowerSimulations.get_default_time_series_names(Storage, EnergyTarget) -combo_table = DataFrame( - "Parameter" => map(x -> "[`$x`](@ref)", collect(keys(combos))), - "Default Time Series Name" => map(x -> "`$x`", collect(values(combos))), -) -mdtable(combo_table, latex=false) -``` - -**Objective:** - -Creates an objective function term based on the [`VariableCost` Options](@ref) where the quantity term is defined as ``Pg_t``, -and objective function terms for [StorageManagementCost](@ref). - -**Expressions:** - -Adds ``Pg`` and ``Qg`` terms to the respective active and reactive power balance expressions created by the selected [Network Formulations](@ref) - -**Constraints:** - -```math -\begin{aligned} -& E_{t+1} = E_t + (Pg^{in}_t - Pg^{out}_t) \cdot \Delta T \\ -& E_t - E^{surplus}_t + E^{shortage}_t = EnergyTargetTimeSeriesParameter_t \\ -& Pg^{in}_t - r * Pg^\text{in, max} \le Pg^\text{in, max} \\ -& Pg^{out}_t + r * Pg^\text{out, max} \le Pg^\text{out, max} \\ -& Qg^\text{min} \le Qg_t \le Qg^\text{max}\\ -& E_t \le E^\text{max} -\end{aligned} -``` diff --git a/docs/src/formulation_library/StorageDispatchWithReserves.md b/docs/src/formulation_library/StorageDispatchWithReserves.md new file mode 100644 index 0000000..942ba91 --- /dev/null +++ b/docs/src/formulation_library/StorageDispatchWithReserves.md @@ -0,0 +1,146 @@ +# `StorageDispatchWithReserves` Formulation + +```@docs +StorageDispatchWithReserves +``` + +## Attributes + + - `"reservation"`: Forces the battery to operate exclusively on charge or discharge mode through the entire operation interval. We recommend setting this to false for models with relatively large resolutions (e.g., 1-Hr) since the storage can take simultaneous charge or discharge positions on average over the period. + - `"cycling_limits"`: This limits the battery's energy cycling. The calculation uses the total energy charge/discharge and the number of cycles. Currently, the formulation only supports a fixed value per operation period. Additional variables for [`StorageChargeCyclingSlackVariable`](@ref) and [`StorageDischargeCyclingSlackVariable`](@ref) are included in the model if `use_slacks` is set to `true`. + - `"energy_target"`: Set a target at the end of the model horizon for the state of charge. Currently, the formulation only supports a fixed value per operation period. Additional variables for [`StorageEnergyShortageVariable`](@ref) and [`StorageEnergySurplusVariable`](@ref) are included in the model if `use_slacks` is set to `true`. + +!!! warning + + Combining the cycle limits and energy target attributes is not recommended. Both + attributes impose constraints on the energy; there is no guarantee that the constraints can be satisfied simultaneously. + + - `"complete_coverage"`: This attribute implements constraints that require the battery to cover the sum of all the ancillary services it participates in simultaneously. It is equivalent to holding energy in case all the services get deployed simultaneously. This constraint is added to the constraints that cover each service independently and corresponds to a more conservative operation regime. + - `"regularization"`: This attribute smooths the charge/discharge profiles to avoid bang-bang solutions via a penalty on the absolute value of the intra-temporal variations of the charge and discharge power. The model can stall in models with large amounts of curtailment or long periods with negative or zero prices due to numerical degeneracy. The regularization term is scaled by the power limits to normalize the term and avoid additional penalties to larger storage units. + +!!! danger + + Setting the energy target attribute in combination with [`EnergyTargetFeedforward`](@ref) or [`EnergyLimitFeedforward`](@ref) is not permitted and StorageSystemsSimulations will throw an exception. + +## Mathematical Model + +### Sets + +```math +\begin{align*} + &\mathcal{P}^{\text{as}_\text{up}} & \text{Up Ancillary Service Products Set}\\ + &\mathcal{P}^{\text{as}_\text{dn}} & \text{Down Ancillary Service Products Set}\\ + &\mathcal{P}^{\text{as}} := \bigcup\left\{ \mathcal{P}^{\text{as}_\text{up}}, \mathcal{P}^{\text{as}_\text{dn}}\right\} & \text{Ancillary Service Products Set}\\ + &\mathcal{T} := \{1,\dots,T\} & \text{Time steps} \\ +\end{align*} +``` + +### Parameters + +#### Operational Parameters + +```math +\begin{align*} + &P^{max,ch}_{st} &\text{Max Charge Power Storage [MW]}\\ + &P^{max,ds}_{st} &\text{Max Discharge Power Storage [MW]}\\ + &\eta^{ch}_{st} &\text{Charge Efficiency Storage [\%/hr]}\\ + &\eta^{ds}_{st} &\text{Discharge Efficiency Storage [\%/hr]}\\ + &R^{*}_{p, t} &\text{Ancillary Service deployment Forecast at time $t$ for service $p \in \mathcal{P}^{\text{as}}$ [\$/MW]}\\ + &E^{max}_{st} &\text{Max Energy Storage Capacity [MWh]}\\ + &E^{st}_{0} &\text{Storage initial energy [MWh]}\\ + &E^{st}_{T} &\text{Storage Energy Target at the end of the horizon, i.e., time-step $T$ [MWh]}\\ + &\Delta t &\text{Timestep length}\\ + &C_{st} & \text{Maximum number of cycles over the horizon.} \\ + && \text{For DA the value is fixed to 3 and in RT the value depends on the DA allocation of cycles} \\ + &N_{p} & \text{Number of periods of compliance to supply an AS.}\\ + && \text{For example Spinning reserve has 12 for 1 hour of compliance when $\Delta_t$ is 5-minutes.} +\end{align*} +``` + +#### Cost Parameters + +```math +\begin{align*} + &\text{VOM} &\text{Storage Variable Operation and Maintenance Cost [\%/MWh]}\\ + &\rho^{e+} &\text{Storage Surplus penalty at end of target cost [\$/MWh]. Used when \texttt{use\_slacks = true}}\\ + &\rho^{e-} &\text{Storage Shortage penalty at end of target cost [\$/MWh]. Used when \texttt{use\_slacks = true}}\\ + &\rho^{c} &\text{Storage Cycling Penalty [\$/MWh]. Used when \texttt{use\_slacks = true}}\\ + &\rho^{z} &\text{Regularization Terms Penalty. Used when \texttt{"regularization" => true}}\\ +\end{align*} +``` + +### Variables + +```math +\begin{align*} + &p^{st, ch}_{t} & \in [0, P^{max,ch}_{st}] &\quad\text{Expected Storage charging power}\\ + &p^{st, ds}_{t} & \in [0, P^{max,ds}_{st}] &\quad\text{Expected Storage discharging power}\\ + &e^{st}_{t} & \in [0, E^{max}_{st}] &\quad \text{Expected Storage Energy}\\ + &\text{ss}^{st}_{t} & \in \{ 0, 1 \} &\quad \text{Charge/Discharge status Storage. Used when \texttt{"reservation" => true}}\\ + &sb_{stc,p,t} & \in [0, P^{max,ch}_{st}] & \quad \text{Ancillary service fraction assigned to Storage Charging}\\ + &sb_{std,p,t} & \in [0, P^{max,ds}_{st}] & \quad \text{Ancillary service fraction assigned to Storage Discharging}\\ + &e^{st+} & \in [0, E^{max}_{st}] &\quad \text{Storage Energy Surplus above target. Used when \texttt{use\_slacks = true}}\\ + &e^{st-} & \in [0, E^{max}_{st}] &\quad \text{Storage Energy Shortage below target. Used when \texttt{use\_slacks = true}}\\ + &c^{ch-} & \in [0, T C_{st}] &\quad \text{Charging Cycling Shortage. Used when \texttt{use\_slacks = true}}\\ + &c^{ds-} & \in [0, T C_{st}] &\quad \text{Discharging Cycling Shortage. Used when \texttt{use\_slacks = true}}\\ + &z^{st, ch}_{t} & \in [0, P^{max,ch}_{st}] &\quad \text{Regularization charge variable. Used when \texttt{"regularization" => true}}\\ + &z^{st, ds}_{t} & \in [0, P^{max,ds}_{st}] &\quad \text{Regularization discharge variable. Used when \texttt{"regularization" => true}}\\ +\end{align*} +``` + +### Model + +```math +\begin{aligned} +\min_{\substack{\boldsymbol{p}^{st, ch}, \boldsymbol{p}^{st, ds}, \boldsymbol{e}^{st}, \\ e^{st+}, e^{st-}, c^{ch-} + c^{ds-}}} +& \rho^{e+} e^{st+} + \rho^{e-} e^{st-} + \rho^{c} \left(c^{ch-} + c^{ds-} \right) + \rho^{z} \left(\frac{z^{ch}}{P^{max,ch}_{st}} + \frac{z^{ds}}{P^{max,ds}_{st}} \right)\\ +& +\Delta t \sum_{t \in \mathcal{T}} \text{VOM}_{st} \left ( \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t} sb_{stc,p,t} + p^{st,ch}_{t} \right) + \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t} sb_{std,p,t} + p^{st,ds}_{t}\right) \right) & +\end{aligned} +``` + +```math +\begin{aligned} +\text{s.t.} & &\\ +&\text{Power Limit Constraints.}&\\ +&p^{st, ch}_{t} + \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} sb_{stc,p,t} \leq (1 - \text{ss}^{st}_{t})P^{max,ch}_{st} & \quad \forall t \in \mathcal{T} \\ +& p^{st, ch}_{t} - \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} sb_{stc,p,t} \geq 0 & \quad \forall t \in \mathcal{T}\\ +& p^{st, ds}_{t} + \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} sb_{std,p,t} \leq \text{ss}^{st}_{t}P^{max,ds}_{st} & \forall t \in \mathcal{T}\\ +& p^{st, ds}_{t} - \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} sb_{std,p,t} \geq 0 & \forall t \in \mathcal{T}\\ +&\text{Energy Storage Limit Constraints}&\\ +&e^{st}_{t} \leq E^{max}_{st} & \forall t \in \mathcal{T}\\ +& e^{st}_{t} \geq E^{min}_{st} & \forall t \in \mathcal{T}\\ +&\text{Energy Bookkeeping constraints}&\\ +& E^{st}_{0} + \Delta t \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,1} sb_{stc,p,1} + p^{st,ch}_{1} - \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,1} sb_{stc,p,1}\right)\eta^{ch}_{st}&\\ +&-\Delta t\left(\sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,1} sb_{std,p,1} + p^{st,ds}_{1} - \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t} sb_{std,p,1}\right)\frac{1}{\eta^{ds}_{st}}=e^{st}_{1}\\ +&e^{st}_{t-1} + \Delta t \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t} sb_{stc,p,t} + p^{st,ch}_{t} - \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t} sb_{stc,p,t}\right)\eta^{ch}_{st}&\\ +&-\Delta t\left(\sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t} sb_{std,p,t} + p^{st,ds}_{t} - \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t} sb_{std,p,t}\right)\frac{1}{\eta^{ds}_{st}} =e^{st}_{t} & \forall t \in \mathcal{T} \setminus {1}\\ +&\text{End of period energy target constraint. Used when \texttt{"energy\_target" => true}}&\\ +&e^{st}_{T} + e^{st+} - e^{st-} = E^{st}_{T}&\\ +&\text{Storage Cycling Limits Constraints. Used when \texttt{"cycling\_limits" => true}}&\\ +& \sum_{t \in \mathcal{T}} \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t} sb_{std,p,t} + p^{st,ds}_{t}\right)\frac{1}{\eta^{ds}_{st}} \Delta t - c^{ds-} \leq C_{st} E^{max}_{st} &\\ +& \sum_{t \in \mathcal{T}} \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t} sb_{stc,p,t} + p^{st,ch}_{t} \right)\eta^{ch}_{st} \Delta t - c^{ch-} \leq C_{st} E^{max}_{st} \\ +&\text{Single Ancillary Services Energy Coverage}&\\ +& sb_{stc,p,t} \eta^{ch}_{st} N_{p} \Delta t \le E_{st}^{max} - e^{st}_{t} & \forall p \in \mathcal{P}^{as_{dn}} \ \forall t \in \mathcal{T}\\ +& sb_{std,p,t} \frac{1}{\eta^{ds}_{st}} N_{p} \Delta t \leq e^{st}_{t}- E^{min}_{st} & \forall p \in \mathcal{P}^{as_{up}}, \ \forall t \in \mathcal{T}\\ +& sb_{stc,p,1} \eta^{ch}_{st} N_{p} \Delta t \le E_{st}^{max} - e^{st}_0 & \forall p \in \mathcal{P}^{as_{dn}}\\ +& sb_{stc,p,t} \eta^{ch}_{st} N_{p} \Delta t \le E_{st}^{max} - e^{st}_{t-1} & \forall p \in \mathcal{P}^{as_{dn}} \ \forall t \in \mathcal{T} \setminus 1\\ +&sb_{std,p,1} \frac{1}{\eta^{ds}_{st}} N_{p} \Delta t \leq e^{st}_0 - E^{min}_{st} & \forall p \in \mathcal{P}^{as_{up}}\\ +& sb_{std,p,t} \frac{1}{\eta^{ds}_{st}} N_{p} \Delta t \leq e^{st}_{t-1} - E^{min}_{st} & \forall p \in \mathcal{P}^{as_{up}}, \ \forall t \in \mathcal{T} \setminus 1 \\ +&\text{Complete Ancillary Services Energy Coverage. Used when \texttt{"complete\_coverage" => true}}&\\ +& \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} sb_{stc,p,t} \eta^{ch}_{st} N_{p} \Delta t \le E_{st}^{max} - e^{st}_{t} & \forall t \in \mathcal{T}\\ +& \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} sb_{std,p,t} \frac{1}{\eta^{ds}_{st}} N_{p} \Delta t \leq e^{st}_{t}- E^{min}_{st} & \forall t \in \mathcal{T}\\ +& \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} sb_{stc,p,1} \eta^{ch}_{st} N_{p} \Delta t \le E_{st}^{max} - e^{st}_0 &\\ +&\sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} sb_{stc,p,t} \eta^{ch}_{st} N_{p} \Delta t \le E_{st}^{max} - e^{st}_{t-1} & \forall t \in \mathcal{T} \setminus 1\\ +&\sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} sb_{std,p,1} \frac{1}{\eta^{ds}_{st}} N_{p} \Delta t \leq e^{st}_0- E^{min}_{st} & \\ +& \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} sb_{std,p,t} \frac{1}{\eta^{ds}_{st}} N_{p} \Delta t \leq e^{st}_{t-1}- E^{min}_{st} & \forall t \in \mathcal{T} \setminus 1\\ +&\text{Regularization Constraints. Used when \texttt{"regularization" => true}}&\\ +& \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t-1} sb_{stc,p,t-1} + p^{st,ch}_{t-1} - \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t-1} sb_{stc,p,t-1}\right) &\\ +& - \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t} sb_{stc,p,t} + p^{st,ch}_{t} - \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t} sb_{stc,p,t}\right) \le z^{st, ch}_{t} & \forall t \in \mathcal{T} \setminus 1\\ +& \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t-1} sb_{stc,p,t-1} + p^{st,ch}_{t-1} - \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t-1} sb_{stc,p,t-1}\right) &\\ +& - \left(\sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t} sb_{stc,p,t} + p^{st,ch}_{t} - \sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t} sb_{stc,p,t}\right) \ge -z^{st, ch}_{t} & \forall t \in \mathcal{T} \setminus 1\\ +&\left(\sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t-1} sb_{std,p,t-1} + p^{st,ds}_{t-1} - \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t-1} sb_{std,p,t-1}\right) &\\ +&-\left(\sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t} sb_{std,p,t-1} + p^{st,ds}_{t} - \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t} sb_{std,p,t}\right) \le z^{st, ds}_{t} & \forall t \in \mathcal{T} \setminus 1\\ +&\left(\sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t-1} sb_{std,p,t-1} + p^{st,ds}_{t-1} - \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t-1} sb_{std,p,t-1}\right) &\\ +&-\left(\sum_{p \in \mathcal{P}^{\text{as}_\text{up}}} R^*_{p,t} sb_{std,p,t} + p^{st,ds}_{t} - \sum_{p \in \mathcal{P}^{\text{as}_\text{dn}}} R^*_{p,t} sb_{std,p,t}\right) \ge -z^{st, ds}_{t} & \forall t \in \mathcal{T} \setminus 1 +\end{aligned} +``` diff --git a/docs/src/index.md b/docs/src/index.md index 4d73a28..4dbaa6f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,11 +4,48 @@ CurrentModule = StorageSystemsSimulations ``` +## Overview + +`StorageSimulations.jl` is a `PowerSimulations.jl` extension to support formulations and models +related to energ storage. + +Operational Storage Model can have multiple combinations of different restrictions. For instance, +it might be relevant to a study to consider cycling limits or employ energy targets coming from a planning model. To manage all these variations `StorageSimulations.jl` heavily uses the `DeviceModel` attributes feature. + +For example, the formulation `StorageDispatchWithReserves` can be parametrized as follows: + +```julia +DeviceModel( + StorageType, # E.g. BatteryEMS or GenericStorage + StorageDispatchWithReserves; + attributes=Dict( + "reservation" => true, + "cycling_limits" => false, + "energy_target" => false, + "complete_coverage" => false, + "regularization" => true, + ), + use_slacks=false, +) +``` + +Each formulation can have different implementations for these attributes and the details can be found in the Formulation Library section in the documentation. + ## Installation -The latest stable release of StorageSystemsSimulations can be installed using the Julia package manager with +The latest stable release of PowerSimulations can be installed using the Julia package manager with + +``` +(@v1.10) pkg> add PowerSimulations StorageSystemsSimulations +``` + +For the current development version, "checkout" this package with + +``` +(@v1.10) pkg> add PowerSimulations StorageSystemsSimulations#main +``` -* * * +An appropriate optimization solver is required for running StorageSystemsSimulations models. Refer to [`JuMP.jl` solver's page](https://jump.dev/JuMP.jl/stable/installation/#Install-a-solver) to select the most appropriate for the application of interest. -PowerSystems has been developed as part of the ..... at the U.S. Department of Energy's National Renewable Energy +StorageSystemsSimulations has been developed as part of the Scalable Integrated Infrastructure Planning (SIIP) initiative at the U.S. Department of Energy's National Renewable Energy Laboratory ([NREL](https://www.nrel.gov/)). diff --git a/docs/src/quick_start_guide.md b/docs/src/quick_start_guide.md index 0e4bdab..e183e3e 100644 --- a/docs/src/quick_start_guide.md +++ b/docs/src/quick_start_guide.md @@ -1,12 +1,12 @@ # Quick Start Guide - - **Julia:** If this is your first time using Julia visit our [Introduction to Julia](https://nrel-sienna.github.io/SIIP-Tutorial/fundamentals/introduction-to-julia/) and the official[Getting started with Julia](https://julialang.org/learning/). - - **Package Installation:** If you want to install packages check the [Package Manager](https://pkgdocs.julialang.org/v1/environments/) instructions, or you can refer to the [PowerSimulations installation instructions](@ref Installation). - - **PowerSystems:** [PowerSystems.jl](https://github.com/nrel-sienna/PowerSystems.jl) manages the data and is a fundamental dependency of PowerSimulations.jl. Check the [Understanding PowerSystems.jl page](https://nrel-sienna.github.io/SIIP-Tutorial/how-to/understanding-powersystems-components/) and [PowerSystems.jl documentation](https://nrel-sienna.github.io/PowerSystems.jl/stable/) to understand how the inputs to the models are organized. - - **Dataset Library:** If you don't have a data set to start using `PowerSimulations.jl` check the test systems provided in [`PowerSystemCaseBuilder.jl`](https://nrel-sienna.github.io/SIIP-Tutorial/how-to/power-system-case-builder/) + - **Julia:** If this is your first time using Julia visit our [Introduction to Julia](https://nrel-Sienna.github.io/SIIP-Tutorial/fundamentals/introduction-to-julia/) and the official [Getting started with Julia](https://julialang.org/learning/). + - **Package Installation:** If you want to install packages check the [Package Manager](https://pkgdocs.julialang.org/v1/environments/) instructions, or you can refer to the [StorageSystemsSimulations installation instructions](@ref Installation). + - **PowerSystems:** [PowerSystems.jl](https://github.com/nrel-Sienna/PowerSystems.jl) manages the data and is a fundamental dependency of PowerSimulations.jl. Check the [PowerSystems.jl Basics Tutorial](https://nrel-sienna.github.io/PowerSystems.jl/stable/tutorials/basics/) and [PowerSystems.jl documentation](https://nrel-Sienna.github.io/PowerSystems.jl/stable/) to understand how the inputs to the models are organized. + - **Dataset Library:** If you don't have a data set to start using `StorageSystemsSimulations.jl` check the test systems provided in [`PowerSystemCaseBuilder.jl`](https://nrel-sienna.github.io/PowerSystems.jl/stable/tutorials/powersystembuilder/) !!! tip - If you need to develop a dataset for a simulation check the [tutorials](https://nrel-sienna.github.io/SIIP-Tutorial/) on how to parse data and attach time series + If you need to develop a dataset for a simulation check the [PowerSystems.jl Tutorials](https://nrel-sienna.github.io/PowerSystems.jl/stable/tutorials/basics/) on how to parse data and attach time series - - **Tutorial:** If you are eager to run your first simulation visit the [Solve a Day Ahead Market Scheduling Problem using PowerSimulations.jl](https://nrel-sienna.github.io/SIIP-Tutorial/fundamentals/day-ahead-market/) tutorial + - **Tutorial:** If you are eager to run your first simulation visit the tutorial section of the documentation diff --git a/docs/src/tutorials/simulation_tutorial.md b/docs/src/tutorials/simulation_tutorial.md new file mode 100644 index 0000000..250e0b3 --- /dev/null +++ b/docs/src/tutorials/simulation_tutorial.md @@ -0,0 +1,33 @@ +# [Simulating operations with StorageSystemSimulations](@id sim_tutorial) + +**Originally Contributed by**: Jose Daniel Lara + +## Introduction + +## Load Packages + +```@example op_problem +using PowerSystems +using PowerSimulations +using StorageSystemsSimulations +using PowerSystemCaseBuilder +using HiGHS # solver +``` + +## Data + +!!! note + + `PowerSystemCaseBuilder.jl` is a helper library that makes it easier to reproduce examples in the documentation and tutorials. Normally you would pass your local files to create the system data instead of calling the function `build_system`. + For more details visit [PowerSystemCaseBuilder Documentation](https://nrel-sienna.github.io/PowerSystems.jl/stable/tutorials/powersystembuilder/) + +```@example op_problem +c_sys5_bat = build_system( + PSITestSystems, + "c_sys5_bat_ems"; + add_single_time_series=true, + add_reserves=true, +) +orcd = get_component(ReserveDemandCurve, c_sys5_bat, "ORDC1") +set_available!(orcd, false) +``` diff --git a/docs/src/tutorials/single_stage_model.md b/docs/src/tutorials/single_stage_model.md new file mode 100644 index 0000000..2c03c41 --- /dev/null +++ b/docs/src/tutorials/single_stage_model.md @@ -0,0 +1,66 @@ +# [Solving an operation with StorageSystemSimulations](@id op_problem_tutorial) + +**Originally Contributed by**: Jose Daniel Lara + +## Introduction + +## Load Packages + +```@example op_problem +using PowerSystems +using PowerSimulations +using StorageSystemsSimulations +using PowerSystemCaseBuilder +using HiGHS # solver +``` + +## Data + +!!! note + + `PowerSystemCaseBuilder.jl` is a helper library that makes it easier to reproduce examples in the documentation and tutorials. Normally you would pass your local files to create the system data instead of calling the function `build_system`. + For more details visit [PowerSystemCaseBuilder Documentation](https://nrel-sienna.github.io/PowerSystems.jl/stable/tutorials/powersystembuilder/) + +```@example op_problem +c_sys5_bat = build_system( + PSITestSystems, + "c_sys5_bat_ems"; + add_single_time_series=true, + add_reserves=true, +) +orcd = get_component(ReserveDemandCurve, c_sys5_bat, "ORDC1") +set_available!(orcd, false) +``` + +```@example op_problem +batt = get_component(BatteryEMS, c_sys5_bat, "Bat2") + +operation_cost = get_operation_cost(batt) +``` + +```@example op_problem +template_uc = ProblemTemplate(PTDFPowerModel) +set_device_model!(template_uc, ThermalStandard, ThermalStandardUnitCommitment) +set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) +set_device_model!(template_uc, PowerLoad, StaticPowerLoad) +set_device_model!(template_uc, Line, StaticBranch) +``` + +```@example op_problem +storage_model = DeviceModel( + BatteryEMS, + StorageDispatchWithReserves; + attributes=Dict( + "reservation" => true, + "energy_target" => false, + "cycling_limits" => false, + "regulatization" => true, + ), +) +set_device_model!(template_uc, storage_model) +``` + +```@example op_problem +set_service_model!(template_uc, ServiceModel(VariableReserve{ReserveUp}, RangeReserve)) +set_service_model!(template_uc, ServiceModel(VariableReserve{ReserveDown}, RangeReserve)) +``` diff --git a/src/StorageSystemsSimulations.jl b/src/StorageSystemsSimulations.jl index e18043a..e882b20 100644 --- a/src/StorageSystemsSimulations.jl +++ b/src/StorageSystemsSimulations.jl @@ -11,8 +11,8 @@ export StorageEnergyShortageVariable export StorageEnergySurplusVariable export StorageChargeCyclingSlackVariable export StorageDischargeCyclingSlackVariable -export ReserveDischargeConstraint -export ReserveChargeConstraint +export StorageRegularizationVariableCharge +export StorageRegularizationVariableDischarge # aux variables export StorageEnergyOutput @@ -26,6 +26,8 @@ export ReserveCoverageConstraintEndOfPeriod export ReserveCompleteCoverageConstraint export ReserveCompleteCoverageConstraintEndOfPeriod export StorageTotalReserveConstraint +export ReserveDischargeConstraint +export ReserveChargeConstraint # FF export EnergyTargetFeedforward diff --git a/src/core/formulations.jl b/src/core/formulations.jl index 0c6efe1..9670a9a 100644 --- a/src/core/formulations.jl +++ b/src/core/formulations.jl @@ -2,7 +2,25 @@ abstract type AbstractStorageFormulation <: PSI.AbstractDeviceFormulation end """ -Formulation type to add storage formulation than can provide ancillary services. With `attributes=Dict("reservation"=>true)` the formulation is augmented -with a binary variable to prevent simultaneous charging and discharging +Formulation type to add storage formulation than can provide ancillary services. If a +storage unit does not contribute to any service, then the variables and constraints related to +services are ignored. + +The formulation supports the following attributes. See Documentation for more details. + +```julia +DeviceModel( + StorageType, # E.g. BatteryEMS or GenericStorage + StorageDispatchWithReserves; + attributes=Dict( + "reservation" => true, + "cycling_limits" => false, + "energy_target" => false, + "complete_coverage" => false, + "regularization" => true, + ), + use_slacks=false, +) +``` """ struct StorageDispatchWithReserves <: AbstractStorageFormulation end diff --git a/src/core/variables.jl b/src/core/variables.jl index dbd6af2..d5a602c 100644 --- a/src/core/variables.jl +++ b/src/core/variables.jl @@ -11,24 +11,47 @@ struct AncillaryServiceVariableDischarge <: PSI.VariableType end struct AncillaryServiceVariableCharge <: PSI.VariableType end """ -Struct to dispatch the creation of a slack variable for energy storage levels < target storage levels +Slack variable for energy storage levels < target storage levels -Docs abbreviation: ``E^{shortage}`` +Docs abbreviation: ``e^{st-}`` """ struct StorageEnergyShortageVariable <: PSI.VariableType end """ -Struct to dispatch the creation of a slack variable for energy storage levels > target storage levels +Slack variable for energy storage levels > target storage levels -Docs abbreviation: ``E^{surplus}`` +Docs abbreviation: ``e^{st+}`` """ struct StorageEnergySurplusVariable <: PSI.VariableType end +""" +Slack variable for the cycling limits to allow for more charging usage than the allowed limited + +Docs nomenclature: ``c^{ch-}`` +""" struct StorageChargeCyclingSlackVariable <: PSI.VariableType end + +""" +Slack variable for the cycling limits to allow for more discharging usage than the allowed limited + +Docs nomenclature: ``c^{ds-}`` +""" struct StorageDischargeCyclingSlackVariable <: PSI.VariableType end abstract type StorageRegularizationVariable <: PSI.VariableType end + +""" +Slack variable for energy storage levels > target storage levels + + Docs nomenclature: ``z^{st, ch}`` +""" struct StorageRegularizationVariableCharge <: StorageRegularizationVariable end + +""" +Slack variable for energy storage levels > target storage levels + +Docs abbreviation: ``z^{st, ds}`` +""" struct StorageRegularizationVariableDischarge <: StorageRegularizationVariable end """ diff --git a/test/test_utils/mock_operation_models.jl b/test/test_utils/mock_operation_models.jl index df357ee..c6b5bc7 100644 --- a/test/test_utils/mock_operation_models.jl +++ b/test/test_utils/mock_operation_models.jl @@ -117,6 +117,7 @@ function mock_construct_device!( PSI.get_network_formulation(template), PSI.get_network_model(template).subnetworks, PSI.get_system(problem), + Dict{Int64, Set{Int64}}(), ) if PSI.validate_available_devices(model, PSI.get_system(problem)) PSI.construct_device!(