Prado EFO PVA
lookup.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 """
3 Created on Tue May 5 09:20:29 2020
4 
5 @author: cd
6 """
7 
8 
9 import abc
10 import numpy as np
11 # import time as tm
12 import datetime as dt
13 import calendar as cl
14 from datetime import timedelta
15 from scipy.interpolate import interp1d
16 # import warnings
17 # warnings.filterwarnings('error', category=RuntimeWarning)
18 
19 
20 class LkupTblBase(metaclass=abc.ABCMeta):
21  def __init__(self, name, idxVals, lkupVals):
22  self.namename = name
23  # TODO: Error check that number of idxVals == lkupVals
24  self.idxValsidxVals = idxVals
25  self.lkupValslkupVals = lkupVals
26  self._minIdx_minIdx = np.amin(self.idxValsidxVals)
27  self._maxIdx_maxIdx = np.amax(self.idxValsidxVals)
28  super().__init__()
29 
30  @abc.abstractmethod
31  def get_val(self, idxVal):
32  pass
33 
34  @classmethod
35  def __subclasshook__(cls, C):
36  if cls is LkupTblBase:
37  attrs = set(dir(C))
38  if set(cls.__abstractmethods__) <= attrs:
39  return True
40  return NotImplemented
41 
42  @property
43  def maxIdx(self):
44  return self._maxIdx_maxIdx
45 
46  @property
47  def minIdx(self):
48  return self._minIdx_minIdx
49 
50 
52  def __init__(self, name, idxVals, lkupVals):
53  # Call super constructor
54  super().__init__(name, idxVals, lkupVals)
55 
56  def get_val(self, valLkup):
57  # iLkup = np.where(self.idxVals <= idxLkup)[0]
58  # if iLkup.size == 0: iLkup = np.full(1, int(0))
59  # return self.lkupVals[iLkup[-1]]
60  try:
61  iLkup = np.where(self.idxValsidxVals <= valLkup)[0]
62  if iLkup.size == 0: iLkup = np.array([0])
63  return self.lkupValslkupVals[iLkup[-1]].item()
64  except Warning as e:
65  print(e)
66  print(valLkup)
67 
68 
70  def __init__(self, name, hypso, xElev, lkupVals):
71  # Convert rating table to storage
72  # xStor = np.interp(xElev, hypso.elev, hypso.stor)
73  xStor = hypso.elev2stor(xElev)
74  # Call super constructor
75  super().__init__(name, xStor, lkupVals)
76 
77 
78 # TODO: Use SciPy Interp function here, see example in the YubaFeather project folder
79 # TODO: The interp function would be a property of the class (might increase speed)
81  def __init__(self, name, idxVals, lkupVals):
82  # Call super constructor
83  super().__init__(name, idxVals, lkupVals)
84  self.interpFncinterpFnc = interp1d(self.idxValsidxVals, self.lkupValslkupVals, kind='linear',
85  fill_value=(min(lkupVals),max(lkupVals)), bounds_error=False)
86  # self.interpFnc = interp1d(self.idxVals, self.lkupVals, kind='cubic')
87 
88  def get_val(self, xVals):
89  # TODO: Scipy alternative the should work better because you could save the function as a property (faster)
90  # if isinstance(xVals, np.ndarray):
91  # valsReturn = np.empty(xVals.size)
92  # for i, curVal in enumerate(xVals):
93  # valsReturn[i] = np.interp(curVal, self.idxVals, self.lkupVals)
94  # return valsReturn
95  # else:
96  # return np.interp(xVals, self.idxVals, self.lkupVals)
97  return self.interpFncinterpFnc(xVals)
98 
99 
100 
101 # # TODO: This should be modified to return multiple values based on a date range
102 # # TODO: Maybe this should subclass LkupTblInterp to provide the most flexibility
103 # # TODO: Need error checking to ensure the proper values are passed
104 # class LkupTblAnn(LkupTblBase):
105 # def __init__(self, name, lkupVals, timeUnit):
106 # # TODO: On init if the timestep of the values passed does not match the
107 # # timestep of the model then use interp to generate the values
108 # self.timeUnit = timeUnit
109 # # Create a datetime64 array for leap year 2020
110 # dtLeapYr = DatetimeIndex(np.arange("2020-01-01", "2021-01-01", np.timedelta64(1, timeUnit),
111 # dtype="datetime64[{}]".format(timeUnit)).astype("datetime64[{}]".format(timeUnit)))
112 # month = [curDt.month for curDt in dtLeapYr]
113 # day = [curDt.day for curDt in dtLeapYr]
114 # hour = [curDt.hour for curDt in dtLeapYr]
115 # # TODO: Could create a pandas table with different month, day, hour columns
116 # # TODO: This could also be faster if it was just based on a single time step
117 # # of year key and use the datetime day_of_year etc... functions; this might
118 # # be better for retrieving multiple values for a date range. Also then this
119 # # could subclass LkupTbl to use less code.
120 # self.vDate = np.array(list(zip(*[month, day, hour])))
121 # # TODO: Add error checking to make sure number of passed vals = number of dates
122 # super().__init__(name, -1, lkupVals)
123 
124 # def get_val(self, dateTime, col=0):
125 # iTime = self.vDate[:, 0] == dateTime.month
126 # if self.timeUnit == 'D':
127 # iTime = iTime & (self.vDate[:, 1] == dateTime.day)
128 # elif self.timeUnit == 'h':
129 # iTime = iTime & (self.vDate[:, 2] == dateTime.hour)
130 # # TODO: There's got to be a cleaner way to do this:
131 # rowLkup = np.where(iTime)[0][0]
132 # if self.lkupVals.ndim > 1:
133 # return self.lkupVals[rowLkup, col]
134 # else:
135 # return self.lkupVals[rowLkup]
136 
137 
138 # TODO: This should default to timeUnit='h'
140  def __init__(self, name, time, monthDayHr, tblVals, *, typ='interp', timeUnit=None):
141  # TODO: This should take a keyword argument to override the timestep of the model
142  # for instance if the table timestep is monthly then no sense in creating hourly values
143  # TODO: Evaluate using numpy Datetime64 for all the date operations. Faster?
144  self.typtyp = typ
145  if timeUnit is not None:
146  # TODO: This should be set to a default of 'h' and not None
147  self.timeUnittimeUnit = timeUnit
148  if timeUnit == 'M':
149  self.tDeltatDelta = np.nan
150  elif timeUnit == 'D':
151  self.tDeltatDelta = timedelta(days=1)
152  else:
153  self.tDeltatDelta = timedelta(hours=1)
154  else:
155  self.timeUnittimeUnit = time.timeUnit
156  self.tDeltatDelta = time.tDelta
157  # TODO: This should be able to handle single value scalars as well as arrays
158  if np.isscalar(tblVals): tblVals = np.array([tblVals], dtype=float)
159  tblVals = tblVals.reshape(len(tblVals), tblVals.ndim) if tblVals.ndim == 1 else tblVals
160  monthDayHr = monthDayHr.reshape(
161  monthDayHr.ndim, monthDayHr.shape[0]) if monthDayHr.ndim == 1 else monthDayHr
162  xDateTime = np.empty(monthDayHr.shape[0] + 1)
163  yVals = np.empty([monthDayHr.shape[0] + 1, tblVals.shape[1]])
164  # TODO: If jan 1 is not the first date then set it as first date?
165  for i in range(0, monthDayHr.shape[0]):
166  curDateTime = dt.datetime(2020,
167  monthDayHr[i, 0],
168  monthDayHr[i, 1],
169  monthDayHr[i, 2], tzinfo=dt.timezone.utc)
170  xDateTime[i] = cl.timegm(curDateTime.timetuple())
171  yVals[i, :] = tblVals[i, :]
172  # Add date for beginning of 2021 to complete lookup table
173  xDateTime[-1] = cl.timegm(dt.datetime(2021, 1, 1, 0, tzinfo=dt.timezone.utc).timetuple())
174  yVals[-1, :] = tblVals[-1, :]
175  # Build array of interpolation values
176  if self.timeUnittimeUnit == 'M':
177  xInterpDates = np.empty(12)
178  for curMonth in range(1, 13):
179  xInterpDates[curMonth-1] = cl.timegm(
180  dt.datetime(2020, curMonth, 1, 0).timetuple())
181  else:
182  dtBgn = dt.datetime(2020, 1, 1, 0)
183  dtEnd = dt.datetime(2021, 1, 1, 0) - time.tDelta
184  xInterpDates = np.linspace(
185  cl.timegm(dtBgn.timetuple()),
186  cl.timegm(dtEnd.timetuple()),
187  int(366*24/(self.tDeltatDelta.total_seconds()/3600)))
188  lkupVals = np.empty([len(xInterpDates), len(yVals[0, :])])
189  if self.typtyp == 'interp':
190  for i in range(len(yVals[0, :])):
191  lkupVals[:, i] = np.interp(xInterpDates, xDateTime, yVals[:, i])
192  else:
193  for i in range(1, len(xDateTime)):
194  iCurPer = (xInterpDates >= xDateTime[i-1]) & (xInterpDates < xDateTime[i])
195  lkupVals[iCurPer, :] = yVals[i-1]
196  self.vDatevDate = np.empty([len(xInterpDates), 3])
197  for i, curDt in enumerate(xInterpDates):
198  # TODO: Should avoid using datetime over pandas
199  # TODO: See if there is a way to do this in pandas
200  self.vDatevDate[i, 0] = dt.datetime.fromtimestamp(curDt, dt.timezone.utc).month
201  self.vDatevDate[i, 1] = dt.datetime.fromtimestamp(curDt, dt.timezone.utc).day
202  self.vDatevDate[i, 2] = dt.datetime.fromtimestamp(curDt, dt.timezone.utc).hour
203  super().__init__(name, -1, lkupVals)
204 
205  def get_val(self, dateTime, col=0):
206  # TODO: This should have an options for begin and end date to return multiple vals
207  # TODO: They would be optional variables called dateRangeBgn and dataRangeEnd
208  # TODO: Use dateTime functions here instead of vDate
209  iTime = self.vDatevDate[:, 0] == dateTime.month
210  if self.timeUnittimeUnit == 'D':
211  iTime = iTime & (self.vDatevDate[:, 1] == dateTime.day)
212  elif self.timeUnittimeUnit == 'h':
213  iTime = iTime & (self.vDatevDate[:, 2] == dateTime.hour)
214  rowLkup = np.where(iTime)[0][0]
215  return self.lkupValslkupVals[rowLkup, col].item()
216 
def get_val(self, dateTime, col=0)
Definition: lookup.py:205
def __init__(self, name, time, monthDayHr, tblVals, *typ='interp', timeUnit=None)
Definition: lookup.py:140
def __subclasshook__(cls, C)
Definition: lookup.py:35
def __init__(self, name, idxVals, lkupVals)
Definition: lookup.py:21
def get_val(self, idxVal)
Definition: lookup.py:31
def minIdx(self)
Definition: lookup.py:47
def maxIdx(self)
Definition: lookup.py:43
def __init__(self, name, hypso, xElev, lkupVals)
Definition: lookup.py:70
def __init__(self, name, idxVals, lkupVals)
Definition: lookup.py:81
def get_val(self, xVals)
Definition: lookup.py:88
def get_val(self, valLkup)
Definition: lookup.py:56
def __init__(self, name, idxVals, lkupVals)
Definition: lookup.py:52