#!/usr/bin/env python # coding: utf-8 # # Rule Curves for Rainy and Namakan Lakes # ### Initializations # In[33]: # Display graphics inline with the notebook get_ipython().run_line_magic('matplotlib', 'inline') # Standard Python modules import numpy as np import matplotlib.pyplot as plt import seaborn as sns import matplotlib.dates as mdates import pandas as pd import os import datetime # Modules to display images and data tables from IPython.display import Image from IPython.core.display import display # set graphics context sns.set_context('talk') # directories dir = '../data/' img = '../images/' # ### Plotting Functions # In[34]: # plot rule curve with a specified color def plotRC(RC,color,alpha=0.5): plt.plot(RC.index,RC['EHW'],color=color,alpha=alpha) plt.plot(RC.index,RC['ELW'],color=color,alpha=alpha) plt.plot(RC.index,RC['AGO'],color+'--',alpha=alpha) RC['LRC'].plot(color=color,lw=3,alpha=alpha) RC['URC'].plot(color=color,lw=3,alpha=alpha) if 'EDL' in RC.columns: RC['EDL'].plot(color=color,lw=3,alpha=alpha) plt.fill_between(RC.index, RC['LRC'].tolist(), RC['URC'].tolist(), color=color, alpha=alpha) plt.ylabel('meters') plt.ylim(336,341.5) plt.gca().xaxis.set_major_locator(mdates.MonthLocator()) plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b')) # add right hand y-axis scaled in feet def twiny(): ax1 = plt.gca() locs = ax1.get_yticks() y1,y2 = ax1.get_ylim() ax2 = plt.twinx() ax2.set_ylim(y1,y2) lbls = ['{0:.2f}'.format(y*3.2808) for y in locs] ax2.set_yticklabels(lbls) plt.grid(None) ax2.set_ylabel('feet') def saveRC(fname): fname = img + fname plt.savefig(fname) get_ipython().system('convert $fname -trim $fname') # ## Definitions # * The rule curves consist of upper and lower bounds on the levels of Namakan and Rainy Lakes during normal conditions. The 1970 Supplementary Order requires the dam operator, insofar as possible, keep the lake levels between these bounds. The 2000 Supplementary Order further requires the dam operators to target the middle of the rule curve bands. # * The 1970 Supplementary Order defines emergency conditions for Namakan and Rainy Lakes. A high water emergency conditions exists if a lake level exceeds a specified elevation when inflows are greater than the dam outflow capacity. The low water emergency condition occurs if the lake level falls below a threshold elevation when the lake outflow is below a specified minimum. # * The 2000 Supplementary Order further specifies 'drought lines' for both lakes. Dam operators may further restrict outflow when lake levels fall below the drought line. # * The 1970 Supplementary Order specified a threshold high water level for each lake such that, if the lake level exceeds the threshold, the dam operators must open all gates and fishways. # ## 1949 Rule Curves for Rainy and Namakan Lakes # In[35]: # Generic Year to represent data yr = 2014 # Rainy Lake RL1949 = { datetime.datetime(yr, 1, 1): [1104.61, 1107.11, 1107.11, 1108.11, 1108.61], datetime.datetime(yr, 2, 1): [1104.61, 1106.61, 1106.61, 1108.11, 1108.61], datetime.datetime(yr, 3, 1): [1104.61, 1105.61, 1105.61, 1108.11, 1108.61], datetime.datetime(yr, 4, 1): [1104.61, 1104.61, 1104.61, 1108.11, 1108.61], datetime.datetime(yr, 5, 1): [1104.61, 1106.61, 1106.61, 1108.11, 1108.61], datetime.datetime(yr, 6, 1): [1104.61, 1107.61, 1107.61, 1108.11, 1108.61], datetime.datetime(yr, 7, 1): [1104.61, 1108.11, 1108.11, 1108.11, 1108.61], datetime.datetime(yr, 8, 1): [1104.61, 1108.11, 1108.11, 1108.11, 1108.61], datetime.datetime(yr, 9, 1): [1104.61, 1108.11, 1108.11, 1108.11, 1108.61], datetime.datetime(yr,10, 1): [1104.61, 1108.11, 1108.11, 1108.11, 1108.61], datetime.datetime(yr,11, 1): [1104.61, 1108.11, 1108.11, 1108.11, 1108.61], datetime.datetime(yr,12, 1): [1104.61, 1107.61, 1107.61, 1108.11, 1108.61], datetime.datetime(yr,12,31): [1104.61, 1107.11, 1107.11, 1108.11, 1108.61] } RL1949 = pd.DataFrame.from_dict(RL1949).transpose()/3.2808 RL1949.columns = ['ELW','LRC','URC','EHW','AGO'] RL1949 = RL1949.resample(rule='1D').interpolate() # Namakan Lake NL1949 = { datetime.datetime(yr, 1, 1): [1108.61, 1113.61, 1113.61, 1118.61, 1119.1], datetime.datetime(yr, 2, 1): [1108.61, 1111.91, 1111.91, 1118.61, 1119.1], datetime.datetime(yr, 3, 1): [1108.61, 1110.31, 1110.31, 1118.61, 1119.1], datetime.datetime(yr, 4, 1): [1108.61, 1108.61, 1108.61, 1118.61, 1119.1], datetime.datetime(yr, 5, 1): [1108.61, 1111.91, 1111.91, 1118.61, 1119.1], datetime.datetime(yr, 6, 1): [1108.61, 1115.31, 1115.31, 1118.61, 1119.1], datetime.datetime(yr, 7, 1): [1108.61, 1118.61, 1118.61, 1118.61, 1119.1], datetime.datetime(yr, 8, 1): [1108.61, 1118.61, 1118.61, 1118.61, 1119.1], datetime.datetime(yr, 9, 1): [1108.61, 1118.61, 1118.61, 1118.61, 1119.1], datetime.datetime(yr,10, 1): [1108.61, 1118.61, 1118.61, 1118.61, 1119.1], datetime.datetime(yr,11, 1): [1108.61, 1116.91, 1116.91, 1118.61, 1119.1], datetime.datetime(yr,12, 1): [1108.61, 1115.31, 1115.31, 1118.61, 1119.1], datetime.datetime(yr,12,31): [1108.61, 1113.61, 1113.61, 1118.61, 1119.1] } NL1949 = pd.DataFrame.from_dict(NL1949).transpose()/3.2808 NL1949.columns = ['ELW','LRC','URC','EHW','AGO'] NL1949 = NL1949.resample(rule='1D').interpolate() # In[44]: plt.figure(figsize=(10,6)) plotRC(NL1949,'b') plotRC(RL1949,'r') twiny() plt.title('Rule Curves for Namakan and Rainy Lakes: 1949-1957') saveRC('RuleCurve1949.png') # ## 1957 Rule Curves for Rainy and Namakan Lakes # # The adjustments that created the 1957 rule curve orders were based on the following conclusions: # # 1. The 1949 rule curve order was basically satisfactory, despite the flooding experienced in 1950 and 1954. # 2. The terms of the 1949 order were essentially met except for the flood years. # 3. No change in the curve order for Rainy Lake. # 4. Add more flexibility and a maximum level for Namakan for the period from October 1 to June 1. # 5. No changes recommended to the controls provided the operators make prompt changes in outflow. # In[43]: # Generic Year to represent data yr = 2014 # Rainy Lake RL1957 = RL1949.copy() # Namakan Lake NL1957 = { datetime.datetime(yr, 1, 1): [1108.61, 1113.61, 1115.27, 1118.61, 1119.1], datetime.datetime(yr, 2, 1): [1108.61, 1111.91, 1114.14, 1118.61, 1119.1], datetime.datetime(yr, 3, 1): [1108.61, 1110.31, 1113.13, 1118.61, 1119.1], datetime.datetime(yr, 4, 1): [1108.61, 1108.61, 1112.00, 1118.61, 1119.1], datetime.datetime(yr, 4,21): [1108.61, 1110.91, 1112.00, 1118.61, 1119.1], datetime.datetime(yr, 5, 1): [1108.61, 1111.91, 1113.61, 1118.61, 1119.1], datetime.datetime(yr, 6, 1): [1108.61, 1115.31, 1118.61, 1118.61, 1119.1], datetime.datetime(yr, 7, 1): [1108.61, 1118.61, 1118.61, 1118.61, 1119.1], datetime.datetime(yr, 8, 1): [1108.61, 1118.61, 1118.61, 1118.61, 1119.1], datetime.datetime(yr, 9, 1): [1108.61, 1118.61, 1118.61, 1118.61, 1119.1], datetime.datetime(yr,10, 1): [1108.61, 1118.61, 1118.61, 1118.61, 1119.1], datetime.datetime(yr,11, 1): [1108.61, 1116.91, 1117.48, 1118.61, 1119.1], datetime.datetime(yr,12, 1): [1108.61, 1115.31, 1116.39, 1118.61, 1119.1], datetime.datetime(yr,12,31): [1108.61, 1113.61, 1116.39, 1118.61, 1119.1] } NL1957 = pd.DataFrame.from_dict(NL1957).transpose()/3.2808 NL1957.columns = ['ELW','LRC','URC','EHW','AGO'] NL1957 = NL1957.resample(rule='1D').interpolate() plt.figure(figsize=(10,6)) plotRC(NL1957,'b') plotRC(RL1957,'r') twiny() plt.title('Rule Curves for Namakan and Rainy Lakes: 1957-1970') saveRC('RuleCurve1957.png') # # ## 1970 Rule Curves for Rainy and Namakan Lakes # Text of the [1970 Rule Curve](http://www.ijc.org/files/tinymce/uploaded/1970-07-29_IJC_Order.pdf) rule curve order # In[5]: # Generic Year to represent data yr = 2014 # Rainy Lake RL1970 = { datetime.datetime(yr, 1, 1): [1104.6, 1106.6, 1107.1, 1108.1, 1108.6], datetime.datetime(yr, 2, 1): [1104.6, 1105.8, 1106.6, 1108.1, 1108.6], datetime.datetime(yr, 3, 1): [1104.6, 1105.2, 1106.2, 1108.1, 1108.6], datetime.datetime(yr, 4, 1): [1104.6, 1104.6, 1105.6, 1108.1, 1108.6], datetime.datetime(yr, 4,21): [1104.6, 1104.6, 1106.2, 1108.1, 1108.6], datetime.datetime(yr, 5, 1): [1104.6, 1105.1, 1106.6, 1108.1, 1108.6], datetime.datetime(yr, 6, 1): [1104.6, 1106.6, 1107.6, 1108.1, 1108.6], datetime.datetime(yr, 7, 1): [1104.6, 1107.4, 1108.1, 1108.1, 1108.6], datetime.datetime(yr, 8, 1): [1104.6, 1107.4, 1108.1, 1108.1, 1108.6], datetime.datetime(yr, 9, 1): [1104.6, 1107.4, 1108.1, 1108.1, 1108.6], datetime.datetime(yr,10, 1): [1104.6, 1107.4, 1108.1, 1108.1, 1108.6], datetime.datetime(yr,10,11): [1104.6, 1107.4, 1108.1, 1108.1, 1108.6], datetime.datetime(yr,11, 1): [1104.6, 1107.2, 1108.1, 1108.1, 1108.6], datetime.datetime(yr,12, 1): [1104.6, 1106.8, 1107.6, 1108.1, 1108.6], datetime.datetime(yr,12,31): [1104.6, 1106.4, 1107.1, 1108.1, 1108.6] } RL1970 = pd.DataFrame.from_dict(RL1970).transpose()/3.2808 RL1970.columns = ['ELW','LRC','URC','EHW','AGO'] # print rule curve in format portable to Simulink 1D Table Lookup # print("Breakpoints: ", RL1970.index.dayofyear, "\n") # print("LRC: [" + ", ".join(["{0:.2f}".format(d) for d in RL1970['LRC']]) + "]\n") # print("URC: [" + ", ".join(["{0:.2f}".format(d) for d in RL1970['URC']]) + "]\n") RL1970 = RL1970.resample(rule='1D').interpolate() # Namakan Lake NL1970 = { datetime.datetime(yr, 1, 1): [1108.6, 1113.6, 1115.3, 1118.6, 1119.1], datetime.datetime(yr, 2, 1): [1108.6, 1111.9, 1114.1, 1118.6, 1119.1], datetime.datetime(yr, 3, 1): [1108.6, 1110.3, 1113.1, 1118.6, 1119.1], datetime.datetime(yr, 4, 1): [1108.6, 1108.6, 1112.0, 1118.6, 1119.1], datetime.datetime(yr, 4,21): [1108.6, 1108.6, 1113.1, 1118.6, 1119.1], datetime.datetime(yr, 5, 1): [1108.6, 1110.2, 1113.6, 1118.6, 1119.1], datetime.datetime(yr, 6, 1): [1108.6, 1115.3, 1116.6, 1118.6, 1119.1], datetime.datetime(yr, 6,21): [1108.6, 1117.5, 1118.6, 1118.6, 1119.1], datetime.datetime(yr, 7, 1): [1108.6, 1117.6, 1118.6, 1118.6, 1119.1], datetime.datetime(yr, 7,21): [1108.6, 1118.0, 1118.6, 1118.6, 1119.1], datetime.datetime(yr, 8, 1): [1108.6, 1118.0, 1118.6, 1118.6, 1119.1], datetime.datetime(yr, 9, 1): [1108.6, 1118.0, 1118.6, 1118.6, 1119.1], datetime.datetime(yr, 9,11): [1108.6, 1118.0, 1118.6, 1118.6, 1119.1], datetime.datetime(yr,10, 1): [1108.6, 1117.6, 1118.6, 1118.6, 1119.1], datetime.datetime(yr,11, 1): [1108.6, 1116.3, 1117.5, 1118.6, 1119.1], datetime.datetime(yr,12, 1): [1108.6, 1115.0, 1116.5, 1118.6, 1119.1], datetime.datetime(yr,12,31): [1108.6, 1113.6, 1115.3, 1118.6, 1119.1] } NL1970 = pd.DataFrame.from_dict(NL1970).transpose()/3.2808 NL1970.columns = ['ELW','LRC','URC','EHW','AGO'] NL1970 = NL1970.resample(rule='1D').interpolate() # In[42]: plt.figure(figsize=(10,6)) plotRC(NL1970,'b') plotRC(RL1970,'r') twiny() plt.title('Rule Curves for Namakan and Rainy Lakes: 1970-2000') saveRC('RuleCurve1970.png') # ## 2000 Rule Curves for Rainy and Namakan Lakes # The [2001 Consolidated Order](http://www.ijc.org/files/tinymce/uploaded/2001-01-18_IJC_Order.pdf) of the International Joint Commission establishes the rule curves for Namakan and Rainy Lakes, and operating constraints for the dams at Kettle Falls and International Falls. The consolidated order incorporates relevant language from the original [Order of 1949](http://www.ijc.org/files/tinymce/uploaded/1949-06-08_IJC_Order.pdf) and Supplemental Orders of [1957](http://www.ijc.org/files/tinymce/uploaded/1957-10-01_IJC_Order.pdf), [1970](http://www.ijc.org/files/tinymce/uploaded/1970-07-29_IJC_Order.pdf), and [2000](http://www.ijc.org/files/tinymce/uploaded/2000-01-05_IJC_Order.pdf), into a single document describing current regulations on the management of levels for Rainy Lake and the Namakan chain of lakes. # # ### Namakan Lake Rule Curve 2000 # In[21]: # Namakan Lake ELW = { datetime.datetime(yr, 1, 1): 337.90, datetime.datetime(yr,12,31): 337.90 } EDL = { datetime.datetime(yr, 1, 1): 338.95, datetime.datetime(yr, 6,30): 338.95, datetime.datetime(yr, 7, 1): 340.15, datetime.datetime(yr, 9,30): 340.15, datetime.datetime(yr,12,31): 338.95 } LRC = { datetime.datetime(yr, 1, 1): 339.70, datetime.datetime(yr, 4, 1): 338.95, datetime.datetime(yr, 4,15): 338.95, datetime.datetime(yr, 6, 7): 340.70, datetime.datetime(yr, 9, 1): 340.45, datetime.datetime(yr,10, 1): 340.45, datetime.datetime(yr,12,31): 339.70 } URC = { datetime.datetime(yr, 1, 1): 340.00, datetime.datetime(yr, 4, 1): 339.70, datetime.datetime(yr, 6, 1): 340.95, datetime.datetime(yr, 9, 1): 340.65, datetime.datetime(yr,10, 1): 340.65, datetime.datetime(yr,12,31): 340.00 } EHW = { datetime.datetime(yr, 1, 1): 340.95, datetime.datetime(yr,12,31): 340.95 } AGO = { datetime.datetime(yr, 1, 1): 341.10, datetime.datetime(yr,12,31): 341.10 } NL2000 = pd.concat([pd.Series(ELW),pd.Series(EDL),pd.Series(LRC), pd.Series(URC),pd.Series(EHW),pd.Series(AGO)], axis=1) NL2000.columns = ['ELW','EDL','LRC','URC','EHW','AGO'] NL2000 = NL2000.resample(rule='1D').interpolate() # ### Rainy Lake Rule Curve 2000 # In[22]: # Rainy Lake ELW = { datetime.datetime(yr, 1, 1): 336.68, datetime.datetime(yr,12,31): 336.68 } LRC = { datetime.datetime(yr, 1, 1): 337.20, datetime.datetime(yr, 4, 1): 336.70, datetime.datetime(yr, 5, 1): 336.80, datetime.datetime(yr, 6, 1): 337.30, datetime.datetime(yr, 7, 1): 337.50, datetime.datetime(yr, 8,15): 337.50, datetime.datetime(yr,12, 1): 337.30, datetime.datetime(yr,12,31): 337.20 } URC = { datetime.datetime(yr, 1, 1): 337.45, datetime.datetime(yr, 4, 1): 337.00, datetime.datetime(yr, 5, 1): 337.40, datetime.datetime(yr, 6, 1): 337.60, datetime.datetime(yr, 7, 1): 337.75, datetime.datetime(yr, 8,15): 337.75, datetime.datetime(yr,12, 1): 337.60, datetime.datetime(yr,12,31): 337.45 } EDL = { datetime.datetime(yr, 1, 1): 336.90, datetime.datetime(yr, 4, 1): 336.70, datetime.datetime(yr, 6,30): 336.70, datetime.datetime(yr, 7, 1): 337.20, datetime.datetime(yr,10,24): 337.20, datetime.datetime(yr,12,31): 336.90 } EHW = { datetime.datetime(yr, 1, 1): 337.75, datetime.datetime(yr,12,31): 337.75 } AGO = { datetime.datetime(yr, 1, 1): 337.90, datetime.datetime(yr,12,31): 337.90 } RL2000 = pd.concat([pd.Series(ELW),pd.Series(LRC),pd.Series(URC), pd.Series(EDL),pd.Series(EHW),pd.Series(AGO)], axis=1) RL2000.columns = ['ELW','LRC','URC','EDL','EHW','AGO'] RL2000 = RL2000.resample(rule='1D').interpolate() # In[41]: plt.figure(figsize=(10,6)) plotRC(RL2000,'r') plotRC(NL2000,'b') twiny() plt.title('Rule Curves for Namakan and Rainy Lakes: 2000 - present') saveRC('RuleCurve2000.png') # ## Graphical Comparison of 1970 and 2000 Rule Curves # In[47]: plt.figure(figsize=(10,6)) plotRC(NL1970,'y') plotRC(RL1970,'g') plotRC(NL2000,'b') plotRC(RL2000,'r') plt.title('Comparison of 1970 and 2000 Rule Curves') import matplotlib.patches as mpatches gold_patch = mpatches.Patch(color='y', alpha = 0.5, label='Namakan 1970') blue_patch = mpatches.Patch(color='b', alpha = 0.5, label='Namakan 2000') green_patch = mpatches.Patch(color='g', alpha = 0.5, label='Rainy 1970') red_patch = mpatches.Patch(color='r', alpha = 0.5, label='Rainy 2000') nlegend = plt.legend(handles=[gold_patch,blue_patch],bbox_to_anchor=[1.0,0.55]) plt.gca().add_artist(nlegend) plt.legend(handles=[green_patch,red_patch],loc=4) twiny() saveRC('RuleCurveComparison.png') # ## Pickle Rule Curves to Data Files # In[11]: NL1970.to_pickle(dir+'NL1970.pkl') RL1970.to_pickle(dir+'RL1970.pkl') NL2000.to_pickle(dir+'NL2000.pkl') RL2000.to_pickle(dir+'RL2000.pkl') # In[12]: RL2000.head()