--- jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.13.8 kernelspec: display_name: Python 3 (ipykernel) language: python name: python3 --- (battery-wind-solar)= # Battery, solar and wind This example shows how [batteries](battery) can be modelled in SHOP to smooth variations for [](solar) or [](wind) to meet a firm demand. We define the optimization horizon over 24 hours with 15 minutes time resolution: ```{code-cell} ipython3 :Collapsed: 'false' from pyshop import ShopSession import pandas as pd import numpy as np import plotly.graph_objs as go shop = ShopSession() starttime = pd.Timestamp('2019-05-16T00:00:00') endtime = pd.Timestamp('2019-05-17T00:00:00') shop.set_time_resolution(starttime=starttime, endtime=endtime, timeunit="minute", timeresolution=pd.Series(index=[starttime],data=[15])) ``` We create a battery with 90% charge and discharge efficiency. The battery size is 4 MWh with 0.5 MW as the maximum charge and discharge capacity. The initial state-of-charge is 2 MWh and end state-of-charge must be greater than or equal to the initial. ```{code-cell} ipython3 :Collapsed: 'false' battery = shop.model.battery.add_object('Battery') battery.charge_efficiency.set(0.9) battery.discharge_efficiency.set(0.9) battery.max_charge_power.set(0.5) battery.max_discharge_power.set(0.5) battery.max_energy.set(4.0) battery.initial_energy.set(2) init_value = battery.initial_energy.get() battery.min_energy_constraint.set(pd.Series(index=[starttime, endtime - pd.Timedelta(hours=1)], data=[np.nan, init_value])) ``` We also create a solar power source with a typical profile acquired from [renewables.ninja](https://www.renewables.ninja/): ```{code-cell} ipython3 :Collapsed: 'false' solar_profile = pd.read_csv('solar_profile.csv', header=3).set_index('time') solar_profile.index = pd.to_datetime(solar_profile.index) solar_profile = solar_profile[starttime:endtime]['electricity'] * 1.6 solar = shop.model.solar.add_object('Solar') solar.power_forecast.set(solar_profile) fig = go.Figure() fig.add_trace(go.Scatter(x=solar_profile.index, y=solar_profile.values, name="Solar power production")) fig.update_layout(xaxis_title="Time", yaxis_title="Power") fig.show() ``` Finally, we create a [](market) where we can buy energy (but not sell) and a [](busbar) where the load is set and all components are connected: ```{code-cell} ipython3 :Collapsed: 'false' :tags: ['remove-output'] market = shop.model.market.add_object('Market') market.max_buy.set(1) market.max_sale.set(0) price = pd.Series(index=pd.date_range(starttime, endtime, freq='6H'), data=[10,20,15,5,5]) market.buy_price.set(price) # market.max_sale.set(1) # market.sale_price.set(price - 0.01) busbar = shop.model.busbar.add_object('Busbar') busbar.connect_to(battery) busbar.connect_to(solar) busbar.connect_to(market) busbar.load.set(0.35) shop.start_sim([], [1]) ``` Since the battery model is linear, we only need one iteration. ## Results We can investigate how the battery is used to maximize the utilization of solar power, where the marginal operating cost is zero. The first figure below shows how the battery energy level evolves over the optimization horizon, while the second figure shows the power output of the three sources - solar, battery and market purchase. ```{code-cell} ipython3 :Collapsed: 'false' battery_energy = battery.energy.get() fig = go.Figure() fig.add_trace(go.Scatter(x=battery_energy.index, y=battery_energy.values, name="Battery energy")) fig.update_layout(xaxis_title="Time", yaxis_title="Energy", title="Battery energy balance") fig.show() ``` ```{code-cell} ipython3 :Collapsed: 'false' net_discharge = battery.net_power_discharge.get() buy = market.buy.get() fig = go.Figure() fig.add_trace(go.Scatter(x=buy.index, y=buy.values, name="Market purchase")) fig.add_trace(go.Scatter(x=net_discharge.index, y=net_discharge.values, name="Battery discharge")) fig.add_trace(go.Scatter(x=solar_profile.index, y=solar_profile.values, name="Solar power")) fig.update_layout(xaxis_title="Time", yaxis_title="Power", title="Power delivered") fig.show() ``` The battery trajectory shows how stored energy is used initially to minimize power purchase during the night, while the battery is charged during the day. Some purchase is required in the evening to reach the initial level. We can also study the dual values to investigate the locational marginal price and the value of stored energy in this system. ```{code-cell} ipython3 :Collapsed: 'false' energy_value = battery.energy_value.get() energy_price = busbar.energy_price.get() market_price = market.buy_price.get() fig = go.Figure() fig.add_trace(go.Scatter(x=energy_value.index, y=energy_value.values, name="Storage energy value")) fig.add_trace(go.Scatter(x=energy_price.index, y=energy_price.values, name="Busbar energy price")) fig.add_trace(go.Scatter(x=market_price.index, y=market_price.values, name="Market energy price")) fig.update_layout(xaxis_title="Time", yaxis_title="Power", title="Dual values") fig.show() ``` The storage marginal value (SMV) of the battery is always 4.5, which is the purchase price in the last hours multiplied with efficiency. The battery is the marginal unit in all periods except mid-day. When the battery is discharging, the busbar price becomes 5, which is the SMV multiplied with the discharge efficiency. When it is charging, the busbar price becomes 4.05, which is the SMV divided by the charge efficiency. In the middle of the day, the solar power is the marginal unit driving the price to zero. The battery is able to charge all the necessary energy in the evening resulting in the SMV of 4.5. Try to enable market sale to see how that influences the prices in the system.