Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
90 changes: 90 additions & 0 deletions docs/control/pyomo_controllers.md
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super helpful to have the math laid out, thanks!

Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,93 @@ tech_to_dispatch_connections: [
["battery", "battery"],
]
```

# Optimized Demand Response Controller

This controller optimizes the dispatch of a Battery Energy Storage System (BESS) based on a pre-defined supervisory signal. This signal could be the Locational Marginal Price (LMP), a demand profile, or a $LMP\times demand$ product depending on the application. The objective is to maximize incentive payments to the battery, subject to constraints on the maximum number of dispatch events per month and on the battery state of charge.

## Definitions

**Given:**
- $\lambda_t$ := `supervisory_signal`: price, demand, or price $\times$ demand time series at time $t$
- $\mathcal{W}$ := `peak_window`: set of hours eligible for dispatch (e.g., 12:00--19:00)
- $\gamma$ := performance incentive (\$/kW per dispatch hour)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this truly required to be per hour, or is it per time-step? In other words, could it be per-minute if the user requested a smaller time step?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that H2I only supports hourly timesteps. I can make this more general.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are currently in the process of enabling smaller time steps, so we are building the capability into new additions to the code!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's awesome!

- $\bar{P}$ := `max_charge_rate` (kW): maximum charge and discharge rate, used as deemed capacity since the battery is assumed to always dispatch at full rated power
- $E_{\max} :=$ `max_capacity` $\times$ (`max_soc_fraction` $-$ `min_soc_fraction`): usable energy capacity (kWh)
- $\eta_c$ := `charge_efficiency`, $\quad \eta_d$ := `discharge_efficiency`
- $\overline{\text{SoC}}$ := `max_soc_fraction`, $\quad \underline{\text{SoC}}$ := `min_soc_fraction`
- `n_control_window` := Horizon length for optimization
- $\mathcal{T} := \{0, 1, \ldots, T\}$: hourly time steps over `n_control_window`
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hours or time steps?

- $\mathcal{M}_m$ := set of hours in month $m$, for $m = 1, \ldots, 12$
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, does this have to be hours?


## Decision Variables

- $u_t \in \{0, 1\}$ := discharge binary: 1 if battery dispatches at hour $t$, 0 otherwise
- $v_t \in \{0, 1\}$ := charge binary: 1 if battery charges at hour $t$, 0 otherwise

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hours or time steps?

## Optimization Problem

This optimization is executed for each window, during which the performance model is invoked and the initial conditions are set.

### Objective

Maximize total annual incentive revenue:

$$
\max_{u_t,\, v_t} \quad \gamma \cdot \bar{P} \sum_{t \in \mathcal{T}} u_t
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the objective be based on max charge/discharge rate, or on actual discharge rate at each time step?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was assuming that the battery always charges and discharges at max discharge rate. If we want to solve for the actual discharge rate as well as when (the binary var) we want to discharge, the math becomes a little harder. I can work on that tomorrow.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how this objective will lead to reducing peaks. It seems like it could just reduce the total demand during the peak window without actually reducing the peak. I understand the threshold on percentile of peak in the horizon constraint, but I think we may want to add something to the objective related to reducing the peaks rather than just maximizing revenue. I think this is probably what you are holding for the next PR though.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized the objective here is what is supposed to manage for the supervisor, but revenue is for the local entity. The supervisor wants to reduce their peaks during the most critical times of the month, but I'm sure maximizing the local revenue will achieve that. We can assume that maximizing revenue locally will maximize revenue upstream as well, which assumes a perfect incentive structure. I think I've convinced myself this is ok for now, but let's make sure we continue to keep this in mind moving forward.

$$

### Constraints

- Dispatch only within peak window:

$$
u_t = 0 \qquad \forall\, t \notin \mathcal{W}
$$

- Dispatch only on high supervisory signal:

$$
u_t = 1 \implies \lambda_t \geq \lambda^*_m \qquad \forall\, t \in \mathcal{M}_m
$$

where $\lambda^*_m$ is the threshold selecting the high LMP/peak load hours within month $m$.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to use a threshold as you have it here. We may want to consider ramp rate or some additional criteria as well in the future.


- Maximum $N_{max}$ events per month:

$$
\sum_{t \in \mathcal{M}_m} u_t \leq N_{\max} \qquad \forall\, m, \quad N_{\max}.
$$

- SoC evolution with charge and discharge:

$$
\text{SoC}_{t+1} = \text{SoC}_t + \frac{\eta_c \cdot v_t \cdot \bar{P}}{E_{\max}} - \frac{u_t \cdot \bar{P}}{\eta_d \cdot E_{\max}} \qquad \forall\, t \in \mathcal{T}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to use actual charge/discharge rate here instead of rated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to discuss whether we want to assume SOC is based on nameplate capacity or actual capacity. In other components we have based it on nameplate capacity, but here you are using actual capacity. I think we should pick a standard for H2I and stick with it. I'm up for conversation on this for sure and would be very interested to get feedback from others, such as @genevievestarke and @aditiegarg9.

$$

- SoC bounds:

$$
\underline{\text{SoC}} \leq \text{SoC}_t \leq \overline{\text{SoC}} \qquad \forall\, t \in \mathcal{T}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to include bars and underscores here to indicate max and min SOC?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

$$

- No simultaneous charge and discharge:

$$
u_t + v_t \leq 1 \qquad \forall\, t \in \mathcal{T}
$$

- No charging during dispatch window (battery reserved for discharge):

$$
v_t = 0 \qquad \forall\, t \in \mathcal{W}
$$

- Binary variables:

$$
u_t \in \{0, 1\}, \quad v_t \in \{0, 1\}, \quad \text{SoC}_t \in [0, 1] \qquad \forall\, t, m
$$
Example 34 performs the optimization with a synthetic LMP signal. The look-ahead horizon is set to 10 hours. As this value increases, the computational complexity grows and the solver may take significantly longer to run or fail to converge. Care should be taken when choosing this parameter: a short horizon limits visibility, making it difficult for the optimizer to identify the best dispatch opportunities across the full month. See figure below for the results.

![](./figures/plm_optimized_dispatch.png)
1 change: 1 addition & 0 deletions docs/user_guide/model_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ Below summarizes the available performance, cost, and financial models for each
- `'SimpleStorageOpenLoopController'`: open-loop control; manages resource flow based on demand and input commodity
- `'DemandOpenLoopStorageController'`: open-loop control; manages resource flow based on demand and storage constraints
- `'HeuristicLoadFollowingStorageController'`: open-loop control that works on a time window basis to set dispatch commands; uses Pyomo
- `'PLMOptimizedStorageController'`: optimized controller for demand response that works on a time window basis.
- `'PeakLoadManagementHeuristicOpenLoopStorageController'`: open-loop control that reduces peaks rather than trying to meet a load
- Optimized Dispatch:
- `'OptimizedDispatchStorageController'`: optimization-based dispatch using Pyomo
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: H2Integrate_config
system_summary: PLM MILP-optimized battery dispatch
driver_config: driver_config.yaml
technology_config: tech_config.yaml
plant_config: plant_config.yaml
Loading
Loading