Skip to content

Commit 8a21d11

Browse files
Battery Degradation How-To (#78)
1 parent 55af31d commit 8a21d11

File tree

2 files changed

+190
-1
lines changed

2 files changed

+190
-1
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
Battery degradation is where battery performance reduces with time or battery use.
2+
3+
The performance of the battery is defined by the parameters of power (MW), capacity (MWh) and efficiency (%).
4+
5+
`energypylinear` does not model battery degradation within a single simulation - degradation can be handled by splitting up the battery lifetime into multiple simulations.
6+
7+
## Modelling a Single Year in Monthly Chunks
8+
9+
To handle battery degradation over a year, we will split the year into 12 months and run a simulation for each month:
10+
11+
<!--phmdoctest-share-names-->
12+
```python
13+
import numpy as np
14+
import pandas as pd
15+
16+
import energypylinear as epl
17+
18+
np.random.seed(42)
19+
days = 35
20+
dataset = pd.DataFrame({
21+
"timestamp": pd.date_range("2021-01-01", periods=days * 24, freq="h"),
22+
"prices": np.random.normal(-1000, 1000, days * 24) + 100
23+
})
24+
battery_params = {
25+
"power_mw": 4,
26+
"capacity_mwh": 10,
27+
"efficiency_pct": 0.9,
28+
"freq_mins": 60
29+
}
30+
31+
results = []
32+
objs = []
33+
for month, group in dataset.groupby(dataset['timestamp'].dt.month):
34+
print(f"Month {month}")
35+
battery = epl.Battery(electricity_prices=group['prices'], **battery_params)
36+
simulation = battery.optimize(verbose=3)
37+
results.append(simulation.results)
38+
objs.append(simulation.status.objective)
39+
40+
year = pd.concat(results)
41+
assert year.shape[0] == days * 24
42+
account = epl.get_accounts(year, verbose=3)
43+
np.testing.assert_allclose(account.profit, -1 * sum(objs))
44+
print(account)
45+
```
46+
47+
```
48+
Month 1
49+
Month 2
50+
<Accounts profit=2501349.07 emissions=15.7333>
51+
```
52+
53+
The results above do not include any battery degradation - battery parameters are the same at the start of each month.
54+
55+
## Modelling Degradation
56+
57+
To model degradation, we need to take a view on how our battery parameters change over time.
58+
59+
For our simulation, we will model:
60+
61+
- battery power decays by 0.1 MW for each 150 MWh of battery charge,
62+
- battery capacity decays by 0.1 MWh for each 150 MWh of battery charge,
63+
- battery efficiency decays by 0.1% over 30 days.
64+
65+
<!--phmdoctest-share-names-->
66+
```python
67+
def get_battery_params(cumulative_charge_mwh: float = 0, cumulative_days: float = 0) -> dict:
68+
"""Get degraded battery parameters based on usage and time."""
69+
power_decay_mw_per_mwh = 0.1 / 150
70+
capacity_decay_mwh_per_mwh = 0.1 / 150
71+
efficiency_decay_pct_per_day = 0.1 / 30
72+
return {
73+
"power_mw": 4 - power_decay_mw_per_mwh * cumulative_charge_mwh,
74+
"capacity_mwh": 10 - capacity_decay_mwh_per_mwh * cumulative_charge_mwh,
75+
"efficiency_pct": 0.9 - efficiency_decay_pct_per_day * cumulative_days,
76+
"freq_mins": 60
77+
}
78+
```
79+
80+
For a fresh battery, our battery parameters are:
81+
82+
<!--phmdoctest-share-names-->
83+
```python
84+
print(get_battery_params())
85+
```
86+
87+
```
88+
{'power_mw': 4.0, 'capacity_mwh': 10.0, 'efficiency_pct': 0.9, 'freq_mins': 60}
89+
```
90+
91+
For a battery that has been charged with 300 MWh over 60 days, our battery parameters are:
92+
93+
<!--phmdoctest-share-names-->
94+
```python
95+
print(get_battery_params(cumulative_charge_mwh=300, cumulative_days=60))
96+
```
97+
98+
```
99+
{'power_mw': 3.8, 'capacity_mwh': 9.8, 'efficiency_pct': 0.7, 'freq_mins': 60}
100+
```
101+
102+
## Modelling a Single Year in Monthly Chunks with Degradation
103+
104+
We can include our battery degradation model in our simulation by keeping track of our battery usage and updating the battery parameters at the start of each month:
105+
106+
<!--phmdoctest-share-names-->
107+
```python
108+
import collections
109+
110+
results = []
111+
cumulative = collections.defaultdict(float)
112+
for month, group in dataset.groupby(dataset['timestamp'].dt.month):
113+
battery_params = get_battery_params(
114+
cumulative_charge_mwh=cumulative['charge_mwh'],
115+
cumulative_days=cumulative['days']
116+
)
117+
print(f"Month: {month}, Battery Params: {battery_params}")
118+
battery = epl.Battery(electricity_prices=group['prices'], **battery_params)
119+
simulation = battery.optimize(verbose=3)
120+
results.append(simulation.results)
121+
cumulative['charge_mwh'] += simulation.results['battery-electric_charge_mwh'].sum()
122+
cumulative['days'] += group.shape[0] / 24
123+
124+
year = pd.concat(results)
125+
assert year.shape[0] == days * 24
126+
account = epl.get_accounts(year, verbose=3)
127+
print(account)
128+
```
129+
130+
```
131+
Month: 1, Battery Params: {'power_mw': 4.0, 'capacity_mwh': 10.0, 'efficiency_pct': 0.9, 'freq_mins': 60}
132+
Month: 2, Battery Params: {'power_mw': 3.0663703705399996, 'capacity_mwh': 9.06637037054, 'efficiency_pct': 0.7966666666666666, 'freq_mins': 60}
133+
<Accounts profit=2460059.00 emissions=16.9273>
134+
```
135+
136+
## Full Example
137+
138+
```python
139+
import collections
140+
141+
import numpy as np
142+
import pandas as pd
143+
144+
import energypylinear as epl
145+
146+
def get_battery_params(cumulative_charge_mwh: float = 0, cumulative_days: float = 0) -> dict:
147+
"""Get degraded battery parameters based on usage and time."""
148+
power_decay_mw_per_mwh = 0.1 / 150
149+
capacity_decay_mwh_per_mwh = 0.1 / 150
150+
efficiency_decay_pct_per_day = 0.1 / 30
151+
return {
152+
"power_mw": 4 - power_decay_mw_per_mwh * cumulative_charge_mwh,
153+
"capacity_mwh": 10 - capacity_decay_mwh_per_mwh * cumulative_charge_mwh,
154+
"efficiency_pct": 0.9 - efficiency_decay_pct_per_day * cumulative_days,
155+
"freq_mins": 60
156+
}
157+
158+
np.random.seed(42)
159+
days = 35
160+
dataset = pd.DataFrame({
161+
"timestamp": pd.date_range("2021-01-01", periods=days * 24, freq="h"),
162+
"prices": np.random.normal(-1000, 1000, days * 24) + 100
163+
})
164+
165+
results = []
166+
cumulative = collections.defaultdict(float)
167+
for month, group in dataset.groupby(dataset['timestamp'].dt.month):
168+
battery_params = get_battery_params(
169+
cumulative_charge_mwh=cumulative['charge_mwh'],
170+
cumulative_days=cumulative['days']
171+
)
172+
print(f"Month: {month}, Battery Params: {battery_params}")
173+
battery = epl.Battery(electricity_prices=group['prices'], **battery_params)
174+
simulation = battery.optimize(verbose=3)
175+
results.append(simulation.results)
176+
cumulative['charge_mwh'] += simulation.results['battery-electric_charge_mwh'].sum()
177+
cumulative['days'] += group.shape[0] / 24
178+
179+
year = pd.concat(results)
180+
assert year.shape[0] == days * 24
181+
account = epl.get_accounts(year, verbose=3)
182+
print(account)
183+
```
184+
185+
```
186+
Month: 1, Battery Params: {'power_mw': 4.0, 'capacity_mwh': 10.0, 'efficiency_pct': 0.9, 'freq_mins': 60}
187+
Month: 2, Battery Params: {'power_mw': 3.0663703705399996, 'capacity_mwh': 9.06637037054, 'efficiency_pct': 0.7966666666666666, 'freq_mins': 60}
188+
<Accounts profit=2460059.00 emissions=16.9273>
189+
```

docs/docs/how-to/price-carbon.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
`energypylinear` has the ability to optimize for both price and carbon as optimization objectives.
1+
`energypylinear` can optimize for both price and carbon as optimization objectives.
22

33
This ability comes from two things - an objective function, which can be either for price or carbon, along with accounting of both price and carbon emissions.
44

0 commit comments

Comments
 (0)