Optimizing energy assets with mixed-integer linear programming.
Project description
energy-py-linear
A Python library for optimizing the dispatch of energy assets with mixed-integer linear programming:
- electric batteries,
- combined heat & power (CHP) generators,
- electric vehicle smart charging.
Models can be optimized to either maximize profit or minimize carbon emissions.
Setup
Requires Python 3.10+:
$ pip install energypylinear
Assets
Battery
Dispatch an electric battery operating in wholesale price arbitrage using epl.Battery
:
import energypylinear as epl
# 2.0 MW, 4.0 MWh battery
asset = epl.battery.Battery(power_mw=2, capacity_mwh=4, efficiency=0.9)
results = asset.optimize(
electricity_prices=[100.0, 50, 200, -100, 0, 200, 100, -100],
freq_mins=60,
initial_charge_mwh=1,
final_charge_mwh=3
)
CHP
Dispatch a CHP unit to generate electricity and heat from natural gas.
The epl.Generator
model can be configured with electric, high and low temperature thermal efficiencies.
This allows modelling both gas engines and gas turbines:
import energypylinear as epl
# 100 MWe gas engine
asset = epl.chp.Generator(
electric_power_max_mw=100,
electric_power_min_mw=30,
electric_efficiency_pct=0.4,
high_temperature_efficiency_pct=0.2,
low_temperature_efficiency_pct=0.2,
)
# 100 MWe gas turbine
asset = epl.chp.Generator(
electric_power_max_mw=100,
electric_power_min_mw=50,
electric_efficiency_pct=0.3,
high_temperature_efficiency_pct=0.5,
)
When optimizing, we can use interval data for the high and low temperature loads. These thermal loads will be met by gas boilers if the CHP chooses not to generate.
The epl.chp.Generator
is allowed to dump both high temperature and low temperature heat.
Dispatch a CHP generator:
import energypylinear as epl
# 100 MWe gas engine
asset = epl.chp.Generator(
electric_power_max_mw=100,
electric_power_min_mw=10,
electric_efficiency_pct=0.4,
high_temperature_efficiency_pct=0.2,
low_temperature_efficiency_pct=0.2,
)
results = asset.optimize(
electricity_prices=[100, 50, 200, -100, 0, 200, 100, -100],
high_temperature_load_mwh=[100, 50, 200, 40, 0, 200, 100, 100],
low_temperature_load_mwh=20
)
EVs
Control a number of EV chargers to charge a number of charge events.
A charge_event
is a period of time where an EV can be charged. This is given as a boolean 2D array, with one binary digit for each pair of charge events and intervals.
Each charge event has a required amount of electricity charge_event_mwh
, that can be delivered anytime the charge_event
is 1.
Chargers are configured by charger_mws
.
Optimize two 100 MWe chargers for 4 charge events over 5 intervals:
import energypylinear as epl
# 2 100 MW EV chargers
asset = epl.evs.EVs(charger_mws=[100, 100], charger_turndown=0.1)
electricity_prices = [-100, 50, 30, 50, 40]
charge_events = [
[1, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 1, 1],
[0, 1, 0, 0, 0],
]
charge_event_mwh = [50, 100, 30, 40]
results = asset.optimize(
electricity_prices=electricity_prices,
charge_events=charge_events,
charge_event_mwh=charge_event_mwh,
)
Examples
Examples for exist in ./examples
:
$ ls ./examples
./examples
├── battery.py
├── chp.py
├── evs.py
└── forecast-accuracy.py
Use Cases
Price vs. Carbon Optimization
A key feature of energypylinear
is the ability to optimize for both price and carbon.
We can dispatch a battery to minimize carbon emissions by passing in objective='carbon'
:
import energypylinear as epl
asset = epl.battery.Battery(power_mw=2, capacity_mwh=4, efficiency=0.9)
results = asset.optimize(
electricity_prices=[100, 50, 200, -100, 0, 200, 100, -100],
electricity_carbon_intensities = [0.1, 0.2, 0.1, 0.15, 0.01, 0.7, 0.5, 0.01],
objective='carbon'
)
We can compare these results above with a simulation that optimizes for price, using a energypylinear.accounting.Account
to compare both simulations. The accounting API is in it's first iteration - expect it to change in the future.
import energypylinear as epl
# interval data
electricity_prices = [100, 50, 200, -100, 0, 200, 100, -100]
electricity_carbon_intensities = [0.1, 0.2, 0.1, 0.15, 0.01, 0.7, 0.5, 0.01]
# battery asset
asset = epl.battery.Battery(power_mw=2, capacity_mwh=4, efficiency=0.9)
# optimize for money
price = asset.optimize(electricity_prices=electricity_prices)
# optimize for the planet
carbon = asset.optimize(
electricity_prices=electricity_prices,
electricity_carbon_intensities=electricity_carbon_intensities,
objective='carbon'
)
# get an account representing the difference between the two
price = epl.get_accounts(
price.interval_data,
price.simulation,
)
carbon = epl.get_accounts(
carbon.interval_data,
carbon.simulation,
)
print(price)
# cost=-1057.777778 emissions=0.08222222199999996 profit=1057.777778
print(carbon)
# cost=-134.44444399999998 emissions=-2.2733333339999997 profit=134.44444399999998
variance = price - carbon
print(variance)
# cost=-923.3333339999999 emissions=2.3555555559999997
print(-variance.cost / variance.emissions)
# 391.9811322845319
Our optimization for price has a high negative cost. The optimization for carbon has lower emissions, but at a higher cost.
Dispatch for Actuals vs. Dispatch for Forecasts
The same primitives can be used to model the variance in performance of an asset optimized for actual prices versus forecast prices:
import energypylinear as epl
# interval data
electricity_prices = [100, 50, 200, -100, 0, 200, 100, -100]
forecasts = [-100, 0, 200, 100, -100, 100, 50, 200]
# battery asset
asset = epl.battery.Battery(power_mw=2, capacity_mwh=4, efficiency=0.9)
# optimize with perfect foresight
actual = asset.optimize(electricity_prices=electricity_prices)
# optimize to the forecast
forecast = asset.optimize(electricity_prices=forecasts)
# create accounts for the two scenarios
perfect_foresight = epl.get_accounts(actual.interval_data, actual.simulation)
# in the forecast we use the actual interval_data, not the forecast interval_data
forecast = epl.get_accounts(actual.interval_data, forecast.simulation)
variance = perfect_foresight - forecast
print(variance)
# cost=-1197.777778 emissions=0.002222221999999996
This idea is also modelled in examples/forecast-accuracy.py.
Test
$ make test
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for energypylinear-0.1.1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3b9f797f4997cb74b5eed3aec57f18e63742573f8110b6704e5e55f48139cc68 |
|
MD5 | 03959e8f8392e76265b4c50770c332ba |
|
BLAKE2b-256 | 090effde2d9e7fa6c2806868d808c75fc8ea10bdac21d5a688ffa49068c37430 |