Prado EFO PVA
junction.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 """
3 Created on Wed Apr 29 22:04:46 2020
4 
5 @author: cd
6 """
7 
8 
9 import abc
10 import numpy as np
11 from efo.general import Constants
12 from efo.time import TimeBase
13 from efo.hypso import HypsoBase
14 from efo.outlet import OutletRatedElev
15 from efo.rule_compliance import RuleComplianceBase
16 
17 
18 class JunctionBase(metaclass=abc.ABCMeta):
19  def __init__(self, name, time, qIn=[]):
20  self.namename = name
21  super().__init__()
22  # Set class properties
23  self.qInqIn = []
24  for qInCur in qIn:
25  self.append_qinappend_qin(qInCur)
26  # TODO: Should use error handling instead
27  if issubclass(type(time), TimeBase):
28  self.TT = time
29  self.qOutqOut = np.full(self.TT.nSteps, np.nan)
30  self.qOutqOut[0] = 0.
31  self.qInTotqInTot = np.full(self.TT.nSteps, np.nan)
32 
33  def append_qin(self, qIn):
34  # TODO: Use error handling to check Qin class
35  self.qInqIn.append(qIn)
36 
37  def _get_qin(self, *, tsOffset=0):
38  qTot = 0.
39  for qInCur in self.qInqIn:
40  qTot += qInCur.get_qin(tsOffset)
41  self.qInTotqInTot[self.TT.step] = qTot
42  return qTot
43 
44  def get_qout(self, *, tsOffset=0):
45  return self.qOutqOut[min(self.TT.end, max(0, self.TT.step + tsOffset))].item()
46 
47  @abc.abstractmethod
48  def calc_qout(self):
49  qOut = max(0., self._get_qin_get_qin())
50  # TODO: Can qOut be a property to control writing access?
51  self.qOutqOut[self.TT.step] = qOut
52  return qOut
53 
54  def set_qout(self, qOutSpecified, *, tsOffset=0):
55  self.qOutqOut[min(self.TT.end, self.TT.step + tsOffset)] = qOutSpecified
56 
57  @classmethod
58  def __subclasshook__(cls, C):
59  if cls is JunctionBase:
60  attrs = set(dir(C))
61  if set(cls.__abstractmethods__) <= attrs:
62  return True
63  return NotImplemented
64 
65 
67  def __init__(self, name, time, qIn=[]):
68  # Call super class constructor
69  super().__init__(name, time, qIn=qIn)
70 
71  def calc_qout(self):
72  return super().calc_qout()
73 
74 
76  def __init__(self, name, time, qIn,
77  ruleMinQ=None, ruleMaxQ=None, ruleDiversion=[]):
78  # Call super class constructor
79  super().__init__(name, time, qIn)
80  # Set class properties
81  # Need to make sure these are max and min rules and diversion rules
82  # TODO: Should this also have a qMax and qMin like ReservoirJunction?
83  # TODO: Instead of having a ruleMinQ and ruleMaxQ could this also have...
84  # ... a rule stack
85  self.ruleMinQruleMinQ = ruleMinQ
86  self.ruleMaxQruleMaxQ = ruleMaxQ
87  self.ruleDiversionruleDiversion = ruleDiversion
88  if ruleDiversion: self.qDivqDiv = np.full(self.TT.nSteps, np.nan)
89  self.continuitycontinuity = np.full(self.TT.nSteps, np.nan)
90 
91  # TODO: Property?
92  def set_rule_minq(self, ruleMinQ):
93  self.ruleMinQruleMinQ = ruleMinQ
94 
95  # TODO: Property?
96  def set_rule_maxq(self, ruleMaxQ):
97  self.ruleMaxQruleMaxQ = ruleMaxQ
98 
99  def _calc_q(self, qIn, qDiv, rule):
100  qOut = qIn - qDiv
101  if qOut < 0.:
102  qDiv += qOut
103  rule.release[self.TT.step] = qDiv
104  qOut = 0.
105  return qOut, qDiv
106 
107  def calc_delta(self, *, ruleType=RuleComplianceBase.MAX, tsOffset=0):
108  if ruleType == RuleComplianceBase.MAX and self.ruleMaxQruleMaxQ:
109  targetQ = self.ruleMaxQruleMaxQ.get_qcomp(tsOffset=tsOffset)
110  elif ruleType == RuleComplianceBase.MIN and self.ruleMinQruleMinQ:
111  targetQ = self.ruleMinQruleMinQ.get_qcomp(tsOffset=tsOffset)
112  else:
113  targetQ = np.nan
114  qDiv = 0.
115  qInInit = self._get_qin_get_qin(tsOffset=tsOffset)
116  if self.ruleDiversionruleDiversion:
117  for curRule in self.ruleDiversionruleDiversion:
118  qDiv += curRule.get_qcomp(qIn=qInInit, tsOffset=tsOffset)
119  qInNet = qInInit - qDiv
120  self.qOutqOut[min(self.TT.end, self.TT.step + tsOffset)] = max(0., qInNet)
121  return qInNet - targetQ if ruleType == RuleComplianceBase.MAX else targetQ - qInNet
122 
123  def calc_qout(self):
124  qInTot = super().calc_qout()
125  # self.qInNet[self.T.step] = qInTot
126  qDiv = 0.
127  qOut = qInTot
128  qMin = self.ruleMinQruleMinQ.get_qcomp(qIn=qInTot) if self.ruleMinQruleMinQ is not None else 0.
129  qDivMax = qInTot - qMin
130  if self.ruleDiversionruleDiversion:
131  for curRule in self.ruleDiversionruleDiversion:
132  curDiv = curRule.calc_release(rlsProposed=max(0., qDivMax-qDiv), qIn=qInTot)
133  qOut, curDiv = self._calc_q_calc_q(qOut, curDiv, curRule)
134  qDiv += curDiv
135  # if self.ruleDiversion: self.qDiv[self.T.step] = qDiv
136  self.qDivqDiv[self.TT.step] = qDiv
137  # qOut = qInTot - qDiv
138  self.continuitycontinuity[self.TT.step] = qOut + qDiv - qInTot
139  self.qOutqOut[self.TT.step] = qOut
140  return qOut
141 
142 
143 
144 # TODO: Maybe make the ruleStack it's own class that can have a method to evaluate the rules...
145 # ...this could also be used to map the rules by priority.
147  def __init__(self, name, time, qIn, storInit, constants,
148  *,hypso=None, ruleStack=[], evapObj=None, seepageObj=None,
149  outletCtrl=None, outletUnctrl=None, ruleEmgc=None, storMax=None):
150  # Call super class constructor
151  super().__init__(name, time, qIn)
152  # Set class properties
153  self.ruleStackruleStack = ruleStack
154  self.set_ctrl_outletset_ctrl_outlet(outletCtrl)
155  self.set_unctrl_outletset_unctrl_outlet(outletUnctrl)
156  self.ruleEmgcruleEmgc = ruleEmgc
157  # TODO: This could be a guide curve rule or just a scalar
158  self.storMaxstorMax = storMax
159  # TODO: Should use error handling instead
160  if issubclass(type(constants), Constants):
161  self.constantsconstants = constants
162  if issubclass(type(hypso), HypsoBase):
163  self.hypsohypso = hypso
164  # Create storage array
165  self.storstor = np.full(self.TT.nSteps, np.nan)
166  self.storstor[0] = storInit
167  # Other propoerties
168  self.set_evapset_evap(evapObj)
169  self.set_seepageset_seepage(seepageObj)
170  # self.continuity = np.full(self.T.nSteps, np.nan)
171  # self.qInNet = np.full(self.T.nSteps, np.nan)
172 
173  def create_ctrl_outlet(self, name, elev, qOutlet):
174  # TODO: Use error handling to check values passed
175  outletCtrl = OutletRatedElev(name, self.TT, self.hypsohypso, elev, qOutlet)
176  self.set_ctrl_outletset_ctrl_outlet(outletCtrl)
177 
178  def create_unctrl_outlet(self, name, elev, qOutlet):
179  # TODO: Use error handling to check values passed
180  outletUnCtrl = OutletRatedElev(name, self.TT, self.hypsohypso, elev, qOutlet)
181  self.set_unctrl_outletset_unctrl_outlet(outletUnCtrl)
182 
183  def set_ctrl_outlet(self, outletCtrl):
184  # TODO: Use error handling to check values passed
185  self.outletCtrloutletCtrl = outletCtrl
186  if outletCtrl:
187  self.rlsCtrlrlsCtrl = np.full(self.TT.nSteps, np.nan)
188  self.rlsCtrlrlsCtrl[0] = 0.
189  self.rlsMinrlsMin = np.full(self.TT.nSteps, np.nan)
190  self.rlsMaxrlsMax = np.full(self.TT.nSteps, np.nan)
191 
192  def set_unctrl_outlet(self, outletUnctrl):
193  # TODO: Use error handling to check values passed
194  self.outletUnCtrloutletUnCtrl = outletUnctrl
195  if outletUnctrl: self.rlsUnCtrlrlsUnCtrl = np.full(self.TT.nSteps, np.nan)
196 
197  def set_evap(self, evapObj):
198  # TODO: Use error handling to check values passed
199  self.evapObjevapObj = evapObj
200  if evapObj: self.lossEvaplossEvap = np.full(self.TT.nSteps, np.nan)
201 
202  def set_seepage(self, seepageObj):
203  # TODO: Use error handling to check values passed
204  self.seepageObjseepageObj = seepageObj
205  if seepageObj: self.lossSeeplossSeep = np.full(self.TT.nSteps, np.nan)
206 
207  def set_rule_emgc(self, ruleEmgc):
208  # TODO: Add error handling
209  self.ruleEmgcruleEmgc = ruleEmgc
210 
211  def set_stor_init(self, storInit):
212  self.storstor[0] = storInit
213 
214  def set_ruleStack(self, ruleStack):
215  # TODO: Error handling needed here
216  if type(ruleStack) == list:
217  self.ruleStackruleStack = ruleStack
218 
219  def append_rule(self, rule):
220  if type(rule) == list:
221  self.ruleStackruleStack = self.ruleStackruleStack + rule
222  # TODO: Use error handling to check values passed, check if RuleFlood
223  # TODO: Need to check for duplicate rules
224  # TODO: Maybe use a set instead
225  else:
226  self.ruleStackruleStack.append(rule)
227 
228  def calc_delta(self, *, ruleType=RuleComplianceBase.MAX, tsOffset=0):
229  # TODO: This is just a very simple implementation for Prado that just checks the defficit to max storage
230  # TODO: This could be much more advanced to check diversions, controlled and uncontrolled releases
231  # TODO: Also maxStor could be a guide curve rule or a scalar and have a check here which type
232  if self.storMaxstorMax:
233  storPrev = self.storstor[max(0,self.TT.step-1)].item()
234  qInNet = self._get_qin_get_qin(tsOffset=tsOffset)
235  lossEvap, storCur, qInNet = self._calc_evap_calc_evap(storPrev, qInNet)
236  lossSeep, storCur, qInNet = self._calc_seepage_calc_seepage(storPrev, qInNet)
237  qDiv, storCur, qInNet = self._calc_diversions_calc_diversions(storPrev, qInNet)
238  return -max(0.,self.storMaxstorMax - storCur)*self.constantsconstants.af2cfs
239  else:
240  return np.nan
241 
242  def _calc_storage(self, storPrev, qInNet, qOut):
243  stor = storPrev - qOut*self.constantsconstants.cfs2af + qInNet*self.constantsconstants.cfs2af
244  if stor <= 0.:
245  qOut += stor*self.constantsconstants.af2cfs
246  if qOut < 0.: qOut = 0.
247  stor = 0.
248  return stor, qOut
249 
250  def _calc_evap(self, stor, qIn):
251  if self.evapObjevapObj:
252  lossEvap = self.evapObjevapObj.calc_evap(stor+qIn*self.constantsconstants.cfs2af)*self.constantsconstants.af2cfs
253  stor, lossEvap = self._calc_storage_calc_storage(stor, qIn, lossEvap)
254  qIn -= lossEvap
255  else:
256  lossEvap = 0.
257  return lossEvap, stor, qIn
258 
259  def _calc_seepage(self, stor, qIn):
260  if self.seepageObjseepageObj:
261  lossSeep = self.seepageObjseepageObj.calc_seepage(stor+qIn*self.constantsconstants.cfs2af)
262  stor, lossSeep = self._calc_storage_calc_storage(stor, qIn, lossSeep)
263  qIn -= lossSeep
264  else:
265  lossSeep = 0.
266  return lossSeep, stor, qIn
267 
268  def _calc_diversions(self, stor, qIn):
269  qDiv = 0.
270  if self.ruleDiversionruleDiversion:
271  for curRule in self.ruleDiversionruleDiversion:
272  curDiv = curRule.calc_release(rlsProposed=qIn+stor*self.constantsconstants.af2cfs,
273  rlsPrev=curRule.release[max(0, self.TT.step-1)].item(),
274  stor=stor,
275  qIn=qIn)
276  storCur, curDiv, qIn = self._calc_storage_calc_storage(stor, qIn, curDiv)
277  qIn -= curDiv
278  qDiv += curDiv
279  return qDiv, stor, qIn
280 
281  def calc_qout(self):
282  qOutTotCur = 0.
283  qInNet = qInInit = super()._get_qin()
284  storPrev = self.storstor[max(0, self.TT.step-1)].item()
285  storCur = storPrev
286  # Get evaporation
287  lossEvap, storCur, qInNet = self._calc_evap_calc_evap(storPrev, qInNet)
288  if self.evapObjevapObj: self.lossEvaplossEvap[self.TT.step] = lossEvap
289  # Get seepage
290  lossSeep, storCur, qInNet = self._calc_seepage_calc_seepage(storPrev, qInNet)
291  if self.seepageObjseepageObj: self.lossSeeplossSeep[self.TT.step] = lossSeep
292  # Calc diversions
293  qDiv, storCur, qInNet = self._calc_diversions_calc_diversions(storPrev, qInNet)
294  if self.ruleDiversionruleDiversion: self.qDivqDiv[self.TT.step] = qDiv
295  # Get uncontrolled release
296  rlsUnCtrl = 0.
297  # Get emergency release
298  rlsEmgc = self.ruleEmgcruleEmgc.calc_release(
299  0., stor=storPrev, rlsUnCtrl=rlsUnCtrl, qIn=qInNet) if self.ruleEmgcruleEmgc is not None else 0.
300  qOutTotCur += rlsEmgc
301  storCur, qOutTotCur = self._calc_storage_calc_storage(storPrev, qInNet, qOutTotCur)
302  # TODO: If your emergency release exceeds your max controlled release then no rule stack
303  # TODO: Should look into ruleStack as not a list but an ordered list and the rules unique
304  # TODO: Python linked list seems like a good choice for rule stack
305  qOutTotStart = qOutTotCur
306  if self.outletCtrloutletCtrl:
307  while True:
308  rlsStack = self.calc_release_ctrlcalc_release_ctrl(self.rlsCtrlrlsCtrl[max(0, self.TT.step-1)],
309  rlsEmgc+rlsUnCtrl, storPrev, storCur, qInNet)
310  storCur, qOutTotCur = self._calc_storage_calc_storage(storPrev, qInNet, qOutTotStart + rlsStack)
311  rlsStack = qOutTotCur - qOutTotStart
312  rlsUnCtrlPrev = rlsUnCtrl
313  rlsUnCtrl = self.outletUnCtrloutletUnCtrl.calc_release(storCur) if self.outletUnCtrloutletUnCtrl else 0.
314  qOutTotCur += rlsUnCtrl
315  storCur, qOutTotCur = self._calc_storage_calc_storage(storPrev, qInNet, qOutTotCur)
316  # TODO: This should also check if the ruleStack rules are type RLS_TOT if not then this check is not necessary
317  if abs(rlsUnCtrl - rlsUnCtrlPrev) < 1e-10:
318  break
319  else:
320  rlsUnCtrl = self.outletUnCtrloutletUnCtrl.calc_release(storCur) if self.outletUnCtrloutletUnCtrl else 0.
321  qOutTotCur += rlsUnCtrl
322  storCur, qOutTotCur = self._calc_storage_calc_storage(storPrev, qInNet, qOutTotCur)
323  if self.outletUnCtrloutletUnCtrl: self.rlsUnCtrlrlsUnCtrl[self.TT.step] = rlsUnCtrl
324  # TODO: Check to see if a numpy reference is returned or a float primitive
325  if self.outletCtrloutletCtrl: self.rlsCtrlrlsCtrl[self.TT.step] = min(rlsStack, self.outletCtrloutletCtrl.calc_release(storCur))
326  self.qOutqOut[self.TT.step] = qOutTotCur
327  self.continuitycontinuity[self.TT.step] = \
328  (storCur-self.storstor[max(self.TT.step-1, 0)])*self.constantsconstants.af2cfs + \
329  qOutTotCur - qInInit + lossEvap + lossSeep + qDiv
330  self.storstor[self.TT.step] = max(0.,storCur)
331  return qOutTotCur
332 
333  # TODO: This could be added to a rulestack object and generalized for diversions and release
334  # TODO: RuleStack could subclass RuleBase for ulimate flexibility
335  def calc_release_ctrl(self, rlsPrev, rlsUnCtrl, storPrev, storCur, qIn):
336  rlsStack = 0.
337  rlsMin = 0.
338  rlsMax = np.inf
339  # TODO: Should look into ruleStack as not a list but an ordered list and the rules unique
340  # TODO: Python linked list seems like a good choice for rule stack
341  # TODO: You could determine the rulease setting rule by determining which rule in the stack
342  # set the release.
343  if self.ruleStackruleStack:
344  for curRule in self.ruleStackruleStack:
345  rlsStack = curRule.calc_release(rlsStack,
346  rlsPrev=rlsPrev,
347  rlsUnCtrl=rlsUnCtrl,
348  stor=storPrev, qIn=qIn)
349  if hasattr(curRule, 'ruleType'):
350  if curRule.ruleType == RuleComplianceBase.MIN:
351  if curRule.qCompCur > rlsMin: rlsMin = curRule.qCompCur
352  elif curRule.ruleType == RuleComplianceBase.MAX:
353  if curRule.qCompCur < rlsMax: rlsMax = curRule.qCompCur
354  storCur, rlsAdj = self._calc_storage_calc_storage(storPrev, qIn, rlsUnCtrl+rlsStack)
355  rlsStack = rlsAdj - rlsUnCtrl
356  if storCur == 0.: rlsMax = rlsStack
357  # curRule.release[self.T.step] = rlsStack
358  self.rlsMinrlsMin[self.TT.step] = rlsMin
359  self.rlsMaxrlsMax[self.TT.step] = rlsMax
360  return rlsStack
361 
362 
363 # NOTES:
364 # Possible logic multiple spillways (not done)
365  # def calc_release_unctrl(self, storInit):
366  # rlsUnCntrl = np.zeros(len(self.outletUnCtrl))
367  # rlsUnCntrlPrev = np.ones(len(self.outletUnCtrl))
368  # while all(rlsUnCntrl != rlsUnCntrlPrev):
369  # rlsUnCntrlPrev = rlsUnCntrl
370  # storCur = storInit
371  # for i, ctrlOutlet in self.outletUnCtrl:
372  # rlsUnCrl(i) = ctrlOutlet.calc_release
373  # storCur =
def set_qout(self, qOutSpecified, *tsOffset=0)
Definition: junction.py:54
def get_qout(self, *tsOffset=0)
Definition: junction.py:44
def __init__(self, name, time, qIn=[])
Definition: junction.py:19
def __subclasshook__(cls, C)
Definition: junction.py:58
def _get_qin(self, *tsOffset=0)
Definition: junction.py:37
def append_qin(self, qIn)
Definition: junction.py:33
def calc_qout(self)
Definition: junction.py:48
def _calc_q(self, qIn, qDiv, rule)
Definition: junction.py:99
def set_rule_minq(self, ruleMinQ)
Definition: junction.py:92
def set_rule_maxq(self, ruleMaxQ)
Definition: junction.py:96
def __init__(self, name, time, qIn, ruleMinQ=None, ruleMaxQ=None, ruleDiversion=[])
Definition: junction.py:77
def calc_delta(self, *ruleType=RuleComplianceBase.MAX, tsOffset=0)
Definition: junction.py:107
def calc_qout(self)
Definition: junction.py:71
def __init__(self, name, time, qIn=[])
Definition: junction.py:67
def set_unctrl_outlet(self, outletUnctrl)
Definition: junction.py:192
def _calc_evap(self, stor, qIn)
Definition: junction.py:250
def __init__(self, name, time, qIn, storInit, constants, *hypso=None, ruleStack=[], evapObj=None, seepageObj=None, outletCtrl=None, outletUnctrl=None, ruleEmgc=None, storMax=None)
Definition: junction.py:149
def set_seepage(self, seepageObj)
Definition: junction.py:202
def append_rule(self, rule)
Definition: junction.py:219
def set_ctrl_outlet(self, outletCtrl)
Definition: junction.py:183
def calc_delta(self, *ruleType=RuleComplianceBase.MAX, tsOffset=0)
Definition: junction.py:228
def _calc_seepage(self, stor, qIn)
Definition: junction.py:259
def create_unctrl_outlet(self, name, elev, qOutlet)
Definition: junction.py:178
def set_stor_init(self, storInit)
Definition: junction.py:211
def create_ctrl_outlet(self, name, elev, qOutlet)
Definition: junction.py:173
def _calc_storage(self, storPrev, qInNet, qOut)
Definition: junction.py:242
def set_ruleStack(self, ruleStack)
Definition: junction.py:214
def calc_release_ctrl(self, rlsPrev, rlsUnCtrl, storPrev, storCur, qIn)
Definition: junction.py:335
def set_rule_emgc(self, ruleEmgc)
Definition: junction.py:207
def set_evap(self, evapObj)
Definition: junction.py:197
def _calc_diversions(self, stor, qIn)
Definition: junction.py:268