Start- and stop costs for river flow#

This example demonstrates how start- and stop costs can be applied to river flow to avoid gates having to start and stop flowing repeatedly, as this can be costly. The start cost can be set through the attributes flow_start_cost and flow_start_cost_const. If both attributes are set, the timeseries attribute will be preferred. The stop cost can be set through the attributes flow_stop_cost and flow_stop_cost_const .

The constraints that are needed for applying the start- and stop costs are similar to the commitment constraints for generators and pumps. The unit commitment constraints for generators are explained here.

To build the constraints for applying the river flow start- and stop costs, a minimum allowed flow must be given through the min_allowed_flow attribute in order to properly decide if water is flowing or not. Additionally, a value for the absolute maximum flow in the river must be set through the absolute_max_flow attribute.

#Necessary imports 
import pandas as pd
import plotly.graph_objs as go

from pyshop import ShopSession

#Functions used in this example for building a SHOP modeland running it
from river import build_model, run_model

Create SHOP session and import model#

Our example model has one river connecting two reservoirs, and a downstream plant.

#Create a standard ShopSession
shop=ShopSession()

#Build SHOP model
build_model(shop)

#Display topology to the screen
display(shop.model.build_connection_tree())
../../_images/954121a5ae7d2cb148dce3479602d9a1af98c705ec143578a71bfd8af2b76453.svg

Run model without any river flow start- or stop costs#

We start by running the model without any start- or stop costs, and plot the resulting flow in the river. As the plot shows, the flow is stopped and started several times during the optimization period.

# Run model
run_model(shop)

# Get river flow
river = shop.model.river.River1
flow = river.upstream_flow.get()

# Plot results
fig = go.Figure()
fig.add_trace(go.Bar(x=flow.index, y=flow.values))
fig.update_layout(xaxis_title="Time", yaxis_title="Upstream flow", title = "River flow")
fig.show()

Add flow start- and stop costs and rerun model#

We now add a start cost and a stop cost for the river, set the flow_commitment_mip_flag to 1, and rerun the model. As the resulting plot shows, the river avoids starting and stopping by flowing the minimum allowed amount instead.

#Create a new ShopSession
shop=ShopSession()

#Build SHOP model
build_model(shop)

river = shop.model.river.River1

# Add start- and stop costs
river.flow_start_cost_const.set(10)
river.flow_stop_cost_const.set(10)

# Set the minimum allowed flow and the absolute max flow
river.min_allowed_flow.set(5)
river.absolute_max_flow.set(100)
river.flow_commitment_mip_flag.set(1)

# Run model
run_model(shop)

# Get river flow
flow = river.upstream_flow.get()

# Plot results
fig = go.Figure()
fig.add_trace(go.Bar(x=flow.index, y=flow.values))
fig.update_layout(xaxis_title="Time", yaxis_title="Upstream flow", title = "River flow")
fig.show()

Start and stop costs for the first time step#

If no historical flow data is provided, no start- or stop costs will be applied for the first timestep. However, if historical flow data is provided through the past_upstream_flow attribute, the river flow state before the optimization period is used to build the constraints for the first time step.

Binary variables#

The default behaviour is to not use binary variables for the river flow commitment constraints. The use of binary variables can be turned on by the flow_commitment_mip_flag attribute, as shown in the example. The start and stop costs are still modelled with continuous variables when the mip flag is 0 (or not set at all), which means that the minimum allowed flow limit can be violated by taking a fractional start or stop cost.

In the same way as the generator commitment variables are locked in the incremental iterations, the river flow commitment variables can be locked by the lock_river_flow_commitment command. If the on/off decisions are not locked, binary variables will be used in incremental iterations if the flow_commitment_mip_flag is active.

river.py#

import pandas as pd
import plotly.offline as py
import plotly.graph_objs as go
from plotly.subplots import make_subplots
import numpy as np

def build_model(shop):
    starttime = pd.Timestamp('2018-02-27')
    endtime = pd.Timestamp('2018-02-28')
    starttime = pd.Timestamp('2018-02-27')
    endtime = pd.Timestamp('2018-02-28')
    shop.set_time_resolution(starttime=starttime, endtime=endtime, timeunit='hour')

    # Add topology
    rsv1 = shop.model.reservoir.add_object('Reservoir1')
    rsv1.max_vol.set(12)
    rsv1.lrl.set(90)
    rsv1.hrl.set(100)
    rsv1.vol_head.set(pd.Series([90, 100, 101], index=[0, 12, 14], name=0))
    rsv1.flow_descr.set(pd.Series([0, 1000], index=[100, 101], name=0))

    river1 = shop.model.river.add_object('River1')
    river1.upstream_elevation.set(90)
    
    rsv2 = shop.model.reservoir.add_object('Reservoir2')
    rsv2.max_vol.set(1)
    rsv2.lrl.set(85)
    rsv2.hrl.set(86)
    rsv2.vol_head.set(pd.Series([85, 86, 87], index=[0, 1, 2]))
    rsv2.flow_descr.set(pd.Series([0, 1000], index=[86, 87]))

    plant1 = shop.model.plant.add_object('Plant1')
    plant1.outlet_line.set(40)
    plant1.main_loss.set([0.0002])
    plant1.penstock_loss.set([0.0001])

    p1g1 = shop.model.generator.add_object('Plant1_G1')
    plant1.connect_to(p1g1)
    p1g1.penstock.set(1)
    p1g1.p_min.set(15)
    p1g1.p_max.set(100)
    p1g1.p_nom.set(100)
    p1g1.startcost.set(500)
    p1g1.gen_eff_curve.set(pd.Series([95, 98], index=[0, 100]))
    p1g1.turb_eff_curves.set([pd.Series([70, 80, 95, 90], index=[10, 25, 90, 100], name=45),
                            pd.Series([72, 82, 98, 92], index=[10, 25, 90, 100], name=55)])

    # Connect objects
    rsv1.connect_to(river1)
    river1.connect_to(rsv2)
    rsv2.connect_to(plant1)

    rsv1.start_head.set(94)
    rsv2.start_head.set(85.95)
    rsv1.energy_value_input.set(38.6)
    rsv2.energy_value_input.set(38.6)

    shop.model.market.add_object('Day_ahead')
    da = shop.model.market.Day_ahead
    da.max_buy.set(9999)
    da.max_sale.set(9999)
    shop.model.market.Day_ahead.sale_price.set(pd.DataFrame([30,80,80,30,30,30,30,30,40,40,30,30,40,30,30,30,30,40,40,30,30,30,30,30],index=[starttime+ pd.Timedelta(hours=i) for i in range(0,24)]))
    shop.model.market.Day_ahead.buy_price.set(shop.model.market.Day_ahead.sale_price.get()+0.002)

    # add gate
    river1.width_depth_curve.set(pd.Series([0,5],index=[2,2],name=0))
    river1.gate_opening_curve.set(pd.Series([0,10],index=[0,1],name=0))
    river1.mip_flag.set(1)

    rsv1.inflow.set(pd.DataFrame([101, 50], index=[starttime, starttime + pd.Timedelta(hours=1)]))
    return shop

def run_model(shop):
    shop.start_sim([],['3'])
    shop.set_code(['inc'],[])
    shop.start_sim([],['3'])