Prado EFO PVA
rule_fcst.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 """
3 Created on Mon May 18 19:48:52 2020
4 
5 @author: cd
6 """
7 
8 
9 import abc
10 import numpy as np
11 from efo.rule import RuleBase, RuleRlsSpecified
12 from efo.junction import ReservoirJunction
13 # import sys
14 
15 # TODO: Numpy array variable should be passed as reference or pointer
16 # TODO: Function arguments should be labeled "ref" or "ptr" at beginning for clarification
17 # TODO: Function arguments that are array copies should be labeled "cpy"
18 # TODO: Should forecast rules also return a release schedule?
19 
20 
21 class RuleFcstBase(RuleBase):
22  def __init__(self, name, timeFcst):
23  # Call super class constructor
24  super().__init__(name, timeFcst)
25  # self.T = timeFcst
26  self.releaserelease = np.full(self.T.Tcont.nSteps, np.nan)
27  self.tsRlstsRls = np.full(self.T.Tcont.nSteps, int(0))
28  self.releaseSchedreleaseSched = np.empty(self.T.nSteps)
29 
30  def calc_release(self, rlsProposed, rlsUnCtrl, stor, rlsPrev=None, qIn=None):
31  rlsProposed = super().calc_release(
32  rlsProposed, rlsPrev=rlsPrev, rlsUnCtrl=rlsUnCtrl, stor=stor, qIn=qIn)
33  # TODO: Maybe create a property in TimeFcst that sets the current forecast datetime
34  # ...indexing to the current continuous time step like done here
35  # TODO: Maybe use the fcstDate property of TimeFcst here instead of dateTime[1]
36  # if self.T.Tcont.curDT == self.T.tFcst[self.T.Tcont.step].dateTime[1]:
37  # if self.T.Tcont.curDT == self.T.fcstDate[self.T.Tcont.step]:
38  # TODO: Mabe add a function in TimeFcst that checks the current forecast date
39  if np.any(self.T.fcstDate == self.T.Tcont.curDT):
40  self.releaseSchedreleaseSched = self.calc_release_fcstcalc_release_fcst(rlsProposed, rlsUnCtrl, stor, qIn)
41  return max(rlsProposed, self.releaseSchedreleaseSched[1].item())
42  else:
43  # iCurTsFcst = self.T.curFcstT.dateTime == self.T.Tcont.curDT
44  # return self.releaseSched[iCurTsFcst][0] if np.any(iCurTsFcst[1:]) else 0
45  iFcstTs = self.T.get_fcst_step()
46  rlsCur = self.releaseSchedreleaseSched[iFcstTs].item() if iFcstTs else 0.
47  return max(rlsProposed, rlsCur)
48 
49  # TODO: Should this include rlsPrev?
50  # TODO: Should this pass a proposed release schedule?
51  @abc.abstractmethod
52  def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor, qIn):
53  # TODO: This should return a tuple that has the release for the current time step
54  # but also the release schedule and possilby the max and min?
55  # TODO: In the world of OOP this should be a protected method because we only
56  # want to call this from other forecast rules, but in python not sure.
57  pass
58 
59  @classmethod
60  def __subclasshook__(cls, C):
61  if cls is RuleFcstBase:
62  attrs = set(dir(C))
63  if set(cls.__abstractmethods__) <= attrs:
64  return True
65  return NotImplemented
66 
67 
69  def __init__(self, name, timeFcst, efoResrvr, storTh, riskTol, constants, *,
70  efoNetwork=None, ruleRlsSpecified=None, efoRlsSched=None,
71  maxItr=100, storMax=None, nHrsLead=None):
72  # Call super class constructor
73  super().__init__(name, timeFcst)
74  # Set sub class properties
75  self.efoNetworkefoNetwork = efoNetwork
76  self.efoResrvrefoResrvr = efoResrvr
77  self.ruleRlsSpecifiedruleRlsSpecified = ruleRlsSpecified
78  self.efoRlsSchedefoRlsSched = efoRlsSched
79  self.maxItrmaxItr = maxItr
80  self.nMembersnMembers = len(efoResrvr)
81  # TODO: This shoud be a storage guide curve rule for flexibility
82  self.storThstorTh = float(storTh)
83  # TODO: Why is riskTol a 2D array?
84  # TODO: Check to make sure that have enough risk tol lead for forecast lead
85  self.riskTolriskTol = riskTol
86  self.constantsconstants = constants
87  self.mbrRlsmbrRls = np.full(self.T.Tcont.nSteps, int(0))
88  # TODO: This should probably be nHoursLead then calc the number of steps
89  self.nStepsLeadnStepsLead = int(nHrsLead/timeFcst.nHrs) + 1 if nHrsLead else timeFcst.nSteps
90  # TODO: This value could also be pulled from the efoResrvr
91  self.storMaxstorMax = storMax if storMax else storTh
92  # if __name__ == "__main__": pool = mp.Pool(mp.cpu_count())
93  # self.pool = mp.Pool(mp.cpu_count())
94 
95  def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor, qIn=None):
96  # Set initial conditions of the ensemble networks to the current observed
97  # TODO: Would it make sense to initialize the big class arrays with the constructor for performance?
98  # TODO: Need to create efo network that has the primary network and the ensembles
99  # TODO: Should this have max and min release properties for easier access for results?
100  # TODO: This could be replaced with the observer pattern to update forecast network
101  if self.efoNetworkefoNetwork is not None:
102  for net in self.efoNetworkefoNetwork: net.set_init_cond()
103  # Initialize variables
104  t = self.T.get_fcst_time()
105  # self.storFcst = np.empty(t.nSteps, self.nMembers, t.nSteps - 1)
106  # self.storFcst[:] = np.nan
107  # TODO: These should be set as references to the enemble reservoirs
108  self.storFcststorFcst = np.full((t.nSteps, self.nMembersnMembers, t.nSteps-1), np.nan)
109  # self.storFcst[0, :, :] = [curRes.stor[0] for curRes in self.efoResrvr]
110  self.storFcststorFcst[0, :, :] = self.efoResrvrefoResrvr[0].stor[0]
111  self.storFcst_preRlsstorFcst_preRls = self.storFcststorFcst.copy()
112  # self.rlsFcst = np.full((t.nSteps, self.nMembers, t.nSteps - 1), np.nan)
113  # TODO: If your efo reservoir does not have an uncontrolled outlet then you don't need these variables
114  # TODO: These should be set as references to the enemble reservoirs
115  self.rlsUnCtrlFcstrlsUnCtrlFcst = np.full((t.nSteps, self.nMembersnMembers, t.nSteps-1), np.nan)
116  if self.efoResrvrefoResrvr[0].outletUnCtrl:
117  self.rlsUnCtrlFcstrlsUnCtrlFcst[0, :, :] = self.efoResrvrefoResrvr[0].rlsUnCtrl[0]
118  else:
119  self.rlsUnCtrlFcstrlsUnCtrlFcst[0, :, :] = 0.
120  self.rlsUnCtrlFcst_preRlsrlsUnCtrlFcst_preRls = self.rlsUnCtrlFcstrlsUnCtrlFcst.copy()
121  self.rlsFcstrlsFcst = np.zeros((t.nSteps, self.nMembersnMembers, t.nSteps-1))
122  for i in range(t.nSteps-1):
123  self.rlsFcstrlsFcst[i+2:, 0:self.nMembersnMembers, i] = np.nan
124  # self.rlsFcst[0, :, :] = [curRes.rlsCtrl[0] for curRes in self.efoResrvr]
125  # TODO: Maybe use the passed rlsPrev here?
126  self.rlsFcstrlsFcst[0, :, :] = self.efoResrvrefoResrvr[0].rlsCtrl[0]
127  self.rlsMaxrlsMax = np.full((t.nSteps, self.nMembersnMembers, t.nSteps-1), np.nan)
128  self.rlsNoConstraintrlsNoConstraint = np.copy(self.rlsFcstrlsFcst)
129  self.prExcThprExcTh = np.full(t.nSteps, np.nan)
130  # self.prExcTh_preRls = np.copy(self.prExcTh)
131  self.prExcTh_preRlsprExcTh_preRls = np.full((t.nSteps, t.nSteps-1), np.nan)
132  self.iRiskyMbrsiRiskyMbrs = np.full((t.nSteps, self.nMembersnMembers), False)
133  riskySelect = np.arange(0, self.nMembersnMembers, dtype=int)
134  riskyMbr = np.copy(riskySelect)
135  iRisky = np.full(self.nMembersnMembers, False)
136  iRiskyChk = np.full(self.nMembersnMembers, True)
137  volAbvThresh = np.zeros([t.nSteps, self.nMembersnMembers])
138  vol2Rls = np.zeros([t.nSteps, self.nMembersnMembers])
139  rlsFcst = np.full(t.nSteps, np.nan)
140  rlsFcst[0] = self.efoResrvrefoResrvr[0].rlsCtrl[0]
141  rlsMax = np.full(t.nSteps, np.nan)
142  rlsToday = np.empty((t.nSteps-1, self.nMembersnMembers))
143  rlsSched = np.zeros(t.nSteps)
144  for tsHoriz in t.steps[1:self.nStepsLeadnStepsLead]:
145  self.T.step = tsHoriz
146  if tsHoriz > 1:
147  self.storFcststorFcst[:, :, tsHoriz-1] = self.storFcststorFcst[:, :, tsHoriz-2]
148  self.rlsUnCtrlFcstrlsUnCtrlFcst[:, :, tsHoriz-1] = self.rlsUnCtrlFcstrlsUnCtrlFcst[:, :, tsHoriz-2]
149  # for j in range(self.nMembers):
150  for curMbr in riskySelect:
151  # TODO: Maybe shouldn't set the override to 0 here?
152  if issubclass(type(self.ruleRlsSpecifiedruleRlsSpecified[curMbr]), RuleRlsSpecified):
153  self.ruleRlsSpecifiedruleRlsSpecified[curMbr].set_release(tsHoriz, 0.)
154  self.efoResrvrefoResrvr[curMbr].calc_qout()
155  # TODO: In a network this should just process up to the efoReseroir...
156  # TODO: and not points downstream to save compute time
157  # self.storFcst[tsHoriz, curMbr, tsHoriz-1] = np.copy(self.efoResrvr[curMbr].stor[tsHoriz])
158  self.storFcststorFcst[tsHoriz, curMbr, tsHoriz-1] = self.efoResrvrefoResrvr[curMbr].stor[tsHoriz].item()
159  if self.efoResrvrefoResrvr[curMbr].outletUnCtrl:
160  # self.rlsUnCtrlFcst[tsHoriz, curMbr, tsHoriz-1] = np.copy(self.efoResrvr[curMbr].rlsUnCtrl[tsHoriz])
161  self.rlsUnCtrlFcstrlsUnCtrlFcst[tsHoriz, curMbr, tsHoriz-1] = self.efoResrvrefoResrvr[curMbr].rlsUnCtrl[tsHoriz].item()
162  else:
163  self.rlsUnCtrlFcstrlsUnCtrlFcst[tsHoriz, curMbr, tsHoriz-1] = 0.
164  self.storFcst_preRlsstorFcst_preRls[:, :, tsHoriz-1] = self.storFcststorFcst[:, :, tsHoriz-1].copy()
165  self.rlsUnCtrlFcst_preRlsrlsUnCtrlFcst_preRls[:, :, tsHoriz-1] = self.rlsUnCtrlFcstrlsUnCtrlFcst[:, :, tsHoriz-1].copy()
166  risky = True
167  while risky:
168  storPlusUnCtrl = self.storFcststorFcst[tsHoriz, :, tsHoriz-1]\
169  + self.rlsUnCtrlFcstrlsUnCtrlFcst[tsHoriz, :, tsHoriz-1]*self.constantsconstants.cfs2af
170  iRisky = (storPlusUnCtrl > self.storThstorTh) & iRiskyChk
171  self.prExcThprExcTh[tsHoriz] = np.sum(iRisky)/self.nMembersnMembers
172  if self.prExcThprExcTh[tsHoriz] - self.riskTolriskTol[tsHoriz] > 1/self.nMembersnMembers/2:
173  # if self.T.Tcont.step == 240:
174  # print(tsHoriz)
175  # sys.exit("ts = " + str(tsHoriz))
176  self.prExcTh_preRlsprExcTh_preRls[:, tsHoriz-1] = self.prExcThprExcTh.copy()
177  iRiskyChk = np.copy(iRisky)
178  # riskyMbr = np.where(self.storFcst[tsHoriz, :, tsHoriz-1] > self.storTh)[0]
179  riskyMbr = np.where(iRisky)[0]
180  nMbrs2Reduce = int(len(riskyMbr) - np.floor(self.riskTolriskTol[tsHoriz]*self.nMembersnMembers))
181  volAbvThresh[tsHoriz, riskyMbr] = \
182  self.storFcststorFcst[tsHoriz, riskyMbr, tsHoriz-1]\
183  + sum(self.rlsUnCtrlFcstrlsUnCtrlFcst[1:tsHoriz+1, riskyMbr, tsHoriz-1])*self.constantsconstants.cfs2af\
184  - self.storThstorTh
185  mbrSorted = np.argsort(volAbvThresh[tsHoriz, riskyMbr])
186  riskySelect = riskyMbr[mbrSorted[0:nMbrs2Reduce]]
187  self.iRiskyMbrsiRiskyMbrs[tsHoriz, riskySelect] = True
188  # TODO: Should add a while loop so you can account for evaportion iterations
189  # TODO: This percent increase (1.01) should be a property
190  # vol2Rls[tsHoriz, riskySelect] = \
191  # 1.01*(self.storFcst[tsHoriz, riskySelect, tsHoriz-1] - self.storTh)
192  vol2Rls[tsHoriz, riskySelect] = 1.01*volAbvThresh[tsHoriz, riskySelect]
193  tasks = []
194  # for j in range(len(riskySelect)):
195  for curMbr in riskySelect:
196  # print((tsHoriz, curMbr))
197  rlsFcst[1:tsHoriz+1] = np.sum(
198  vol2Rls[:, curMbr])*self.constantsconstants.af2cfs/tsHoriz
199  self.rlsNoConstraintrlsNoConstraint[1:tsHoriz+1, curMbr, tsHoriz-1] = np.copy(rlsFcst[1:tsHoriz+1])
200  rlsFcst[1:tsHoriz+1], rlsMax[1:tsHoriz+1] = self._calc_release_sched_calc_release_sched(
201  rlsFcst[1:tsHoriz+1], tsHoriz, curMbr)
202  self.rlsMaxrlsMax[1:tsHoriz+1, curMbr, tsHoriz-1] = np.copy(rlsMax[1:tsHoriz+1])
203  if np.sum(vol2Rls[:, curMbr]) - np.sum(rlsFcst[1:tsHoriz+1])*self.constantsconstants.cfs2af > 0.01:
204  iRiskyChk[curMbr] = False
205  # vol2Rls[tsHoriz, curMbr] = \
206  # vol2Rls[tsHoriz, curMbr] - \
207  # (np.sum(vol2Rls[:, curMbr]) - np.sum(rlsFcst[1:tsHoriz+1])*self.constants.cfs2af)
208  vol2Rls[tsHoriz, curMbr] -= \
209  (np.sum(vol2Rls[:, curMbr]) - np.sum(rlsFcst[1:tsHoriz+1])*self.constantsconstants.cfs2af)\
210  - np.sum(self.rlsUnCtrlFcstrlsUnCtrlFcst[1:tsHoriz+1, curMbr, tsHoriz-1])
211  self.rlsFcstrlsFcst[1:tsHoriz+1, curMbr, tsHoriz-1] = np.copy(rlsFcst[1:tsHoriz+1])
212  # print(tsHoriz)
213  risky = True
214  else:
215  if np.isnan(self.prExcTh_preRlsprExcTh_preRls[tsHoriz, tsHoriz-1]):
216  self.prExcTh_preRlsprExcTh_preRls[:, tsHoriz-1] = self.prExcThprExcTh.copy()
217  # TODO: Create a function that initializes these variables, would have to return...
218  # TODO: a tuple of multiple variables. Not sure this would save any code redundancy
219  riskySelect = np.arange(0, self.nMembersnMembers, dtype=int)
220  riskyMbr = np.copy(riskySelect)
221  iRiskyChk = np.full(self.nMembersnMembers, True)
222  risky = False
223  # rlsToday[:, :] = self.rlsFcst[1, :, :].T
224  # rlsToday[:, :] = np.nansum(self.rlsFcst[1:int(self.T.fcstFreq/self.T.nHrs)+1, :, :], axis=0).T
225  rlsToday[:, :] = np.nanmean(self.rlsFcstrlsFcst[1:int(self.T.fcstFreq/self.T.nHrs)+1, :, :], axis=0).T
226  if np.any(rlsToday[:] > 0.):
227  # TODO: You could have a count of the number of ens mbrs and ts that have the same day 1 release
228  # TODO: This could be used to provide multiple release schedules
229  # TODO: Needs to check if there is a spill in the 24 hour horizon and if so
230  # ...go with the max release to prevent this spill
231  tsMax, mbrMax = np.where(rlsToday==np.max(rlsToday))
232  # TODO: The time step of release should be the max of the maxes
233  # TODO: to do this you have to look out to the min horiz of all of the tsMax...
234  # and then determine which of those has the max total release. Tricky!
235  # TODO: Check if tsRls considers 0 hr - it shouldn't and if so fix it
236  # TODO: the selected release schedule should secondarily priority which...
237  # ...release has the highest time step average
238  self.tsRlstsRls[self.T.Tcont.step] = int(tsMax[0])
239  self.mbrRlsmbrRls[self.T.Tcont.step] = int(mbrMax[0])
240  # TODO: Does this need to be set since you return the release schedule and it is reset by super class?
241  self.releaserelease[self.T.Tcont.step] = self.rlsFcstrlsFcst[1, mbrMax[0], tsMax[0]]
242  rlsSched = self.rlsFcstrlsFcst[:, mbrMax[0], tsMax[0]]
243  # Fill in the period to next Fcst if needed
244  if np.any(np.isnan(rlsSched[:int(self.T.fcstFreq/self.T.nHrs)+1])):
245  tsLastRls = np.where(np.isnan(rlsSched))[0][0]
246  rlsSched[tsLastRls:int(self.T.fcstFreq/self.T.nHrs)+1] = rlsSched[tsLastRls - 1]
247  else:
248  # TODO: Does this need to be set since you return the release schedule and it is reset by super class?
249  self.releaserelease[self.T.Tcont.step] = 0.
250  # rlsSched = np.zeros(t.nSteps)
251  return rlsSched
252 
253  def _calc_release_sched(self, rlsFcst, tsHoriz, ensMbr):
254  # TODO: This should start at ts 0 instead of 1 in case there is dependency on ts 0 for future applications
255  # pctConvg = 0.005
256  # rlsTot = np.nansum(rlsFcst)
257  # maxItr = 10
258  curItr = 0.
259  # Create reference variables to ensemble reservoir objects
260  rlsMax = np.copy(self.efoResrvrefoResrvr[ensMbr].rlsMax[1:tsHoriz+1])
261  rlsCtrlApplied = self.efoResrvrefoResrvr[ensMbr].rlsCtrl[1:tsHoriz+1]
262  storFcst = self.storFcststorFcst[1:tsHoriz+1, ensMbr, tsHoriz-1]
263  rlsUnCtrlFcst = self.rlsUnCtrlFcstrlsUnCtrlFcst[1:tsHoriz+1, ensMbr, tsHoriz-1]
264  # rlsMaxPrev = np.zeros(len(rlsMax))
265  rlsFcstInit = np.copy(rlsFcst)
266  rlsFcstPrev = np.array([np.copy(rlsFcst)])
267  while True:
268  # Recaculate storage
269  for i in range(1, tsHoriz+1):
270  if issubclass(type(self.efoResrvrefoResrvr[ensMbr]), ReservoirJunction):
271  self.T.step = i
272  if issubclass(type(self.ruleRlsSpecifiedruleRlsSpecified[ensMbr]), RuleRlsSpecified):
273  # self.ruleRlsSpecified[ensMbr].set_release(i, np.copy(rlsFcst[i-1]))
274  self.ruleRlsSpecifiedruleRlsSpecified[ensMbr].set_release(i, rlsFcst[i-1].item())
275  # TODO: In a network this should just process up to the efoReseroir...
276  # TODO: and not points downstream to save compute time
277  # Set break at i==7
278  self.efoNetworkefoNetwork[ensMbr].process_fcst_junctions()
279  # TODO: Try to use the locally defined reference variables instead
280  rlsMax[i-1] = self.efoResrvrefoResrvr[ensMbr].rlsMax[i].item()
281  storFcst[i-1] = self.efoResrvrefoResrvr[ensMbr].stor[i].item()
282  # rlsFcst = np.copy(rlsCtrlApplied)
283  rlsFcst[i-1] = self.efoResrvrefoResrvr[ensMbr].rlsCtrl[i].item()
284  if self.efoResrvrefoResrvr[ensMbr].outletUnCtrl:
285  rlsUnCtrlFcst[i-1] = self.efoResrvrefoResrvr[ensMbr].rlsUnCtrl[i].item()
286  else:
287  rlsUnCtrlFcst[i-1] = 0.
288  # print(self.T.Tcont.step, tsHoriz, curItr)
289  if curItr == 0: rlsMaxPrev = np.copy(rlsMax)
290  if np.all(rlsFcstInit == rlsCtrlApplied, axis=0) or curItr >= self.maxItrmaxItr:
291  break
292  elif np.any(np.all(rlsFcstPrev == rlsCtrlApplied, axis=1)):
293  # rlsFcst = np.copy(rlsCtrlApplied)
294  # rlsMaxPrev = np.copy(rlsMax)
295  break
296  # Account for storage above spillway
297  # We're reducing the max release by the amount that exceeds our storage target
298  # so the release above max can be redistributed to another fcst timestep
299  # TODO: Not sure this totally makes sense because for a low storage threshold w/ multi-obj...
300  # ...you might frequently exceed the storTh but this would force you rlsMax to 0
301  # TODO: This concept works of you exceed you storMax in lead time, but doesn't make sense...
302  #... if you exceed it on lead 0
303  # TODO: In that case you need a storTh for management and a storMax to adjusting rlsMax
304  # TODO: Should this be accounted for if we are not considering uncontrolled releases?
305  # TODO: Check this with Lake Mendocino for really big events
306  # TODO: May want a separate variable that is the storage above the storMax that get's...
307  # ...redistributed differently
308  rlsMax2Spill = np.fmax(np.zeros(rlsFcst.size),
309  rlsCtrlApplied - (storFcst - self.storMaxstorMax)*self.constantsconstants.af2cfs - rlsUnCtrlFcst)
310  rlsMax = np.fmin(rlsMax, rlsMax2Spill)
311  # Get equally distributed release
312  rlsFcstPrev = np.vstack((rlsFcstPrev, np.copy(rlsCtrlApplied)))
313  rlsFcst = self._get_adjusted_release_eqldist_get_adjusted_release_eqldist(rlsFcstInit, rlsMax)
314  # zChk = np.array((storFcst, rlsFcst, rlsMax, rlsCtrlApplied)).T
315  rlsMaxPrev = np.copy(rlsMax)
316  curItr += 1
317  # return rlsFcst, rlsMaxPrev
318  return rlsFcst, rlsMax
319 
320  def old_calc_release_sched(self, rlsFcst, tsHoriz, ensMbr):
321  # TODO: This should start at ts 0 instead of 1 in case there is dependency on ts 0 for future applications
322  # TODO: Should this first calculate the zero release max to help reduce iterations?
323  pctConvg = 0.005
324  rlsTot = np.nansum(rlsFcst)
325  maxItr = 100
326  curItr = 0
327  # Create reference variables to ensemble reservoir objects
328  rlsMax = self.efoResrvrefoResrvr[ensMbr].rlsMax[1:tsHoriz+1]
329  rlsCtrlApplied = self.efoResrvrefoResrvr[ensMbr].rlsCtrl[1:tsHoriz+1]
330  storFcst = self.storFcststorFcst[1:tsHoriz+1, ensMbr, tsHoriz-1]
331  rlsUnCtrlFcst = self.rlsUnCtrlFcstrlsUnCtrlFcst[1:tsHoriz+1, ensMbr, tsHoriz-1]
332  rlsMaxPrev = np.zeros(len(rlsMax))
333  rlsFcstInit = np.copy(rlsFcst)
334  rlsFcstPrev = np.array([np.copy(rlsFcst)])
335  # rlsFcstPrev = np.empty([maxItr, rlsFcst.size])
336  # idxZero = 1
337  while True:
338  # Recaculate storage
339  for i in range(1, tsHoriz+1):
340  if issubclass(type(self.efoResrvrefoResrvr[ensMbr]), ReservoirJunction):
341  self.T.step = i
342  if issubclass(type(self.ruleRlsSpecifiedruleRlsSpecified[ensMbr]), RuleRlsSpecified):
343  self.ruleRlsSpecifiedruleRlsSpecified[ensMbr].set_release(i, np.copy(rlsFcst[i-1]))
344  # TODO: In a network this should just process up to the efoReseroir...
345  # TODO: and not points downstream to save compute time
346  self.efoNetworkefoNetwork[ensMbr].process_fcst_junctions()
347  # TODO: Use the locally defined reference variables instead
348  storFcst[i-1] = np.copy(self.efoResrvrefoResrvr[ensMbr].stor[i])
349  rlsUnCtrlFcst[i-1] = np.copy(self.efoResrvrefoResrvr[ensMbr].rlsUnCtrl[i])
350  # TODO: If you are maintaining forecasted storage levels then all future timesteps...
351  # TODO: should be at the max release schedule
352  if curItr == 0: rlsMaxPrev = np.copy(rlsMax)
353  if (rlsTot - np.sum(rlsCtrlApplied) < pctConvg*rlsTot and np.sum(rlsUnCtrlFcst)==0.)\
354  or curItr >= maxItr:
355  break
356  elif np.any(np.all(rlsFcstPrev == rlsCtrlApplied, axis=1)):
357  rlsFcst = np.copy(rlsCtrlApplied)
358  rlsMaxPrev = np.copy(rlsMax)
359  break
360  # zChk = np.array((rlsFcst, self.efoResrvr[ensMbr].rlsCtrl, self.storFcst[:, ensMbr, tsHoriz-1], rlsMax)).T
361  # Roll back releases above target
362  iAbvTarget = storFcst[:] > self.storThstorTh
363  iUnCtrl = rlsUnCtrlFcst[:] > 0.
364  # volAbvTarget = sum(self.storFcst[1:tsHoriz+1, ensMbr, tsHoriz-1] - self.storTh) + \
365  # sum(self.rlsUnCtrlFcst[1:tsHoriz+1, ensMbr, tsHoriz-1])*self.constants.cfs2af
366  rlsMax[iAbvTarget | iUnCtrl] = np.fmax(0., rlsCtrlApplied[iAbvTarget | iUnCtrl] \
367  - (storFcst[iAbvTarget | iUnCtrl] - self.storThstorTh)*self.constantsconstants.af2cfs \
368  - rlsUnCtrlFcst[iAbvTarget | iUnCtrl])
369  # Get equally distributed release
370  rlsFcstPrev = np.vstack((rlsFcstPrev, np.copy(rlsCtrlApplied)))
371  rlsFcst = self._get_adjusted_release_eqldist_get_adjusted_release_eqldist(rlsFcstInit, rlsMax)
372  rlsMaxPrev = np.copy(rlsMax)
373  curItr += 1
374  return rlsFcst, rlsMaxPrev
375 
376  def _get_adjusted_release_eqldist(self, rlsFcstInit, rlsMax):
377  rlsFcst = np.copy(rlsFcstInit)
378  rlsFcstCheck = np.zeros(len(rlsFcst))
379  iVal = ~np.isnan(rlsFcst) & ~np.isnan(rlsMax)
380  iMaxed = np.full(len(rlsFcst), False)
381  # iMaxed[0] = True
382  while any(iMaxed==False) and any(rlsFcst[iVal] - rlsFcstCheck[iVal] != 0.):
383  rlsFcstCheck = np.copy(rlsFcst)
384  iGrtrMax = rlsFcst > rlsMax
385  iMaxed = iMaxed | iGrtrMax
386  rlsDiffVol = np.sum(rlsFcst[iGrtrMax] - rlsMax[iGrtrMax])
387  rlsFcst[iGrtrMax] = rlsMax[iGrtrMax]
388  rlsAdd = rlsDiffVol/max(1,np.sum(~iMaxed & iVal))
389  rlsFcst[~iMaxed] += rlsAdd
390  return rlsFcst
391 
392  def _get_adjusted_release_rollback(self, rlsFcstInit, tsHoriz, rlsMax):
393  rlsFcst = np.copy(rlsFcstInit)
394  rlsFcstCheck = np.copy(rlsFcst)
395  carryOver = 0.
396  for i in range(tsHoriz, 0, -1):
397  rlsFcst[i] += carryOver
398  rlsFcstCheck[i] = rlsFcst[i]
399  if rlsFcst[i] > rlsMax[i]:
400  rlsFcst[i] = rlsMax[i]
401  # Readjust carryover
402  carryOver += (rlsFcstCheck[i] - rlsFcst[i])/(i-1) if i > 1 else 0.
403  return rlsFcst
404 
405 
407  def __init__(self, name, rulePfo, ruleEfoNoRls):
408  # Call super class constructor
409  super().__init__(name, rulePfo.T)
410  self.rulePforulePfo = rulePfo
411  self.ruleEfoNoRlsruleEfoNoRls = ruleEfoNoRls
412  self.riskEforiskEfo = np.full((self.T.Tcont.nSteps, self.T.nSteps), 0.)
413 
414  def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor, qIn=None):
415  zeroRls = self.ruleEfoNoRlsruleEfoNoRls.calc_release_fcst(rlsProposed, rlsUnCtrl, stor, qIn)
416  self.riskEforiskEfo[self.T.Tcont.step,1:] = self.ruleEfoNoRlsruleEfoNoRls.prExcTh[1:]
417  rlsSched = self.rulePforulePfo.calc_release_fcst(rlsProposed, rlsUnCtrl, stor, qIn)
418  # TODO: Does this need to be set since you return the release schedule and it is reset by super class?
419  self.releaserelease[self.T.Tcont.step] = rlsSched[1]
420  return rlsSched
421 
422 
424  def __init__(self, name, ruleEfo, ruleEfo_NoRls, tsArchive=0,
425  fileName='efoFcstResults', tsRlsOvrd=None, saveAllRlsSched=False):
426  # Call super class constructor
427  super().__init__(name, ruleEfo.T)
428  self.ruleEforuleEfo = ruleEfo
429  self.ruleEfo_NoRlsruleEfo_NoRls = ruleEfo_NoRls
430  self.tsArchivetsArchive = tsArchive
431  self.fileNamefileName = fileName
432  self.tsRlsOvrdtsRlsOvrd = tsRlsOvrd
433  self.SaveAllRlsSchedSaveAllRlsSched = saveAllRlsSched
434 
435  def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor, qIn=None):
436  rlsSched = self.ruleEforuleEfo.calc_release_fcst(rlsProposed, rlsUnCtrl, stor, qIn)
437  self.releaserelease[self.T.Tcont.step] = rlsSched[1]
438  # TODO: This should take multiple time steps and archive to a 3d array of results
439  if self.T.Tcont.step == self.tsArchivetsArchive:
440  zeroRls = self.ruleEfo_NoRlsruleEfo_NoRls.calc_release_fcst(rlsProposed, rlsUnCtrl, stor, qIn)
441  t = self.T.get_fcst_time()
442  self.tFcsttFcst = t
443  # self.tsRls = int(self.ruleEfo.tsRls[self.T.Tcont.step]) if self.tsRlsOvrd is None else int(self.tsRlsOvrd)
444  if self.tsRlsOvrdtsRlsOvrd:
445  self.tsRlstsRlstsRls = int(self.tsRlsOvrdtsRlsOvrd)
446  rlsToday = np.nanmean(
447  self.ruleEforuleEfo.rlsFcst[1:int(self.T.fcstFreq/self.T.nHrs)+1, :, :self.tsRlstsRlstsRls], axis=0).T
448  self.mbrRlsmbrRls = int(np.where(rlsToday==np.max(rlsToday))[1][0])
449  else:
450  self.tsRlstsRlstsRls = int(self.ruleEforuleEfo.tsRls[self.T.Tcont.step])
451  self.mbrRlsmbrRls = int(self.ruleEforuleEfo.mbrRls[self.T.Tcont.step])
452  self.storFcst_NoRlsstorFcst_NoRls = self.ruleEfo_NoRlsruleEfo_NoRls.storFcst[:, : , t.end-1]
453  self.rlsUnCtrlFcst_NoRlsrlsUnCtrlFcst_NoRls = self.ruleEfo_NoRlsruleEfo_NoRls.rlsUnCtrlFcst[:, : , t.end-1]
454  self.prExcTh_NoRlsprExcTh_NoRls = self.ruleEfo_NoRlsruleEfo_NoRls.prExcTh
455  self.riskTolriskTol = self.ruleEforuleEfo.riskTol
456  self.storFcststorFcst = self.ruleEforuleEfo.storFcst[:, : , t.end-1]
457  self.rlsUnCtrlFcstrlsUnCtrlFcst = self.ruleEforuleEfo.rlsUnCtrlFcst[:, : , t.end-1]
458  # self.rlsFcst = self.ruleEfo.rlsFcst[:, : , t.end-1]
459  self.releaseSchedreleaseSchedreleaseSched = rlsSched
460  self.prExcThprExcTh = self.ruleEforuleEfo.prExcTh
461  self.iRiskyMbrsAlliRiskyMbrsAll = np.any(self.ruleEforuleEfo.iRiskyMbrs, axis=0)
462  # Time step of release variables
463  # TODO: The NoRls variables are temporary to create the explanation figures, kinda silly to have
464  self.storFcst_NoRls_tsRlsstorFcst_NoRls_tsRls = self.ruleEfo_NoRlsruleEfo_NoRls.storFcst[:, : , self.tsRlstsRlstsRls]
465  self.rlsUnCtrlFcst_NoRls_tsRlsrlsUnCtrlFcst_NoRls_tsRls = self.ruleEfo_NoRlsruleEfo_NoRls.rlsUnCtrlFcst[:, : , self.tsRlstsRlstsRls]
466  self.rlsFcst_NoRls_tsRlsrlsFcst_NoRls_tsRls = self.ruleEfo_NoRlsruleEfo_NoRls.rlsFcst[:, : , self.tsRlstsRlstsRls]
467  self.storFcst_preRlsstorFcst_preRls = self.ruleEforuleEfo.storFcst_preRls[:, : , self.tsRlstsRlstsRls]
468  self.rlsUnCtrlFcst_preRlsrlsUnCtrlFcst_preRls = self.ruleEforuleEfo.rlsUnCtrlFcst_preRls[:, : , self.tsRlstsRlstsRls]
469  self.storFcst_tsRlsstorFcst_tsRls = self.ruleEforuleEfo.storFcst[:, :, self.tsRlstsRlstsRls]
470  self.rlsUnCtrlFcst_tsRlsrlsUnCtrlFcst_tsRls = self.ruleEforuleEfo.rlsUnCtrlFcst[:, : , self.tsRlstsRlstsRls]
471  self.iRiskyMbrs_AlliRiskyMbrs_All = np.any(self.ruleEforuleEfo.iRiskyMbrs, axis=0)
472  # TODO: Check if tsRls considers 0 hr
473  self.iRiskyMbrs_tsRlsiRiskyMbrs_tsRls = self.ruleEforuleEfo.iRiskyMbrs[self.tsRlstsRlstsRls+1, :].flatten()
474  self.iRiskyMbrs_tsRls_AllPreviRiskyMbrs_tsRls_AllPrev = np.any(self.ruleEforuleEfo.iRiskyMbrs[:self.tsRlstsRlstsRls+1, :], axis=1)
475  self.prExcTh_preRlsprExcTh_preRls = self.ruleEforuleEfo.prExcTh_preRls[:, self.tsRlstsRlstsRls]
476  # self.rlsFcst_tsRls = self.ruleEfo.rlsFcst[:, self.mbrRls, self.tsRls]
477  self.rlsFcst_tsRlsrlsFcst_tsRls = self.ruleEforuleEfo.rlsFcst[:, :, self.tsRlstsRlstsRls]
478  self.rlsMax_tsRlsrlsMax_tsRls = self.ruleEforuleEfo.rlsMax[:, self.mbrRlsmbrRls, self.tsRlstsRlstsRls]
479  # Temporary for presentation
480  if self.SaveAllRlsSchedSaveAllRlsSched:
481  if self.tsRlsOvrdtsRlsOvrd:
482  self.rlsFcstAllrlsFcstAll = self.ruleEforuleEfo.rlsFcst[:, :, :self.tsRlsOvrdtsRlsOvrd].reshape(self.ruleEforuleEfo.rlsFcst.shape[0], -1)
483  else:
484  self.rlsFcstAllrlsFcstAll = self.ruleEforuleEfo.rlsFcst.reshape(self.ruleEforuleEfo.rlsFcst.shape[0], -1)
485  else: None
486  # Save to a zipped numpy file
487  np.savez(self.fileNamefileName+'_'+self.T.Tcont.curDT.strftime('%Y-%m-%d-%H'),
488  tFcst=t.dateTime,
489  tsRls = self.tsRlstsRlstsRls,
490  mbrRls = self.mbrRlsmbrRls,
491  releaseSched = self.releaseSchedreleaseSchedreleaseSched,
492  storFcst_NoRls=self.storFcst_NoRlsstorFcst_NoRls,
493  rlsUnCtrlFcst_NoRls = self.rlsUnCtrlFcst_NoRlsrlsUnCtrlFcst_NoRls,
494  storFcst_preRls = self.storFcst_preRlsstorFcst_preRls,
495  rlsUnCtrlFcst_preRls = self.rlsUnCtrlFcst_preRlsrlsUnCtrlFcst_preRls,
496  prExcTh_NoRls=self.prExcTh_NoRlsprExcTh_NoRls,
497  riskTol=self.riskTolriskTol,
498  storFcst=self.storFcststorFcst,
499  rlsUnCtrlFcst = self.rlsUnCtrlFcstrlsUnCtrlFcst,
500  prExcTh=self.prExcThprExcTh,
501  iRiskyMbrsAll=self.iRiskyMbrsAlliRiskyMbrsAll,
502  storFcst_NoRls_tsRls = self.storFcst_NoRls_tsRlsstorFcst_NoRls_tsRls,
503  rlsUnCtrlFcst_NoRls_tsRls = self.rlsUnCtrlFcst_NoRls_tsRlsrlsUnCtrlFcst_NoRls_tsRls,
504  rlsFcst_NoRls_tsRls = self.rlsFcst_NoRls_tsRlsrlsFcst_NoRls_tsRls,
505  storFcst_tsRls = self.storFcst_tsRlsstorFcst_tsRls,
506  rlsUnCtrlFcst_tsRls = self.rlsUnCtrlFcst_tsRlsrlsUnCtrlFcst_tsRls,
507  iRiskyMbrs_tsRls = self.iRiskyMbrs_tsRlsiRiskyMbrs_tsRls,
508  rlsFcst_tsRls = self.rlsFcst_tsRlsrlsFcst_tsRls,
509  rlsMax_tsRls = self.rlsMax_tsRlsrlsMax_tsRls,
510  rlsFcstAll = self.rlsFcstAllrlsFcstAll,
511  iRiskyMbrs_All = self.iRiskyMbrs_AlliRiskyMbrs_All,
512  prExcTh_preRls = self.prExcTh_preRlsprExcTh_preRls
513  )
514  return rlsSched
515 
516 
517 # TODO: This should keep track of the mapping between the fcst reservoir and the network.
519  def __init__(self, name, timeFcst, fcstNetwork):
520  # Call super class constructor
521  super().__init__(name, timeFcst)
522  self.fcstNetworkfcstNetwork = fcstNetwork
523 
524  def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor):
525  pass
526 
527 
528 
def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor, qIn)
Definition: rule_fcst.py:52
def __subclasshook__(cls, C)
Definition: rule_fcst.py:60
def calc_release(self, rlsProposed, rlsUnCtrl, stor, rlsPrev=None, qIn=None)
Definition: rule_fcst.py:30
def __init__(self, name, timeFcst)
Definition: rule_fcst.py:22
def __init__(self, name, ruleEfo, ruleEfo_NoRls, tsArchive=0, fileName='efoFcstResults', tsRlsOvrd=None, saveAllRlsSched=False)
Definition: rule_fcst.py:425
def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor, qIn=None)
Definition: rule_fcst.py:435
def _calc_release_sched(self, rlsFcst, tsHoriz, ensMbr)
Definition: rule_fcst.py:253
def _get_adjusted_release_eqldist(self, rlsFcstInit, rlsMax)
Definition: rule_fcst.py:376
def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor, qIn=None)
Definition: rule_fcst.py:95
def __init__(self, name, timeFcst, efoResrvr, storTh, riskTol, constants, *efoNetwork=None, ruleRlsSpecified=None, efoRlsSched=None, maxItr=100, storMax=None, nHrsLead=None)
Definition: rule_fcst.py:71
def _get_adjusted_release_rollback(self, rlsFcstInit, tsHoriz, rlsMax)
Definition: rule_fcst.py:392
def old_calc_release_sched(self, rlsFcst, tsHoriz, ensMbr)
Definition: rule_fcst.py:320
def __init__(self, name, rulePfo, ruleEfoNoRls)
Definition: rule_fcst.py:407
def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor, qIn=None)
Definition: rule_fcst.py:414
def calc_release_fcst(self, rlsProposed, rlsUnCtrl, stor)
Definition: rule_fcst.py:524
def __init__(self, name, timeFcst, fcstNetwork)
Definition: rule_fcst.py:519