Scripting Basics

Scripts in HEC-ResSim are written in Jython, which is an implementation of Python within Java. Like other programming languages, Jython uses Variables, Classes, Objects, Functions, and Methods. Typically, a ResSim user is concerned with creating variables, setting the value of those variables, and then changing the value of those variables within some kind of computation. Often, the user wants the value of a variable to be based on another variable the program already knows, like pool elevation, inflow a reservoir, or flow at a junction. Variables that ResSim “knows” the value of are split into three categories – Model Variables, State Variables, and External Timeseries.

  • Model Variables are innate variables that correspond to elements in the network. They include things like reservoir inflows and outflows, pool elevations, and flows at junctions and reaches.
  • State Variables are user-defined variables that can be used by the model to define zones and operating rules. They do not correspond to any elements you can see in the model schematic.
  • External Timeseries are timeseries stored in an external DSS file that are brought into the model through their use in an operating rule.

Each variable described above can be used within a user-defined state-variable script or a scripted rule. The variable itself is a Timeseries Object. To access a Model Variable, State Variable, or External Timeseries as a timeseries object in your script, you can either manually type out the necessary Jython syntax, or you can navigate to your variable of interest in the API tree. Once you’ve found the variable you want, a double-click will paste the Jython syntax necessary to call that object into the script editor. For example, say you want your scripted rule to utilize the pool elevation of a reservoir. The reservoir’s pool elevation is stored in a Model Variable. You can access that model variable by finding it in the TimeSeries branch of the API Tree and double-clicking on it. This will paste the syntax for calling that model variable into the script editor. This can also be done for the State Variables and External Timeseries that exist within your model.

Once you have the timeseries object, you need to call a Method to extract a value from it.  Methods that can be applied to timeseries objects are listed under the TimeSeries API in the API Tree. Just like Model Variables, State Variables, and External Timeseries, double-clicking on a method in the API Tree will paste the appropriate syntax into the script editor. The example below shows a scripted rule that uses a model variable – pool elevation of a reservoir called “Lake Darling” – to compute flow over a spillway using the weir equation. Note this script assumes the spillway is never affected by tailwater submergence.

Scripted Rule Example

Description: This example of a scripted rule used in a ResSim operations set determines a release from a reservoir based on the percentage of occupied flood storage. If the percent of occupied flood storage is greater than 50%, releases for this rule are set to 2000 cfs. If the percent of occupied flood storage is between 20% and 50%, releases for this rule are set to 1000 cfs. If the percent of occupied flood storage is less than 20%, releases for this rule are set to 0 cfs. After releases are determined, it is specified as a minimum release rule using "opValue.init(OpRule.RULETYPE_MIN, Release)". Another option that is presented in the scripted rule is to write the calculated release value to a state variable using "network.getStateVariable("ReleaseTest").setValue(currentRuntimestep, Release)", which can be used at other reservoirs besides the reservoir containing the scripted rule within its operations set.

Release Scripted Rule

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'			: debug,
					'lineNo'		: lineNo,
					'outputDebug'	: outputDebug,
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	lineNo			= InitInfo['lineNo']
	outputDebug		= InitInfo['outputDebug']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	PreviousValue = network.getTimeSeries("Reservoir","Fort Peck Lake", "Pool", "Stor").getPreviousValue(currentRuntimestep)
	BottomFloodControlZoneStor = network.getTimeSeries("Reservoir","Fort Peck Lake", "Carryover Multiple Use", "Stor-ZONE").getPreviousValue(currentRuntimestep)
	ExclusiveFloodControlZoneStor = network.getTimeSeries("Reservoir","Fort Peck Lake", "Exclusive Flood Control", "Stor-ZONE").getPreviousValue(currentRuntimestep)

	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), '\n\t\t\tPreviousValue = ', PreviousValue,
			'\n\t\t\tBottomFloodControlZoneStor = ', BottomFloodControlZoneStor, ' acre-ft',
			'\n\t\t\t%s' % 'BottomFloodControlZoneStor = %.0f acre-ft' % BottomFloodControlZoneStor)
	
	PercentFull = ((PreviousValue - BottomFloodControlZoneStor) / (ExclusiveFloodControlZoneStor - BottomFloodControlZoneStor)) * 100.
	outputDebug(debug, lineNo(), 'PercentFull = %.2f percent' % PercentFull)
	
	if PercentFull > 50. :
		Release = 2000.
	elif PercentFull > 20. :
		Release = 1000.
	else : Release = 0.
	
	network.getStateVariable("ReleaseTest").setValue(currentRuntimestep, Release)
	TestRelease = network.getStateVariable("ReleaseTest").getValue(currentRuntimestep)
	outputDebug(True, lineNo(), 'Test Release = %.0f cfs' % TestRelease)

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, Release)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue

#==============================================================================

Console Output:
Debug Line 97   |	01Mar1930, 24:00
					PreviousValue = 17253500.0
					BottomFloodControlZoneStor = 14788340.0 acre-ft
					BottomFloodControlZoneStor = 14788340 acre-ft
Debug Line 102   |	PercentFull = 67.09 percent
Debug Line 112   |	Test Release = 2000 cfs
  
PY

External Plot Example

Description: This example of a custom plot displays discharges at the 4 control points of the reservoir system separated into 4 viewports. Minimum flows at each location are shown as the hatched time series. Releases from the downstream reservoir in the System must keep flows (purple lines) above the minimum flows required for navigation. Also displayed on the plot are the local flows at each location (light blue) and the observed total flows at each location (red). A curveProperties dictionary is used to specify all of the properties for each curve added to the plot. This allows a user to quickly change various curve properties. This script is not stored within the watershed directory. Instead, external scripts are stored in the users .../AppData/Roaming/... directory. 

Control Points

'''
Author: Mike Perryman/Ryan Larsen
Date: 08-25-2021
Description: Logic for plotting GAPT releases and the downstream navigation, water supply, and flood targets
'''
# -------------------------------------------------------------------
# Required imports to create the OpValue return object.
# -------------------------------------------------------------------
from hec.heclib.util 		import HecTime
from hec.io 				import TimeSeriesContainer
from hec.script 			import ClientAppWrapper, ResSim, HecDss, Plot, Constants
from hec.script.Constants	import TRUE, FALSE
import javax

#
# load the ResSimUtils module
#
wsDir = ResSim.getWatershed().getWorkspacePath()
scriptDir = os.path.normpath(os.path.join(wsDir, "shared"))
if scriptDir not in sys.path : sys.path.append(scriptDir)
import ResSimUtils

from ResSimUtils import outputDebug, lineNo, getSimulationDSSFileName, getResSimTimewindow, getFPart, getSimulation, getSelectedAlternativeNames

def verifyState() :
	'''
	Verify that ResSim is in the correct module and has simulation open
	'''
	global simulation
	module = ClientAppWrapper.getCurrentModule()
	if module.getName() != "Simulation" : 
		raise AssertionError, "ResSim is %s module, Simulation module is required" % `module`
	simulation = module.getSimulation()
	if not simulation :
		raise AssertionError, "Must have a simulation open."

def getPathnames(filename, Fpart, startTime, endTime) :
	DssFile = HecDss.open(filename, startTime, endTime)
	DssFile.setTrimMissing(False)
	print "DssFile = ", DssFile
	print

	# Service Levels
	SUX_FSL = DssFile.get("//SL_SUX/FLOW-SERVICE//1DAY/" + Fpart + "/")
	OMA_FSL = DssFile.get("//SL_OMA/FLOW-SERVICE//1DAY/" + Fpart + "/")
	NCNE_FSL = DssFile.get("//SL_NCNE/FLOW-SERVICE//1DAY/" + Fpart + "/")
	MKC_FSL = DssFile.get("//SL_MKC/FLOW-SERVICE//1DAY/" + Fpart + "/")
	SUX_FSL_Final = DssFile.get("//NAV_TARGET_SUX/FLOW-SERVICE//1DAY/" + Fpart + "/")
	OMA_FSL_Final = DssFile.get("//NAV_TARGET_OMA/FLOW-SERVICE//1DAY/" + Fpart + "/")
	NCNE_FSL_Final = DssFile.get("//NAV_TARGET_NCNE/FLOW-SERVICE//1DAY/" + Fpart + "/")
	MKC_FSL_Final = DssFile.get("//NAV_TARGET_MKC/FLOW-SERVICE//1DAY/" + Fpart + "/")
	

	# Flows
	SUX_FlowReg = DssFile.get("//SUX/FLOW//1DAY/" + Fpart + "/")
	OMA_FlowReg = DssFile.get("//OMA/FLOW//1DAY/" + Fpart + "/")
	NCNE_FlowReg = DssFile.get("//NCNE/FLOW//1DAY/" + Fpart + "/")
	MKC_FlowReg = DssFile.get("//MKC/FLOW//1DAY/" + Fpart + "/")
	
	
	# Local Inflows
	SUX_LocIn = DssFile.get("//SUX/FLOW-LOCAL//1DAY/" + Fpart + "/")
	OMA_LocIn = DssFile.get("//OMA/FLOW-LOCAL//1DAY/" + Fpart + "/")
	NCNE_LocIn = DssFile.get("//NCNE/FLOW-LOCAL//1DAY/" + Fpart + "/")
	MKC_LocIn = DssFile.get("//MKC/FLOW-LOCAL//1DAY/" + Fpart + "/")

	#Observed Flows
	SUX_FlowOBS = DssFile.get('/MISSOURI RIVER/SUX/FLOW//1DAY/RAW-USGS/')
	OMA_FlowOBS = DssFile.get('/MISSOURI RIVER/OMA/FLOW//1DAY/RAW-USGS/')
	NCNE_FlowOBS = DssFile.get('/MISSOURI RIVER/NCNE/FLOW//1DAY/RAW-USGS/')
	MKC_FlowOBS = DssFile.get('/MISSOURI RIVER/MKC/FLOW//1DAY/RAW-USGS/')

	FSLs = [SUX_FSL, OMA_FSL, NCNE_FSL, MKC_FSL, SUX_FSL_Final, OMA_FSL_Final, NCNE_FSL_Final, MKC_FSL_Final]
	
	FlowREGs = [SUX_FlowReg, OMA_FlowReg, NCNE_FlowReg, MKC_FlowReg]

	FlowLOCs = [SUX_LocIn, OMA_LocIn, NCNE_LocIn, MKC_LocIn]

	FlowOBS = [SUX_FlowOBS, OMA_FlowOBS, NCNE_FlowOBS, MKC_FlowOBS]
	
	return FSLs , FlowREGs, FlowLOCs, FlowOBS


def plotControlPoints(FSLs , FlowREGs, FlowLOCs, FlowOBS) :

	thePlot = Plot.newPlot("Plot_ControlPoints")
	layout = Plot.newPlotLayout()

	suxView = layout.addViewport(0.25)
	omaView = layout.addViewport(0.25)
	ncneView = layout.addViewport(0.25)
	mkcView = layout.addViewport(0.25)

	suxViewCurves = []
	suxViewCurves.append(FSLs[4])
	suxViewCurves.append(FSLs[0])
	suxViewCurves.append(FlowREGs[0])
	suxViewCurves.append(FlowLOCs[0])
	suxViewCurves.append(FlowOBS[0])

	omaViewCurves = []
	omaViewCurves.append(FSLs[5])
	omaViewCurves.append(FSLs[1])
	omaViewCurves.append(FlowREGs[1])
	omaViewCurves.append(FlowLOCs[1])
	omaViewCurves.append(FlowOBS[1])

	ncneViewCurves = []
	ncneViewCurves.append(FSLs[6])
	ncneViewCurves.append(FSLs[2])
	ncneViewCurves.append(FlowREGs[2])
	ncneViewCurves.append(FlowLOCs[2])
	ncneViewCurves.append(FlowOBS[2])

	mkcViewCurves = []
	mkcViewCurves.append(FSLs[7])
	mkcViewCurves.append(FSLs[3])
	mkcViewCurves.append(FlowREGs[3])
	mkcViewCurves.append(FlowLOCs[3])
	mkcViewCurves.append(FlowOBS[3])

	for x in range(len(suxViewCurves)) :
		suxView.addCurve("Y1", suxViewCurves[x])
	for x in range(len(omaViewCurves)) :
		omaView.addCurve("Y1", omaViewCurves[x])
	for x in range(len(ncneViewCurves)) :
		ncneView.addCurve("Y1", ncneViewCurves[x])
	for x in range(len(mkcViewCurves)) :
		mkcView.addCurve("Y1", mkcViewCurves[x])

	suxView.setAxisName("Y1" , "SUX")
	omaView.setAxisName("Y1" , "OMA")
	ncneView.setAxisName("Y1" , "NCNE")
	mkcView.setAxisName("Y1" , "MKC")

	thePlot.configurePlotLayout(layout)

	thePlot.setPlotTitleVisible(Constants.TRUE)
	thePlot.setPlotTitleText("Control Points (Service Levels and Flows)")
	#thePlot.getPlotTitle().setFont("Arial Black")
	thePlot.getPlotTitle().setFontSize(18)

	thePlot.showPlot()

	#thePlot.setPlotTitleVisible(Constants.TRUE)
	#thePlot.setPlotTitleText("Control Points Service Levels and Flows")
	#thePlot.getPlotTitle().setFontSize(12)

	suxV = thePlot.getViewport(0)
	suxViewY1Axis = suxV.getAxis("Y1")
	#suxViewY1Axis.setScaleLimits(0, 2.0e5)
	#suxViewY1Axis.setViewLimits(0, 2.0e5)
	suxViewY1Axis.setLabel("Sioux City \n Flow (cfs)")

	omaV = thePlot.getViewport(1)
	omaViewY1Axis = omaV.getAxis("Y1")
	#omaViewY1Axis.setScaleLimits(0, 2.0e5)
	#omaViewY1Axis.setViewLimits(0, 2.0e5)
	#omaViewY1Axis.setMajorTicInterval(1.0e4)
	#omaViewY1Axis.setMinorTicInterval(5.0e3)
	omaViewY1Axis.setLabel("Omaha City \n Flow (cfs)")

	ncneV = thePlot.getViewport(2)
	ncneViewY1Axis = ncneV.getAxis("Y1")
	#ncneViewY1Axis.setScaleLimits(0, 2.0e5)
	#ncneViewY1Axis.setViewLimits(0, 2.0e5)
	ncneViewY1Axis.setLabel("Nebraska City \n Flow (cfs)")

	mkcV = thePlot.getViewport(3)
	mkcViewY1Axis = mkcV.getAxis("Y1")
	#mkcViewY1Axis.setScaleLimits(0, 2.0e5)
	#mkcViewY1Axis.setViewLimits(0, 2.0e5)
	mkcViewY1Axis.setLabel("Kansas City \n Flow (cfs)")


	curveProperties = {
																							  																																																														
							#	key						Curve				LineColor			LineStyle		LineWeight	SymbolVisible		SymbolType				SymbolSize		SymbolLineColor		SymbolFillColor		SymbolInterval		SymbolSkipCount		FirstSymbolOffset	Fill Type	Fill Color			Fill Pattern			
							#	----------------		----------------	----------------	------------	----------	------------------	--------------------	------------	----------------	----------------	----------------	----------------	-----------------	----------	----------------	------------------		
								'SUX_FSL'				: [ FSLs[0]			, 'gray'			, 'Dash'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"	, "gray"			, "Diagonal Cross"	] ,
								'OMA_FSL'				: [ FSLs[1]			, 'gray'			, 'Dash'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"	, "gray"			, "Diagonal Cross"	] ,
								'NCNE_FSL'				: [ FSLs[2]			, 'gray'			, 'Dash'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"	, "gray"			, "Diagonal Cross"	] ,
								'MKC_FSL'				: [ FSLs[3]			, 'gray'			, 'Dash'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"	, "gray"			, "Diagonal Cross"	] ,
																							  																																																														
								'SUX_FSL_Final'			: [ FSLs[4]			, 'gray'			, 'Solid'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"Below"		, "gray"				, "Diagonal Cross"	] ,
								'OMA_FSL_Final'			: [ FSLs[5]			, 'gray'			, 'Solid'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"Below"		, "gray"				, "Diagonal Cross"	] ,
								'NCNE_FSL_Final'		: [ FSLs[6]			, 'gray'			, 'Solid'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"Below"		, "gray"				, "Diagonal Cross"	] ,
								'MKC_FSL_Final'			: [ FSLs[7]			, 'gray'			, 'Solid'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"Below"		, "gray"				, "Diagonal Cross"	] ,

								'SUX_FlowREG'			: [ FlowREGs[0]		, 'darkblue'		, 'Solid'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"		, "darkblue"		, "Solid"			] ,
								'OMA_FlowREG'			: [ FlowREGs[1]		, 'darkblue'		, 'Solid'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"		, "darkblue"		, "Solid"			] ,
								'NCNE_FlowREG'			: [ FlowREGs[2]		, 'darkblue'		, 'Solid'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"		, "darkblue"		, "Solid"			] ,
								'MKC_FlowREG'			: [ FlowREGs[3]		, 'darkblue'		, 'Solid'	, 	2			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"		, "darkblue"		, "Solid"			] ,
																							  																																																														
								'SUX_FlowLocIn'			: [ FlowLOCs[0]		, 'lightblue'		, 'Solid'	, 	1			, Constants.TRUE	, 'Triangle'			, 6				, 'lightblue'		, 'lightblue'		, 0					, 30				, 0					,"None"		, "lightblue"		, "Solid"			] ,
								'OMA_FlowLocIn'			: [ FlowLOCs[1]		, 'lightblue'		, 'Solid'	, 	1			, Constants.TRUE	, 'Triangle'			, 6				, 'lightblue'		, 'lightblue'		, 0					, 30				, 0					,"None"		, "lightblue"		, "Solid"			] ,
								'NCNE_FlowLocIn'		: [ FlowLOCs[2]		, 'lightblue'		, 'Solid'	, 	1			, Constants.TRUE	, 'Triangle'			, 6				, 'lightblue'		, 'lightblue'		, 0					, 30				, 0					,"None"		, "lightblue"		, "Solid"			] ,
								'MKC_FlowLocIn'			: [ FlowLOCs[3]		, 'lightblue'		, 'Solid'	, 	1			, Constants.TRUE	, 'Triangle'			, 6				, 'lightblue'		, 'lightblue'		, 0					, 30				, 0					,"None"		, "lightblue"		, "Solid"			] ,
																							  																																																														
								'SUX_OBS'				: [ FlowOBS[0]		, 'red'				, 'Solid'	, 	1			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"		, "darkgreen"		, "Solid"			] ,
								'OMA_OBS'				: [ FlowOBS[1]		, 'red'				, 'Solid'	, 	1			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"		, "darkgreen"		, "Solid"			] ,
								'NCNE_OBS'				: [ FlowOBS[2]		, 'red'				, 'Solid'	, 	1			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"		, "darkgreen"		, "Solid"			] ,
								'MKC_OBS'				: [ FlowOBS[3]		, 'red'				, 'Solid'	, 	1			, Constants.FALSE	, 'Triangle'			, 6				, 'blue'			, 'blue'			, 0					, 0					, 0					,"None"		, "darkgreen"		, "Solid"			] 
																							  																																																														
					}

	propKeys = curveProperties.keys()
	for key in propKeys :
		curve = thePlot.getCurve(curveProperties[key][0])
		LineColor = curveProperties[key][1]
		LineStyle = curveProperties[key][2]
		LineWeight = curveProperties[key][3]
		SymbolsVisible = curveProperties[key][4]
		SymbolType = curveProperties[key][5]
		SymbolSize = curveProperties[key][6]
		SymbolLineColor = curveProperties[key][7]
		SymbolFillColor = curveProperties[key][8]
		SymbolInterval = curveProperties[key][9]
		SymbolSkipCount = curveProperties[key][10]
		FirstSymbolOffset = curveProperties[key][11]
		FillType = curveProperties[key][12]
		FillColor = curveProperties[key][13]
		FillPattern = curveProperties[key][14]
		curve.setLineColor(LineColor)
		curve.setLineStyle(LineStyle)
		curve.setLineWidth(LineWeight)
		curve.setSymbolsVisible(SymbolsVisible)
		curve.setSymbolType(SymbolType)
		curve.setSymbolSize(SymbolSize)
		curve.setSymbolLineColor(SymbolLineColor)
		curve.setSymbolFillColor(SymbolFillColor)
		curve.setSymbolInterval(SymbolInterval)
		curve.setSymbolSkipCount(SymbolSkipCount)
		curve.setFirstSymbolOffset(FirstSymbolOffset)
		curve.setFillType(FillType)
		curve.setFillColor(FillColor)
		curve.setFillPattern(FillPattern)

def main():
	# Run script using ResSim
	verifyState()
	simulation = getSimulation()
	lookbackTime, startTime, endTime = getResSimTimewindow(simulation)
	Fpart = getFPart(getSelectedAlternativeNames()[0])
	filename = getSimulationDSSFileName()

	FSLs , FlowREGs, FlowLOCs, FlowOBS = getPathnames(filename, Fpart, startTime, endTime)
	
	plotControlPoints(FSLs , FlowREGs, FlowLOCs, FlowOBS)
	
main()
PY

External OSI Tool Example

Description: This example of a 

Release Scripted Rule

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'			: debug,
					'lineNo'		: lineNo,
					'outputDebug'	: outputDebug,
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	lineNo			= InitInfo['lineNo']
	outputDebug		= InitInfo['outputDebug']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	PreviousValue = network.getTimeSeries("Reservoir","Fort Peck Lake", "Pool", "Stor").getPreviousValue(currentRuntimestep)
	BottomFloodControlZoneStor = network.getTimeSeries("Reservoir","Fort Peck Lake", "Carryover Multiple Use", "Stor-ZONE").getPreviousValue(currentRuntimestep)
	ExclusiveFloodControlZoneStor = network.getTimeSeries("Reservoir","Fort Peck Lake", "Exclusive Flood Control", "Stor-ZONE").getPreviousValue(currentRuntimestep)

	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), '\n\t\t\tPreviousValue = ', PreviousValue,
			'\n\t\t\tBottomFloodControlZoneStor = ', BottomFloodControlZoneStor, ' acre-ft',
			'\n\t\t\t%s' % 'BottomFloodControlZoneStor = %.0f acre-ft' % BottomFloodControlZoneStor)
	
	PercentFull = ((PreviousValue - BottomFloodControlZoneStor) / (ExclusiveFloodControlZoneStor - BottomFloodControlZoneStor)) * 100.
	outputDebug(debug, lineNo(), 'PercentFull = %.2f percent' % PercentFull)
	
	if PercentFull > 50. :
		Release = 2000.
	elif PercentFull > 20. :
		Release = 1000.
	else : Release = 0.
	
	network.getStateVariable("ReleaseTest").setValue(currentRuntimestep, Release)
	TestRelease = network.getStateVariable("ReleaseTest").getValue(currentRuntimestep)
	outputDebug(True, lineNo(), 'Test Release = %.0f cfs' % TestRelease)

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, Release)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue
  
PY

ResSim Scripting Utility: ResSimUtils.py

A ResSimUtils.py file can be used to store functions that would normally reside within an external (e.g., plot scripts, OSI tools, etc.) or compute script (e.g., scripted rules, state variables, etc.). One benefit of utilizing a utilities script is reducing the number of locations a function is defined. If multiple rules contain the same function and an update is needed for that function, a user would need to make the update in multiple rules or scripts. Another benefit is to reduce the number of characters in the scripts. ResSim will throw a null pointer error if scripted rules contain over 100,000 characters, which can happen when capturing complex reservoir operations. To minimize characters, logic can be moved to functions within a ResSimUtils.py file and loaded during the initialization portion of a simulation.riS

Variable definitions returned from functions should be named with caution as the definitions can conflict with variables available during the compute. For example, if a variable named "network" is returned from one of the functions, that will overwrite the predefined "network" variable passed into a ResSim script

External or Utility Scripts

Example functions shown below are best utlized in ResSim external or utility scripts that are found in the user's .../AppData/Roaming/HEC/HEC-ResSim/... directory. The functions are imported from a utilities script during the initialization portion of the simulation. 

External Functions

#==============================================================================
def closeWatershed() :
    '''
    Returns True or False
    '''
    return ResSim.closeWatershed()

#==============================================================================
def computeSimulationRun(simulationRun) :
    '''
    Computes one alternative of a simulation
    '''
    selectModule("Simulation").computeRun(simulationRun, -1, True, True)
    
#==============================================================================
def extractSimulationData() :
    '''
    Performs new extract for the current simulation
    '''
    getSimulation().runExtract(getSimulation().getSimulationExtract())

#==============================================================================
def createNewTimeSeries(    debug,          # Set to either True or False to print debug statements
                            Pathname,       # Generic pathname
                            Times,          # List of times in minutes
                            Values,         # List of values
                            Units,          # Units of values
                            ValueType,      # Type of values. PER-CUM, PER-AVER, INST-VAL, INST-CUM
                            TimeZone = None # Optional time zone specification for CWMSVue time series    
                            ) : 
    '''
    Create a new time series for either CWMSVue or DSSVue
    '''
    Tsc = TimeSeriesContainer()                    
    Interval = Times[1] - Times[0]
    PathnameParts = Pathname.split('.')
    if len(PathnameParts) > 3 : 
        # CWMSVue
        DssVue = False
        FullName                    = Pathname
        LocationParts               = PathnameParts[0].split('-')
        BaseLocation, SubLocation   = LocationParts[0], '-'.join(LocationParts[1 : ])
        ParameterParts              = PathnameParts[1].split('-')
        BaseParameter, SubParameter = ParameterParts[0], '-'.join(ParameterParts[1 : ])
        if PathnameParts[3][0] == '~' : Interval = 0 # Local regular time series specify Interval as 0
        VersionParts                = PathnameParts[-1].split('-')
        BaseVersion, SubVersion     = VersionParts[0], '-'.join(VersionParts[1 : ])
    else :
        # DSSVue
        DssVue = True # If this remains True, Tsc.watershed will be set
        PathnameParts = Pathname.split('/')
        Watershed                   = PathnameParts[0]
        LocationParts               = PathnameParts[1].split('-')
        BaseLocation, SubLocation   = LocationParts[0], '-'.join(LocationParts[1 : ])
        ParameterParts              = PathnameParts[2].split('-')
        BaseParameter, SubParameter = ParameterParts[0], '-'.join(ParameterParts[1 : ])
        if PathnameParts[-2][ : 2] == 'IR' : Interval = 0 # Irregular time series specify Interval as 0
        VersionParts                = PathnameParts[-1].split('-')
        BaseVersion, SubVersion     = VersionParts[0], '-'.join(VersionParts[1 : ])

    Tsc.fullName                = Pathname
    if DssVue : 
        Tsc.watershed           = Watershed 
    Tsc.location                = BaseLocation
    Tsc.subLocation             = SubLocation
    Tsc.parameter               = BaseParameter
    Tsc.subParameter            = SubParameter
    Tsc.interval                = Interval
    Tsc.version                 = BaseVersion
    Tsc.subVersion              = SubVersion
    Tsc.type                    = ValueType
    Tsc.units                   = Units
    Tsc.times                   = Times
    Tsc.values                  = Values
    Tsc.quality                 = [0] * len(Values)
    Tsc.startTime               = Times[0]
    Tsc.endTime                 = Times[-1]
    Tsc.numberValues            = len(Values)
    if TimeZone is not None :
        Tsc.timeZoneID          = TimeZone
        Tsc.timeZoneRawOffset   = 0
        
    return Tsc

#==============================================================================
def getAlternativeNames() :
    '''
    Returns an array of strings
    '''
    names = []
    for run in getSimulationRuns() : names.append(run.getUserName())
    return names
    
#==============================================================================
def getCurrentModule() :
    '''
    Returns hec.client.ClientMode object
    '''
    return ResSim.getCurrentModule()
    
#==============================================================================
def getFPart(alternativeName) :
    '''
    Returns the F Part for the specified alternative name
    '''
    for run in getSimulationRuns() :
        if str(run) == alternativeName : 
            fpart = run.getKey()
            break
    else :
        raise ValueError, "Alternative Not Found: %s" % alternativeName
    return fpart
    
#==============================================================================
def getFParts() :
    '''
    Returns the F Parts for each alternative name
    '''
    fparts = {}
    for run in getSimulationRuns() :
        fparts[str(run)] = run.getKey()
    return fparts
    
#==============================================================================
def getOSIWindow(SwitchToTab):
	'''
    Retrieve the OSI window if open
    '''
    if(SwitchToTab):
        rssrun = ClientAppWrapper.getCurrentModule().getRssRun()
        return OpSupportPlugin.openOSI(rssrun)
    else:
        osiWindow = OpSupportPlugin.getOSI()
        if(osiWindow == None):
            print ('No OSI Window open. Using reservoir operations rules.')
            #raise EnvironmentError("No OSI Window open. Please open an OSI window or enable SwitchToTab")
        return osiWindow

#==============================================================================
def getResSimModelInfo() :
    '''
    Retrieve the module, simulation, network, Fpart, and OutputDssPath for the simulation
    '''
    module = ResSim.getCurrentModule()
    if `module` != "Simulation" :
        msg = "ResSim is not in Simulation Module, exiting."
        logOutput(msg)
        MessageBox.showError(msg, scriptName)
        return -1
    simulation = module.getSimulation()
    rssRun = module.getActiveRun()
    network = rssRun.getRssSystem()
    Fpart = rssRun.getKey()
    OutputDssPath = simulation.getOutputDSSFilePath()
    return module, simulation, Fpart, OutputDssPath

#==============================================================================
def getResSimTimewindow(simulation):
    '''
    getResSimTimewindow Function  : Get the ResSim time window
    Author/Editor                 : Mike Perryman
    Last updated                  : Unknown
    '''
    runTimeWindow = simulation.getRunTimeWindow()
    lookbackTime = runTimeWindow.getLookbackTimeString()
    startTime = runTimeWindow.getStartTimeString()
    endTime = runTimeWindow.getEndTimeString()

    return lookbackTime, startTime, endTime

#==============================================================================
def getSelectedAlternativeNames() :
    '''
    Returns an array of strings
    '''
    names = []
    for run in getSelectedSimulationRuns() : names.append(run.getUserName())
    return names
    
#==============================================================================
def getSelectedSimulationRuns() :
    '''
    Returns an array of hec.model.SimulationRun objects
    '''
    return selectModule("Simulation").getSelectedSimulationRuns()
    
#==============================================================================
def getSimulation() :
    '''
    Returns the current simulation
    '''
    simulation = selectModule("Simulation").getSimulation()
    if not simulation :
        raise Exception, "No simulation is currently open."
        
    return simulation
    
#==============================================================================
def getSimulationDSSFileName() :
    '''
    Returns the full name of the DSS file for the current simulation
    '''
    return getSimulation().getOutputDSSFilePath()
    
#==============================================================================
def getSimulationName() :
    '''
    Returns the name of the current simulation
    '''
    return getSimulation().getName()
    
#==============================================================================
def getSimulationRun(alternativeName) :
    '''
    Returns an hec.model.SimulationRun object
    '''
    run = selectModule("Simulation").getSimulationRun(alternativeName)
    if not run :
        raise ValueError, "Alternative Not Found: %s" % alternativeName
        
    return run 
    
#==============================================================================
def getSimulationRuns() :
    '''
    Returns an array of hec.model.SimulationRun objects
    '''
    return selectModule("Simulation").getSimulationRuns()
    
#==============================================================================
def getTab(opSupportFrame, tabName, SwitchToTab):
    '''
    getTab Function    : If SwitchToTab is True, attempt to open the specified OSI tab. If it isn't available, throw an error. If SwitchToTab
                         is False, get the current active tab.
    Author/Editor      : RMA
    Last updated       : Unknown
    '''
    if(not isinstance(opSupportFrame, OpSupportFrame)):
        raise TypeError("getTab expects an OpSupportFrame, was passed a " + type(opSupportFrame).__name__)
    if(SwitchToTab):
        success = opSupportFrame.setSelectedTab(tabName) # Should return an OpSupportTabPanel or null, returns a boolean
        if(success is False):
            raise EnvironmentError("No tab named "+tabName)
        tab = opSupportFrame.getSelectedTab()
    else :
        tab = opSupportFrame.getTab(tabName)
    return tab

#==============================================================================
def getTimeWindow() :
    '''
    Returns hec.heclib.util.HecTime objects for start and end time
    '''
    startTime = HecTime()
    endTime = HecTime()
    getCurrentModule().getTimeWindow(startTime, endTime)
    return startTime, endTime
    
#==============================================================================
def getTimeWindowString() :
    '''
    Returns time window as a string
    '''
    return getCurrentModule().getTimeWindowString()
    
#==============================================================================
def getWatershed() :
    '''
    Returns hec.client.ClientWorkspace object or None
    '''
    return ResSim.getWatershed()
    
#==============================================================================
def getWatershedName() :
    '''
    Returns string or None
    '''
    return ResSim.getWatershedName()

#==============================================================================
def isWatershedOpened() :
    '''
    returns whether any watershed is opened
    '''
    return ResSim.isWatershedOpened()
    
#==============================================================================
def openSimulation(simulationName) :
    '''
    causes the simulation module to open the specified simulation
    '''
    if not selectModule("Simulation").openSimulation(simulationName) :
        raise ValueError, "Simulation Not Found: %s" % simulationName
        
#==============================================================================
def openWatershed(watershedName) :
    '''
    returns hec.client.ClientWorkspace object or None
    '''
    return ResSim.openWatershed(watershedName)
#==============================================================================
def runSimulation(simulationName, newExtract=False, *alternativeNames) :
    '''
    returns True if all (possibly specified)  simulations are executed
    
    if no alternativenames are specified, all alternatives are executed
    '''
    
    #---------------------#
    # open the simulation #
    #---------------------#
    openSimulation(simulationName)

    #-----------------------------------#
    # get the runs for the alternatives #
    #-----------------------------------#
    if not alternativeNames :
        #------------------#
        # all alternatives #
        #------------------#
        runs = getSimulationRuns()
    else :
        #------------------------#
        # specified alternatives #
        #------------------------#
        runs = []
        for alternativeName in alternativeNames :
            runs.append(getSimulationRun(str(alternativeName).strip()))

    if newExtract :
        print("===========================================")
        start = time.time()
        print(time.ctime(start))
        print("Extracting data for %s" % str(getSimulation()))
        print("===========================================")
        extractSimulationData()
        print("===========================================")
        end = time.time()
        print(time.ctime(end))
        print("%s extract finished in %s" % (str(getSimulation()), hms(end - start)))
        print("===========================================")
    #----------------------#
    # run the alternatives #
    #----------------------#
    for run in runs : 
        print
        print("===========================================")
        start = time.time()
        print(time.ctime(start))
        print("Computing %s" % run)
        print("===========================================")
        computeSimulationRun(run)
        print("===========================================")
        end = time.time()
        print(time.ctime(end))
        print("%s compute finished in %s" % (run, hms(end - start)))
        print("===========================================")

    return True
      
#==============================================================================
def selectModule(moduleName) :
    '''
    returns hec.client.ClientMode object
    '''
    oldModule = getCurrentModule()
    oldModuleName = oldModule.getName()
    if oldModuleName == moduleName : 
        module = oldModule
    else :
        ResSim.selectModule(moduleName)
        module = ResSim.getCurrentModule()
        if not module or module.getName() != moduleName :
            ResSim.selectModule(oldModuleName)
            raise ValueError, "Invalid Module Name: %s" % moduleName
        
    return module
    
#==============================================================================
PY

Description: openWatershed, getWatershed, getWatershedName, isWatershedOpened are functions used to return watershed properties. In this example, an external script is run to return the watershed, watershed name, and check if a watershed is open. 

getWatershed, getWatershedName, and isWatershedOpened Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.script 		import ResSim
import sys, os, inspect

#
# Load the ResSimUtils module
#
wsDir = ResSim.getWatershed().getWorkspacePath()
sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
if sharedDir not in sys.path : sys.path.append(sharedDir)
import ResSimUtils
reload(ResSimUtils)
from ResSimUtils import outputDebug, lineNo, getWatershed, getWatershedName, isWatershedOpened

#
# Input data
#
# Set debug = True to print all debug statements and = False to turn them off
debug = True

Watershed = getWatershed()
WatershedName = getWatershedName()
WatershedOpen = isWatershedOpened()
outputDebug(debug, lineNo(), 'Watershed = ', Watershed,
			'\n\t\t\tWatershedName = ', WatershedName,
			'\n\t\t\tIs a Watershed Open? ', WatershedOpen)

#==============================================================================

Console Output: 
Debug Line 29   |	Watershed = C:/Users/G0PDRRJL/Documents/Projects/HEC_Detail/Watersheds/MR_System_2021-08-16_v35_ResSim_Testing
					WatershedName = MR_System_2021-08-16_v35_ResSim_Testing
					Is a Watershed Open? True 
PY

Description: getCurrentModule is used to return the active module in ResSim. In this example, an external script is run from the Simulation module so the simulation is returned. getResSimModelInfo uses several methods to return the current simulation, active run, 

openSimulation, getSimuationName, getSelectedSimulationRuns, extractSimulationData, and runSimulation Example

#==============================================================================

Console Output: 

 
PY

Description: getCurrentModule is used to return the active module in ResSim. In this example, an external script is run from the Simulation module so the simulation is returned. getResSimModelInfo uses several methods to return the current simulation, active run, 

getTimeWindow and getTimeWindowString Example

#==============================================================================

Console Output: 
PY

Description: getCurrentModule is used to return the active module in ResSim. In this example, an external script is run from the Simulation module so the simulation is returned. getResSimModelInfo uses several methods to return the current simulation, active run, 

getAlternatives and getSelectedAlternativeNames Example

#==============================================================================

Console Output: 
PY

Description: getCurrentModule is used to return the active module in ResSim. In this example, an external script is run from the Simulation module so the simulation is returned. getResSimModelInfo uses several methods to return the current simulation, active run, 

getFPart and getFParts Example

#==============================================================================

Console Output: 
PY

Description: getCurrentModule is used to return the active module in ResSim. In this example, an external script is run from the Simulation module so the simulation is returned. getResSimModelInfo uses several methods to return the current simulation, active run, 

getCurrentModule and getResSimModelInfo Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.script 		import ResSim
import sys, os

#
# Load the ResSimUtils module
#
wsDir = ResSim.getWatershed().getWorkspacePath()
sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
if sharedDir not in sys.path : sys.path.append(sharedDir)
import ResSimUtils
reload(ResSimUtils)
from ResSimUtils import outputDebug, lineNo, getCurrentModule, getResSimModelInfo

#
# Input data
#
# Set debug = True to print all debug statements and = False to turn them off
debug = True


ResSimCurModule = getCurrentModule()
outputDebug(debug, lineNo(), 'Current ResSim Module = ', ResSimCurModule)
module, simulation, Fpart, OutputDssPath = getResSimModelInfo()
outputDebug(debug, lineNo(), 'Current ResSim Module = ', module,
			'\n\t\t\tSimulation = ', simulation,
			'\n\t\t\tAlternative Fpart = ', Fpart,
			'\n\t\t\tOutput Dss Pathname = ', OutputDssPath)

#==============================================================================

Console Output: 
Debug Line 30   |	Current ResSim Module = Simulation
Debug Line 32   |	Current ResSim Module = Simulation
					Simulation = Delete
					Alternative Fpart = MM2018PC--0
					Output Dss Pathname = C:/Users/G0PDRRJL/Documents/Projects/HEC_Detail/Watersheds/MR_System_2021-08-16_v35_ResSim_Testing/rss/Delete/simulation.dss  
PY

Description: getCurrentModule is used to return the active module in ResSim. In this example, an external script is run from the Simulation module so the simulation is returned. getResSimModelInfo uses several methods to return the current simulation, active run, 

getOSIWindow and getTab Example

#==============================================================================

Console Output: 
PY

Compute Scripts

Compute scripts are used during a ResSim simulation. Scripted Rules within an operations set and State Variable scripts are two examples of compute scripts. Due to the file size limit on compute scripts, examples shown below are using functions stored in a utilities script, which are imported during the initialization portion of the simulation. 

Compute Functions

#==============================================================================
def getFraction(    val, 
                    loVal, 
                    hiVal
                    ) :
    '''
    Returns fraction of value between high and low values
    '''
    if hiVal == loVal : return 1.
    else : return float(val - loVal) / (hiVal - loVal)

#==============================================================================
def getInterp(  debug, # Set to True to print all debug statements
                seq, 
                val
                ) :
    '''
    Returns interpolation/extrapolation info for a tuple/list.  Returns the location in the list
    '''
    assert len(seq) > 1
    if val > seq[-1] :
        lo = len(seq) - 2
    else :
        lo = max(0, bisect(seq, val) - 1)
    fraction = getFraction(val, seq[lo], seq[lo+1])
    if   fraction > 1. : outputDebug(debug, lineNo(), '** Value %f above high value of %f' % (val, seq[-1]))
    elif fraction < 0. : outputDebug(debug, lineNo(), '** Value %f below low value of %f' % (val, seq[0]))
    return lo, fraction

#==============================================================================
def getYValue(  debug, # Set to True to print all debugging statements
                pdc, 
                lo, 
                fraction, 
                curve
                ) :
    '''
    Get the Y value from a curve
    '''
    hi = lo + 1
    return pdc.yOrdinates[curve][lo] + fraction * (pdc.yOrdinates[curve][hi] - pdc.yOrdinates[curve][lo])

#==============================================================================
def julianDay(  *args
                ) :
    '''
    Returns the julian day, accounting for leap years
    '''
    Year = args[-1] # The year should always be the last argument
    DaysPrev = [0]
    for x in range(1, 13, 1) :
        DaysPrev.append(DaysPrev[x - 1] + maxDay(x, Year))
    argCount = len(args)
    if argCount == 2 :
        if type(args[0]) == type(0) or type(args[0]) == type(0.0) :
            jday = args[0]
            if maxDay(2, Year) == 28 :
                if not 1 <= jday <= 365 :
                    raise ValueError('Julian day out of range 1..365')
            elif maxDay(2, Year) == 29 :
                if not 1 <= jday <= 366 :
                    raise ValueError('Julian day out of range 1..366')
            for i in range(12)[::-1] :
                if jday > DaysPrev[i] :
                    Month = i + 1
                    break
            Day = jday - DaysPrev[Month - 1]
            Date = '%s_%02d' % (calendar.month_abbr[Month], Day)
            return Date
        else :
            Month, Day = args[0].split('_')
            Month = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'].index(Month.upper()) + 1
            Day = int(Day);
            return julianDay(Month, Day, Year)
    elif argCount == 3 :
        Month, Day, Year = args
        if not 1 <= Month <= 12 :
            raise ValueError('Month is out of range 1..12')
        if not 1 <= Day <= 31 :
            raise ValueError('Day is out of range 1..31')
        
        return DaysPrev[Month - 1] + Day
    else :
        raise ValueError('Expected 2 or 3 arguments, got %d' % argCount)

#==============================================================================
def linearInterpolate(  xVal1,    # x value used for slope
                        xVal2,    # x value used for slope
                        yVal1,    # y value used for slope
                        yVal2,    # y value used for slope
                        xVal    # y value used for interpolation
                        ):
    '''
    Linearly interpolates to calculate a value
    '''
    slope = (yVal1 - yVal2) / (xVal1 - xVal2)
    interpVal = slope * (xVal - xVal2) + yVal2
    return interpVal

#==============================================================================
def lineNo() :
    '''
    Returns the current line number
    '''
    return inspect.currentframe().f_back.f_lineno

#==============================================================================
def maxDay( mon,    # Current month
            year    # Current year
            ) :
    '''
    Returns the total number of days in a month, accounting for leap years
    '''
    maxday = None
    if mon in (1,3,5,7,8,10,12) :
        maxday = 31
    elif mon in (4,6,9,11) :
        maxday = 30
    elif year % 4 != 0 or (year % 100 == 0 and year % 400 != 0) :
        maxday = 28
    else :
        maxday = 29
    return maxday

#==============================================================================
def outputDebug(    *args
                    ) :
    '''
    Prints formatted debug statements. Statements are "On" or "Off" depending on the first argument being True or False
    '''
    ArgCount = len(args)
    if ArgCount < 2 :
        raise ValueError('Expected at least 2 arguments, got %d' % argCount)
    if type(args[0]) != type(True) :
        raise ValueError('Expected first argument to be either True or False')
    if type(args[1]) != type(1) :
        raise ValueError('Expected second argument to be line number')

    if args[0] == True: 
        DebugStatement = 'Debug Line %d   |\t' % args[1]
        for x in range(2, ArgCount, 1) :
            DebugStatement += str(args[x])
        print DebugStatement

#==============================================================================
def percentile( debug,          # Set to True to print debug statements. Set to False to turn all debug statements off
                Values,         # List of values
                Quantile,       # Quantile of data requested
                EquationType    # There are multiple equations to estimate the value of a quantile. Type R-6 will match Excel's PERCENTILE.EXC and type R-7 will match
                                #    Excel's PERCENTILE.INC. Enter the type as a string i.e. 'R-6' or 'R-7'
                ) :
    '''
    Estimate quantile value based on a sample and specified quantile. The equations match the R-6 type of quantile estimation that is used by Excel's 
    PERCENTILE.EXC function
    '''
    if not Values : 
        return None
    SortedValues = sorted(Values)
    outputDebug(debug, lineNo(), 'Sorted Values = ', len(SortedValues))
    if EquationType == 'R-6' : 
        h = (len(SortedValues) + 1) * Quantile - 1
        if Quantile == 0. or Quantile == 1. : sys.exit('Quantiles between 0 and 1 are acceptable for R-6 equation type. If 0 or 1 is needed, change equation type to R-7.')
        if 0 < Quantile < 1 : pass
        else : sys.exit('Quantiles between 0 and 1 are acceptable for R-6 equation type.')
    elif EquationType == 'R-7' : 
        h = ((len(SortedValues) - 1) * Quantile + 1) - 1
        if 0 <= Quantile <= 1 : pass
        else : sys.exit('Quantiles between 0 and 1 are acceptable for R-6 equation type.')
    else : sys.exit('Only R-6 and R-7 equation types are supported with this function.')
    if h < 0 : h = 0
    f = math.floor(h)
    c = math.ceil(h)
    outputDebug(debug, lineNo(), 'h = ', h, '\tf = ', f, '\tc = ', c)
    if f == c :
        return SortedValues[int(h)]
    
    ValueOfQuantile = ((h - f) * (SortedValues[int(c)] - SortedValues[int(f)])) / (c - f) + SortedValues[int(f)]
    return ValueOfQuantile

#==============================================================================
def routeFlow(  RoutingCoefficients,    # List of routing coefficients
                FlowDataset             # Flow data that will be routed
                ) :
    '''
    Performs coefficient routing
    '''
    lag = len(RoutingCoefficients) - 1 # Length of time before any portion of flow reaches the downstream location
    routedFlow = []
    for i in range(len(FlowDataset)):
        if i < lag:
            routedFlow.append(0) # Add a place holder of 0 for days less than the lag time
        else:
            routedFlowTempList = []
            for c in range(len(RoutingCoefficients)):
                flow = FlowDataset[i-c] * RoutingCoefficients[c]
                routedFlowTempList.append(flow)
            release = sum(routedFlowTempList)
            routedFlow.append(release)
    return routedFlow

#==============================================================================
PY

Description: outputDebug and lineNo are used to print debug statements. Print statements can be added to write items to the console log; however, print statements should be turned off once debugging is complete and a compute is started because writing many items to the console log can significantly slow the compute down. If print statements are within a script, a user would need to either comment out the statements or delete them altogether. Utilizing the outputDebug function allows a user to keep degug statements within the script and quickly turn them off by switching one variable from True to False. In this example, a variable named debug is defined as True in the initRuleScript portion of a script rule, which turns on the debug statements within this scripted rule, writing them to the console log. The second arguement of the outputDebug function is another function: lineNo. lineNo will inspect the script and determine which line number the debug statement resides. This allows a user to more easily track debug statements throughout a scripted rule. After lineNo, a user can format and define as many arguements as desired. 

outputDebug and lineNo Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'			: debug,
					'lineNo'		: lineNo,
					'outputDebug'	: outputDebug,
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	lineNo			= InitInfo['lineNo']
	outputDebug		= InitInfo['outputDebug']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 'Formatted debug statement.\n\t\t\tSet debug = False to turn off print statements.')

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, 0)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue

#==============================================================================

Console Output:
Debug Line 93   |	01Mar1930, 24:00 Pass Number 1:	Formatted debug statement.
					Set debug = False to turn off print statements.

PY

Description: maxDay is used to determine the number of days in each month. A user provides the month and year as an integer. This function is used within the julianDay function. 

maxDay Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo, maxDay

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'			: debug,
					'lineNo'		: lineNo,
					'maxDay'		: maxDay,
					'outputDebug'	: outputDebug,
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	lineNo			= InitInfo['lineNo']
	maxDay			= InitInfo['maxDay']
	outputDebug		= InitInfo['outputDebug']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	JanMaxDay = maxDay(1, Year)
	FebNonLeapYearMaxDay = maxDay(2, Year)
	FebLeapYearMaxDay = maxDay(2, 2016)
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 
			'\n\t\t\tJanMaxDay = ', JanMaxDay,
			'\n\t\t\tFebNonLeapYearMaxDay = ', FebNonLeapYearMaxDay,
			'\n\t\t\tFebLeapYearMaxDay = ', FebLeapYearMaxDay)

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, 0)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue
  #==============================================================================

Console Output:
Debug Line 98   |	01Mar1930, 24:00 Pass Number 1:	
					JanMaxDay = 31
					FebNonLeapYearMaxDay = 28
					FebLeapYearMaxDay = 29

PY

Description: julianDay is used to determine the julian day of a calendar with a range of 1-365 or 1-366 during a leap year. A user can provide the function 2 or 3 arguements to calculate the julian day. If providing 2 arguements, the first arguement is a string with the month abbreviation and day (e.g., 'MAR_01') followed by the year. If providing 3 arguements, the first, second, and third arguements are the month, day, and year integers, respectively. 

julianDay Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo, julianDay, maxDay

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'			: debug,
					'julianDay'		: julianDay,
					'lineNo'		: lineNo,
					'maxDay'		: maxDay,
					'outputDebug'	: outputDebug,
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	julianDay		= InitInfo['julianDay']
	lineNo			= InitInfo['lineNo']
	maxDay			= InitInfo['maxDay']
	outputDebug		= InitInfo['outputDebug']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	Mar01jDayNonLeapYear = julianDay('MAR_01', Year) # Non Leap Year
	Mar01jDayLeapYear = julianDay('MAR_01', 2016) # Leap Year
	Jul15jDay = julianDay('JUL_15', Year)
	Sep07jDay = julianDay(9, 7, Year)
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 
			'\n\t\t\tMar01jDayNonLeapYear = ', Mar01jDayNonLeapYear,
			'\n\t\t\tMar01jDayLeapYear = ', Mar01jDayLeapYear,
			'\n\t\t\tJul15jDay = ', Jul15jDay,
			'\n\t\t\tSep07jDay = ', Sep07jDay)
	stop

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, 0)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue

#==============================================================================

Console Output:
Debug Line 101   |	01Mar1930, 24:00 Pass Number 1:	
					Mar01jDayNonLeapYear = 60
					Mar01jDayLeapYear = 61
					Jul15jDay = 196
					Sep07jDay = 250
   
PY

Description: getFraction is used to determine where a value falls within a list. In this example, the function is given a reservoir storage along with a reservoir storage higher than the given storage and a reservoir storage lower than the given storage. If the given storage is equal to the  lower storage value, the computed fraction will be 0.0. If the given julian day is between 2 values, the computed fraction will be a percentage between the values surrounding the given julian day. This function is a component in other functions used for linear interpolation.

getFraction Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo, getFraction

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'			: debug,
					'getFraction'	: getFraction,
					'lineNo'		: lineNo,
					'outputDebug'	: outputDebug,
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	getFraction		= InitInfo['getFraction']
	lineNo			= InitInfo['lineNo']
	outputDebug		= InitInfo['outputDebug']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	HighValue = 51.5 # MAF
	LowValue = 46.8 # MAF

	CurrentStorage = 46.8 # MAF
	SystemStorageFraction = getFraction(CurrentStorage, HighValue, LowValue)
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 'SystemStorageFraction = %.2f' % SystemStorageFraction)

	CurrentStorage = 49.0 # MAF
	SystemStorageFraction = getFraction(CurrentStorage, HighValue, LowValue)
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 'SystemStorageFraction = %.2f' % SystemStorageFraction)

	CurrentStorage = 51.5 # MAF
	SystemStorageFraction = getFraction(CurrentStorage, HighValue, LowValue)
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 'SystemStorageFraction = %.2f' % SystemStorageFraction)

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, 0)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue

#==============================================================================

Console Output:
Debug Line 100   |	01Mar1930, 24:00 Pass Number 1:	SystemStorageFraction = 1.00
Debug Line 104   |	01Mar1930, 24:00 Pass Number 1:	SystemStorageFraction = 0.53
Debug Line 108   |	01Mar1930, 24:00 Pass Number 1:	SystemStorageFraction = -0.00
 
PY

ServiceLevelCurves.dss

Service Level Curves


Description: getInterp is used to determine the list index of the first value smaller than a given value. It also utilizes getFraction to determine where a value falls within a list. In this example, the function is give a list of julian days retrieved from the x ordinates in a paired data container. Based on a given julian day (e.g., Feb 1, Feb 25, etc.) it determines the index of the value that is immediately less than the provided value. If the given julian day is in the list of values, the computed fraction will be 0.0. If the given julian day is between 2 values, the computed fraction will be a percentage between the values surrounding the given julian day.

getInterp Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.dss		import HecDss
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo, getInterp, julianDay

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True

	#
	# Load the system level curves from DSS
	#
	ForecastDssFullname = network.getRssRun().getDSSOutputFile()
	ServiceLevelDssFullname = sharedDir + '\\ServiceLevelCurves.dss'
	ServiceLevelPathname	= '/MISSOURI RIVER/SYS/DATE-VOLUME///SERVICE LEVEL/'
	ThresholdsPathname	  	= '/MISSOURI RIVER/SYS/DATE-VOLUME///THRESHOLDS/'
	ServiceLevelPdc		 	= None
	ThresholdsPdc		   	= None
	ErrorMessage			= None
	LastNavEndDate		  	= {}

	#
	# Verify the service level info is available
	#
	try : 
		while True:
			if not os.path.exists(ServiceLevelDssFullname) :
				ErrorMessage = "DSS file does not exist: %s" % ServiceLevelDssFullname
				break
			ServiceLevelDssFile = HecDss.open(ServiceLevelDssFullname)
			pathnames = ServiceLevelDssFile.getPathnameList()
			outputDebug(debug, lineNo(), 'pathnames = ', pathnames)
			if ServiceLevelPathname not in pathnames :
				ErrorMessage = "DSS file %s does not contain pathname %s" % (ServiceLevelDssFullname, ServiceLevelPathname)
				break
			if ThresholdsPathname not in pathnames :
				ErrorMessage = "DSS file %s does not contain pathname %s" % (ServiceLevelDssFullname, ThresholdsPathname)
				break
			break
		if ErrorMessage :
			print 'ERROR : ' + ErrorMessage
			return False
		#
		# Get the service level information
		#
		ServiceLevelPdc = ServiceLevelDssFile.get(ServiceLevelPathname)
		LevelValues = [float(label) for label in ServiceLevelPdc.labels]
		ThresholdsPdc = ServiceLevelDssFile.get(ThresholdsPathname)
		ThresholdCurves = {}
		for i in range(len(ThresholdsPdc.labels)) :
			ThresholdCurves[ThresholdsPdc.labels[i]] = i
	finally : 
		try : ServiceLevelDssFile.close()
		except : pass
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'				: debug,
					'getInterp'			: getInterp,
					'julianDay'			: julianDay,
					'lineNo'			: lineNo,
					'outputDebug'		: outputDebug,
					'ServiceLevelPdc'	: ServiceLevelPdc,
					'ThresholdCurves'	: ThresholdCurves,
					'ThresholdsPdc'		: ThresholdsPdc,
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	getInterp		= InitInfo['getInterp']
	julianDay		= InitInfo['julianDay']
	lineNo			= InitInfo['lineNo']
	outputDebug		= InitInfo['outputDebug']
	ServiceLevelPdc = InitInfo['ServiceLevelPdc']
	ThresholdCurves	= InitInfo['ThresholdCurves']
	ThresholdsPdc	= InitInfo['ThresholdsPdc']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	jDay			= julianDay(Month, Day, Year) # Current julian day
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	Feb01Lo, Feb01Fraction = getInterp(debug, ServiceLevelPdc.xOrdinates, julianDay('FEB_01', Year))
	Feb25Lo, Feb25Fraction = getInterp(debug, ServiceLevelPdc.xOrdinates, julianDay('FEB_25', Year))
	Mar01Lo, Mar01Fraction = getInterp(debug, ServiceLevelPdc.xOrdinates, jDay)
	Mar15Lo, Mar15Fraction = getInterp(debug, ServiceLevelPdc.xOrdinates, julianDay('MAR_15', Year))
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 
			'\n\t\t\tFebruary 1 Julian Day = ', julianDay('FEB_01', Year), ' Feb01 Low Index = ', Feb01Lo, ' Feb01Fraction = %.2f' % Feb01Fraction,
			'\n\t\t\tFebruary 25 Julian Day = ', julianDay('FEB_25', Year), ' Feb25 Low Index = ', Feb25Lo, ' Feb25Fraction = %.2f' % Feb25Fraction,
			'\n\t\t\tMarch 1 Julian Day = ', jDay, ' Mar01 Low Index = ', Mar01Lo, ' Mar01Fraction = %.2f' % Mar01Fraction,
			'\n\t\t\tMarch 15 Julian Day = ', julianDay('MAR_15', Year), ' Mar15 Low Index = ', Mar15Lo, ' Mar15Fraction = %.2f' % Mar15Fraction)

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, 0)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue

#==============================================================================

Console Output:
Debug Line 155   |	01Mar1930, 24:00 Pass Number 1:	
					February 1 Julian Day = 32 Feb01 Low Index = 1 Feb01Fraction = 0.07
					February 25 Julian Day = 56 Feb25 Low Index = 1 Feb25Fraction = 0.93
					March 1 Julian Day = 60 Mar01 Low Index = 4 Mar01Fraction = 0.00
					March 15 Julian Day = 74 Mar15 Low Index = 18 Mar15Fraction = 0.00 
PY

ServiceLevelCurves.dss

Service Level Curves


Description: getYValue is used to retrieve a list of y ordinates from a paired data container, linearly interpolating values if between x ordinates listed in the paired data container. In this example, getInterp is initially used to calculate the index of the specified julian day that is immediately less than the provided julian day. If the given julian day is in the list of values, the computed fraction will be 0.0. If the given julian day is between 2 values, the computed fraction will be a percentage between the values surrounding the given julian day. Those parameters are then used within the getYValue function to retrieve a list of y values for the given index.

getYValue Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.dss		import HecDss
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo, getInterp, getYValue, julianDay

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True

	#
	# Load the system level curves from DSS
	#
	ForecastDssFullname = network.getRssRun().getDSSOutputFile()
	ServiceLevelDssFullname = sharedDir + '\\ServiceLevelCurves.dss'
	ServiceLevelPathname	= '/MISSOURI RIVER/SYS/DATE-VOLUME///SERVICE LEVEL/'
	ThresholdsPathname	  	= '/MISSOURI RIVER/SYS/DATE-VOLUME///THRESHOLDS/'
	ServiceLevelPdc		 	= None
	ThresholdsPdc		   	= None
	ErrorMessage			= None
	LastNavEndDate		  	= {}

	#
	# Verify the service level info is available
	#
	try : 
		while True:
			if not os.path.exists(ServiceLevelDssFullname) :
				ErrorMessage = "DSS file does not exist: %s" % ServiceLevelDssFullname
				break
			ServiceLevelDssFile = HecDss.open(ServiceLevelDssFullname)
			pathnames = ServiceLevelDssFile.getPathnameList()
			outputDebug(debug, lineNo(), 'pathnames = ', pathnames)
			if ServiceLevelPathname not in pathnames :
				ErrorMessage = "DSS file %s does not contain pathname %s" % (ServiceLevelDssFullname, ServiceLevelPathname)
				break
			if ThresholdsPathname not in pathnames :
				ErrorMessage = "DSS file %s does not contain pathname %s" % (ServiceLevelDssFullname, ThresholdsPathname)
				break
			break
		if ErrorMessage :
			print 'ERROR : ' + ErrorMessage
			return False
		#
		# Get the service level information
		#
		ServiceLevelPdc = ServiceLevelDssFile.get(ServiceLevelPathname)
		LevelValues = [float(label) for label in ServiceLevelPdc.labels]
		ThresholdsPdc = ServiceLevelDssFile.get(ThresholdsPathname)
		ThresholdCurves = {}
		for i in range(len(ThresholdsPdc.labels)) :
			ThresholdCurves[ThresholdsPdc.labels[i]] = i
	finally : 
		try : ServiceLevelDssFile.close()
		except : pass
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'				: debug,
					'getInterp'			: getInterp,
					'getYValue'			: getYValue,
					'julianDay'			: julianDay,
					'lineNo'			: lineNo,
					'outputDebug'		: outputDebug,
					'ServiceLevelPdc'	: ServiceLevelPdc,
					'ThresholdCurves'	: ThresholdCurves,
					'ThresholdsPdc'		: ThresholdsPdc,
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	getInterp		= InitInfo['getInterp']
	getYValue		= InitInfo['getYValue']
	julianDay		= InitInfo['julianDay']
	lineNo			= InitInfo['lineNo']
	outputDebug		= InitInfo['outputDebug']
	ServiceLevelPdc = InitInfo['ServiceLevelPdc']
	ThresholdCurves	= InitInfo['ThresholdCurves']
	ThresholdsPdc	= InitInfo['ThresholdsPdc']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	jDay			= julianDay(Month, Day, Year) # Current julian day
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	lo, fraction = getInterp(debug, ServiceLevelPdc.xOrdinates, jDay)
	WaterSupplyFcsts = [getYValue(debug, ServiceLevelPdc, lo, fraction, i) for i in range(len(ServiceLevelPdc.yOrdinates))]
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 
			'\n\t\t\tMarch 1 Julian Day = ', jDay, ' Mar01 lo = ', lo, ' Mar01Fraction = %.2f' % fraction, 
			'\n\t\t\tMar01 Water Supply Forecasts = ', WaterSupplyFcsts)

	lo, fraction = getInterp(debug, ServiceLevelPdc.xOrdinates, julianDay('MAR_20', Year))
	WaterSupplyFcsts = [getYValue(debug, ServiceLevelPdc, lo, fraction, i) for i in range(len(ServiceLevelPdc.yOrdinates))]
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 
			'\n\t\t\tMarch 20 Julian Day = ', julianDay('MAR_20', Year), ' Mar20 lo = ', lo, ' Mar20Fraction = %.2f' % fraction, 
			'\n\t\t\tMar20 Water Supply Forecasts = ', WaterSupplyFcsts)

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, 0)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue

#==============================================================================

Console Output:
Debug Line 155   |	01Mar1930, 24:00 Pass Number 1:	
			March 1 Julian Day = 60 Mar01 lo = 4 Mar01Fraction = 0.00
			Mar01 Water Supply Forecasts = [67.052001953125, 70.00199890136719, 74.03299713134766, 80.38300323486328, 85.47100067138672, 90.8550033569336, 95.8550033569336, 100.8550033569336]
Debug Line 161   |	01Mar1930, 24:00 Pass Number 1:	
			March 20 Julian Day = 79 Mar20 lo = 23 Mar20Fraction = 0.00
			Mar20 Water Supply Forecasts = [66.48899841308594, 69.44499969482422, 73.33699798583984, 79.6240005493164, 84.59200286865234, 89.82499694824219, 94.82499694824219, 99.82499694824219]
PY

Fort Randall Guide Curve

Description: linearInterpolate is used to linearly interpolate between 2 sets of x and y values and a given x value. In this example, Fort Randall reservoir is drawdown from September through November, with the minimum elevation of 1337.5 ft occuring at the end of the navigation season, which is shown as December 1 in the plot. If the navigation season is shortened during a long-term drought, the drawdown/guide curve needs to be adjusted to correspond to the new end of navigation season. In this example, the navigation end date is November 1 so the linearInterpolation function is used to estimate the guide curve elevations on 2 dates: October 20 and November 1.

linearInterpolate Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo, julianDay, maxDay, linearInterpolate

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'				: debug,
					'julianDay'			: julianDay,
					'linearInterpolate'	: linearInterpolate,
					'lineNo'			: lineNo,
					'maxDay'			: maxDay,
					'outputDebug'		: outputDebug,
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug				= InitInfo['debug']
	julianDay			= InitInfo['julianDay']
	linearInterpolate	= InitInfo['linearInterpolate']
	lineNo				= InitInfo['lineNo']
	maxDay				= InitInfo['maxDay']
	outputDebug			= InitInfo['outputDebug']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Input for example
	GcSep01Elev = 1355.2
	GcOct01Elev = 1353.5
	GcNov01Elev = 1345.0
	GcDec01Elev = 1337.5
	# September drawdown values
	StartElev = GcSep01Elev
	StartHecTime = HecTime(); StartHecTime.set('01Sep%d 0000' % Year) # Oct 1 hec time
	EndElev1 = GcOct01Elev
	EndHecTime1 = HecTime(); EndHecTime1.set('01Oct%d 0000' % Year) # Oct 1 hec time
	# October-November drawdown values
	EndElev2 = GcDec01Elev
	EndHecTime2 = HecTime(); EndHecTime2.set('01Nov%d 0000' % Year) # End of navigation season
	
	# Example Calculation
	# Navigation end date is Nov 1 instead of Dec 1. Estimate new guide curve elevation on Oct 20
	FutureHecTime = HecTime(); FutureHecTime.set('20Oct%d 0000' % Year)
	GcCurElev = linearInterpolate(EndHecTime1.value(), EndHecTime2.value(), EndElev1, EndElev2, FutureHecTime.value())
	OriginalGcElev = 1345.58
	
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 
			'\n\t\t\tFutureHecTime = ', FutureHecTime, '   Original Guide Curve Elevation = %.2f ft' % OriginalGcElev, '\tNew Guide Curve Elevation = %.2f ft' % GcCurElev)

	# Navigation end date is Nov 1 instead of Dec 1. Estimate new guide curve elevation on Nov 1
	FutureHecTime = HecTime(); FutureHecTime.set('01Nov%d 0000' % Year)
	GcCurElev = linearInterpolate(EndHecTime1.value(), EndHecTime2.value(), EndElev1, EndElev2, FutureHecTime.value())
	OriginalGcElev = 1343.27
	
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 
			'\n\t\t\tFutureHecTime = ', FutureHecTime, '   Original Guide Curve Elevation = %.2f ft' % OriginalGcElev, '\tNew Guide Curve Elevation = %.2f ft' % GcCurElev)

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, 0)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue

#==============================================================================

Console Output:
Debug Line 118   |	01Mar1930, 24:00 Pass Number 1:	
					FutureHecTime = 19 October 1930, 24:00   Original Guide Curve Elevation = 1345.58 ft	New Guide Curve Elevation = 1343.69 ft
Debug Line 126   |	01Mar1930, 24:00 Pass Number 1:	
					FutureHecTime = 31 October 1930, 24:00   Original Guide Curve Elevation = 1343.27 ft	New Guide Curve Elevation = 1337.50 ft
 
PY

Description: percentile is used to estimate a value at a given quantile based on a list of values and a specified quantile. This function will perform both the R-6 estimate, which will match Excel's PERCENTILE.EXC, and the R-7 estimate, which will match Excel's PERCENTILE.INC. In this example, 125 years of annual runoff above Sioux City, IA is provided to the function along with 3 different percentiles: 0.9, 0.50, and 0.10. The function is run for both the R-6 and R-7 methods.

percentile Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo, percentile

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True
	
	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'			: debug,
					'lineNo'		: lineNo,
					'outputDebug'	: outputDebug,
					'percentile'	: percentile
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	lineNo			= InitInfo['lineNo']
	outputDebug		= InitInfo['outputDebug']
	percentile		= InitInfo['percentile']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	# 125 years of annual runoff above Sioux City, IA
	AnnualRunoff = [28088, 32391, 23194, 24598, 20908, 24598, 25491, 19796, 28713, 34651, 32683, 34893, 23190, 22701, 33597, 29496, 27897, 
					32794, 32997, 33295, 26301, 13891, 29199, 22801, 24101, 31222, 27098, 26479, 22000, 36988, 30283, 24653, 18452, 10701, 
					19463, 18166, 11164, 14323, 14339, 14315, 20652, 17271, 12101, 16714, 25105, 31394, 29726, 22729, 20355, 28279, 28353, 
					23189, 29369, 28856, 34232, 25367, 19223, 16410, 19418, 22079, 16971, 20017, 20120, 12432, 30333, 20313, 23705, 32473, 
					19685, 31026, 23744, 30122, 27312, 33134, 32962, 23140, 25009, 35539, 27687, 16080, 40634, 29479, 18687, 19259, 33598, 
					26802, 30824, 18843, 36203, 21314, 12352, 17700, 16691, 22330, 16448, 36156, 23859, 37160, 35592, 49037, 26412, 31175, 
					16490, 22537, 15737, 17445, 16162, 20077, 18171, 21112, 26631, 33416, 38676, 61004, 19545, 24740, 35285, 25807, 24089, 
					29560, 42077, 60871, 31231, 15173, 19343]
	Percentile90R6 = percentile(debug, AnnualRunoff, 0.90, 'R-6')
	Percentile50R6 = percentile(debug, AnnualRunoff, 0.50, 'R-6')
	Percentile10R6 = percentile(debug, AnnualRunoff, 0.10, 'R-6')
	Percentile90R7 = percentile(debug, AnnualRunoff, 0.90, 'R-7')
	Percentile50R7 = percentile(debug, AnnualRunoff, 0.50, 'R-7')
	Percentile10R7 = percentile(debug, AnnualRunoff, 0.10, 'R-7')
	outputDebug(debug, lineNo(), CurrentHecTime.dateAndTime(4), ' Pass Number %d:\t' % PassNumber, 
			'\n\t\t\t90th Percentile (R6) = %.0f MAF' % Percentile90R6,
			'\n\t\t\t50th Percentile (R6) = %.0f MAF' % Percentile50R6,
			'\n\t\t\t10th Percentile (R6) = %.0f MAF' % Percentile10R6,
			'\n\t\t\t90th Percentile (R7) = %.0f MAF' % Percentile90R7,
			'\n\t\t\t50th Percentile (R7) = %.0f MAF' % Percentile50R7,
			'\n\t\t\t10th Percentile (R7) = %.0f MAF' % Percentile10R7)

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, 0)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue

#==============================================================================

Console Output:
Debug Line 110   |	01Mar1930, 24:00 Pass Number 1:	
					90th Percentile (R6) = 35387 MAF
					50th Percentile (R6) = 24653 MAF
					10th Percentile (R6) = 16129 MAF
					90th Percentile (R7) = 35128 MAF
					50th Percentile (R7) = 24653 MAF
					10th Percentile (R7) = 16261 MAF
 
PY


Description: routeFlow is used to route flow based on a coefficient routing method. To ensure the same routing parameters are used in the script as the network, routing coefficients are retrieved from the network in the initRuleScript portion and stored to a dictionary. In this example, releaess from FTPK Dam are routed downstream to WPMT. Routing coefficients are assigned to the FTPK Dam to Milk-MR JCT but the coefficients represent the routing of flow from FTPK Dam to WPMT. If routing coefficients existed for the Milk-MR JCT to WPMT reach, the example script would have continue routing the flow until it reached the next downstream junction of choice. Note in the console output section that the first 2 days printed have 0 flow. With most routing, past data is required to ensure that the routed flow is complete. This routing reach has 3 routing coefficients so 2 previous days of flow (27Feb and 28Feb) are required so the routed flow on the current timestep is not missing flows. The routeFlow function will replace these incomplete flow values with 0 so the user knows that flow is incomplete.

routeFlow Example

'''
Author: Ryan Larsen
Date: 12-13-2022
Description: Examples of functions within the ResSimUtils.py file
'''
# required imports to create the OpValue return object.
from hec.heclib.util	import HecTime
from hec.rss.model 		import OpValue
from hec.rss.model 		import OpRule
from hec.script 		import Constants, ResSim
import sys, os, calendar

#
# initialization function. optional.
#
# set up tables and other things that only need to be performed once during
# the compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
#
#
def initRuleScript(currentRule, network):
	#
	# Load the ResSimUtils module
	#
	wsDir = ResSim.getWatershed().getWorkspacePath()
	sharedDir = os.path.normpath(os.path.join(wsDir, "shared"))
	if sharedDir not in sys.path : sys.path.append(sharedDir)
	import ResSimUtils
	reload(ResSimUtils)
	from ResSimUtils import outputDebug, lineNo, routeFlow

	#
	# Input data
	#
	# Set debug = True to print all debug statements and = False to turn them off
	debug = True
	
	#
	# Retrieve network info
	#
	# Initialize NetworkData dictionary and save reservoir names
	NetworkData = {}
	NetworkData.setdefault('ReservoirData', {})
	NetworkData.setdefault('ReachData', {})
	NetworkData.setdefault('JunctionData', {})
	ReservoirNames = network.getReservoirNames()
	ReservoirNames = [str(name) for name in ReservoirNames]
	outputDebug(debug, lineNo(), 'ReservoirNames = ', ReservoirNames)
	for ReservoirName in ReservoirNames : 
		ReservoirElement = network.findReservoir(ReservoirName)
		DsElements = network.getDownstreamElements(ReservoirElement)
		DcpId = str(DsElements[0].getName()[ : 4])
		NetworkData['ReservoirData'].setdefault(DcpId, {}).setdefault('ReservoirName', ReservoirName)
		NetworkData['ReservoirData'][DcpId]['ReservoirElement'] = ReservoirElement
		NetworkData['ReservoirData'][DcpId]['StorageElement'] = ReservoirElement.getStorageFunction()
		NetworkData['ReservoirData'][DcpId]['DsElements'] = DsElements
		outputDebug(debug, lineNo(), '%s Reservoir Name = %s' % (DcpId, NetworkData['ReservoirData'][DcpId]['ReservoirName']))
	
	CurrentAlt = network.getAlternative()
	outputDebug(debug, lineNo(), 'CurrentAlt = ', CurrentAlt)
	JunctionNames = network.getJunctionNames()
	JunctionNames = [str(name) for name in JunctionNames]
	outputDebug(debug, lineNo(), 'JunctionNames = ', JunctionNames)
	for JunctionName in JunctionNames :
		JunctionNameParts = JunctionName.split('_')
		if len(JunctionNameParts[0]) > 4 :
			JunctionNameParts = JunctionName.split('-')
		DcpId = str(JunctionNameParts[0])
		JunctionElement = network.findJunction(JunctionName)
		
		# Find the most upstream junction and save to dictionary
		try : NetworkData['JunctionData']['MostUsJunction']
		except : NetworkData['JunctionData'].setdefault('MostUsJunction', JunctionName)
		if NetworkData['JunctionData']['MostUsJunction'] != JunctionName :
			CurrentDownstreamElements = network.getDownstreamElements(JunctionElement)
			MostUsJunctionElement = network.findJunction(NetworkData['JunctionData']['MostUsJunction'])
			MostUsJunctionDownstreamElements = network.getDownstreamElements(MostUsJunctionElement)
			if len(MostUsJunctionDownstreamElements) < len(CurrentDownstreamElements) : 
				NetworkData['JunctionData']['MostUsJunction'] = JunctionName
		
		LocalFlowTs = JunctionElement.getLocalFlowTimeSeries()
		for Ts in LocalFlowTs :
			TsName = Ts.getName()
			# Remove ~Exx from the name. The depletions names come with ~Exx at the beginning of the name (i.e. ~E70~BIS_Depletions_Present_Incremental)
			TsNameParts = TsName.split('~')
			TsName = TsNameParts[-1]
			LocationInflowFactor = CurrentAlt.getLocationInflowFactor(JunctionElement, TsName)
			outputDebug(False, lineNo(), '%s LocationInflowFactor for ' % (JunctionName), TsName, ' :', LocationInflowFactor)
			if LocationInflowFactor > 0 :
				outputDebug(False, lineNo(), 'Add %s to dictionary from junction %s. DcpId is %s' % (TsName, JunctionName, DcpId))
				try : NetworkData['JunctionData'][DcpId]['LocalFlow'].append((TsName, LocationInflowFactor))
				except : NetworkData['JunctionData'].setdefault(DcpId, {}).setdefault('LocalFlow', [(TsName, LocationInflowFactor)])
				NetworkData['JunctionData'][DcpId]['JunctionName'] = JunctionName
		
				# Store downstream elements to NetworkData
				DsElements = network.getDownstreamElements(JunctionElement)
				NetworkData['JunctionData'][DcpId].setdefault('DsElements', DsElements)

	# Find routing coefficients for each reservoir routing reach
	# Get the routing coefficents for each routing reach downstream of project
	JunctionKeys = NetworkData['JunctionData'].keys()
	for DcpId in JunctionKeys :
		if DcpId == 'MostUsJunction' : pass
		else : 
			DsElements = NetworkData['JunctionData'][DcpId]['DsElements']
			try : NetworkData['ReachData'][DcpId].setdefault('CoeffList', [])
			except : NetworkData['ReachData'].setdefault(DcpId, {}).setdefault('CoeffList', [])
			
			for element in DsElements :
				outputDebug(debug, lineNo(), 'Element = ', element.getName())
				try : Reach = network.findReach(element.getName())
				except : pass
				
				if Reach :
					ReachRouting = Reach.getFunction()
					try : 
						RoutingCoeff = ReachRouting.getCoefArray()
						RoutingCoeff = [float(coeff) for coeff in RoutingCoeff]
						NetworkData['ReachData'][DcpId]['CoeffList'].append(RoutingCoeff)
					except : pass
				
				# Break loop if the element is the downstream reservoir
				DsElementDcpId = str(element.getName()[ : 4])
				if DsElementDcpId in JunctionKeys and DsElementDcpId != DcpId : break

	# -------------------------------------------------------------------
	# init info that is passed to runRuleScript()
	# -------------------------------------------------------------------
	InitInfo = {
					'debug'			: debug,
					'lineNo'		: lineNo,
					'NetworkData'	: NetworkData,
					'outputDebug'	: outputDebug,
					'routeFlow'		: routeFlow
				}
	currentRule.varPut('InitInfo', InitInfo)
	

	# return Constants.TRUE if the initialization is successful
	# and Constants.FALSE if it failed.  Returning Constants.FALSE
	# will halt the compute.
	return Constants.TRUE


# runRuleScript() is the entry point that is called during the
# compute.
#
# currentRule is the rule that holds this script
# network is the ResSim network
# currentRuntimestep is the current Run Time Step
def runRuleScript(currentRule, network, currentRuntimestep):

	# -------------------------------------------------------------------
	# Retrieve init info from initRuleScript() and set equal to variables
	# -------------------------------------------------------------------
	
	InitInfo = currentRule.varGet('InitInfo')
	
	debug			= InitInfo['debug']
	lineNo			= InitInfo['lineNo']
	NetworkData		= InitInfo['NetworkData']
	outputDebug		= InitInfo['outputDebug']
	routeFlow		= InitInfo['routeFlow']

	# create new Operation Value (OpValue) to return
	opValue = OpValue()

	# -------------------------------------------------------------------
	# Input Data
	# -------------------------------------------------------------------
	
	# Simulation Time & Step Info
	Rtw				= currentRuntimestep.getRunTimeWindow()  # Run time window
	Year			= currentRuntimestep.getHecTime().year()  # Current year
	Month			= currentRuntimestep.getHecTime().month()  # Current month
	Day				= currentRuntimestep.getHecTime().day()  # Current day
	MonthAbbr		= calendar.month_abbr[Month]
	CurrentHecTime	= HecTime(); CurrentHecTime.set('%02d%s%d 2400' % (Day, MonthAbbr, Year)) # Current HecTime
	PassCounter		= network.getComputePassCounter() # Pass number of simulation
	PassNumber		= PassCounter + 1 # Pass number starts at 0 so add 1 to start it at 1

	# Example Calculation
	# Forecast Days
	FcstDays = 14

	# Routing coefficients
	FtpkToWpmtRoutingCoeffs	= NetworkData['ReachData']['FTPK']['CoeffList']
	
	# Lag for each reach
	FtpkToWpmtLag = 0 # Lag from FTPK to WPMT
	for x in range(len(FtpkToWpmtRoutingCoeffs)) :
		FtpkToWpmtLag += (len(FtpkToWpmtRoutingCoeffs[x]) - 1)

	# List of HecTimes for forecast period
	HecTimes = []
	for x in range(-FtpkToWpmtLag, FcstDays + FtpkToWpmtLag, 1) :
		hecTime = HecTime(); hecTime.set(CurrentHecTime.dateAndTime(4))
		hecTime.addDays(x)
		HecTimes.append(hecTime)
	
	FtpkPreviousRelease = 10000.
	FtpkFcstReleases = [12000.] * FcstDays
	for x in range(5, FcstDays, 1) :
		if x <= 11 : FtpkFcstReleases[x] = FtpkFcstReleases[x - 1] + 2000.
		else : FtpkFcstReleases[x] = FtpkFcstReleases[x - 1]
	# Insert previous FTPK releases into FtpkFcstReleases. Previous releases are needed because routed flows for the first few days will be 
	#	missing flows.
	for x in range(1, FtpkToWpmtLag + 1) :
		FtpkFcstReleases.insert(0, FtpkPreviousRelease)

	# Route flows from FTPK to WPMT
	RoutedFtpkRelease = FtpkFcstReleases[:]
	for ReachNumber in range(len(FtpkToWpmtRoutingCoeffs)) :
		ReachCoeffList = FtpkToWpmtRoutingCoeffs[ReachNumber] # If there are multiple reaches with routing parameters, select the routing parameters 
															 #	for each reach and route flow from upstream reservoir to downstream reservoir
		outputDebug(debug, lineNo(), 'Reach No %d Routing Coefficients: ' % (ReachNumber + 1), ReachCoeffList)
		for x in range(len(ReachCoeffList)) :
			if x == 0 : RoutingDay = 'Day n'
			else : RoutingDay = 'Day n-%d' % x
			outputDebug(debug, lineNo(), '%10s: %.3f' % (RoutingDay, ReachCoeffList[x]))
		RoutedFtpkRelease = routeFlow(ReachCoeffList, RoutedFtpkRelease)

	DebugStatement = '\n%22s%22s%22s%22s' % (
							(' ' * 22), 'Date', 'FTPK Fcst Release', 'FTPK Routed Release'
							)
	DebugStatement += '\n%22s%22s%22s%22s' % (
							(' ' * 22), (' ' * 22), 'cfs', 'cfs'
							)
	for x in range(len(FtpkFcstReleases)) :
		DebugStatement += '\n%22s%22s%22s%22s' % (
							'', HecTimes[x].dateAndTime(4), '%.0f' % FtpkFcstReleases[x], '%.0f' % RoutedFtpkRelease[x]) 
	outputDebug(debug, lineNo(), DebugStatement)

	# set type and value for OpValue
	#  type is one of:
	#  OpRule.RULETYPE_MAX  - maximum flow
	#  OpRule.RULETYPE_MIN  - minimum flow
	#  OpRule.RULETYPE_SPEC - specified flow
	opValue.init(OpRule.RULETYPE_MIN, 0)

	# return the Operation Value.
	# return "None" to have no effect on the compute
	return opValue

#==============================================================================

Console Output:
Debug Line 218   |	Reach No 1 Routing Coefficients: [0.103, 0.659, 0.238]
Debug Line 222   |	     Day n: 0.103
Debug Line 222   |	   Day n-1: 0.659
Debug Line 222   |	   Day n-2: 0.238
Debug Line 234   |	
                                        Date     FTPK Fcst Release   FTPK Routed Release
                                                               cfs                   cfs
                            27Feb1930, 24:00                 10000                     0
                            28Feb1930, 24:00                 10000                     0
                            01Mar1930, 24:00                 12000                 10206
                            02Mar1930, 24:00                 12000                 11524
                            03Mar1930, 24:00                 12000                 12000
                            04Mar1930, 24:00                 12000                 12000
                            05Mar1930, 24:00                 12000                 12000
                            06Mar1930, 24:00                 14000                 12206
                            07Mar1930, 24:00                 16000                 13730
                            08Mar1930, 24:00                 18000                 15730
                            09Mar1930, 24:00                 20000                 17730
                            10Mar1930, 24:00                 22000                 19730
                            11Mar1930, 24:00                 24000                 21730
                            12Mar1930, 24:00                 26000                 23730
                            13Mar1930, 24:00                 26000                 25524
                            14Mar1930, 24:00                 26000                 26000 
PY