HWRF  trunk@4391
numerics.py
1 """!Time manipulation and other numerical routines.
2 
3 This module implements various simple numerical algorithms, such as
4 partial sorting, time manipulation or fraction-to-date conversions.
5 It also contains two array-like classes that take datetime objects as
6 indices."""
7 
8 ##@var __all__
9 # The symbols exported by "from hwrf.numerics import *"
10 __all__ = [ 'partial_ordering','fcst_hr_min','split_fraction','to_fraction',
11  'to_datetime_rel','to_datetime','to_timedelta','TimeArray',
12  'minutes_seconds_rest','nearest_datetime','is_at_timestep',
13  'great_arc_dist', 'timedelta_epsilon', 'TimeMapping',
14  'within_dt_epsilon', 'randint_zeromean']
15 
16 import fractions,math,datetime,re,random
17 import hwrf.exceptions
18 
19 ########################################################################
20 
21 class partial_ordering(object):
22  """!Sorts a pre-determined list of objects, placing unknown items
23  at a specified location.
24 
25  This class is a drop-in replacement for cmp in sorting routines.
26  It represents a partial ordering of objects by specifying the
27  order of a known subset of those objects, and inserting all
28  unknown objects in a specified location in the that list (at the
29  end, by default) in an order determined by cmp(a,b). Example:
30 
31  @code{.py}
32  p=partial_ordering([3,2,1]) # list is ordered as [3,2,1] with
33  # everything else after that
34 
35  sorted([0,1,2,3,6,4,5],p) # = [3, 2, 1, 0, 4, 5, 6]
36  p(1,-99) # = -1, so -99 goes after 1 since -99 is not
37  # in the partial ordering
38  p(1,3) # = 1, so 3 goes before 1 since 3 is before 1
39  # in the partial ordering
40  p(5,10) # = -1 since cmp(5,10)=-1
41  @endcode"""
42  def __init__(self,ordering,unordered=None,backupcmp=cmp):
43  """!partial_ordering constructor.
44 
45  Creates a partial ordering. The subset that is ordered is
46  specified by the ordered iterable "ordered" while the index at
47  which to place unordered values is optionally specified by
48  "unordered", which can be anything that can be cmp()'ed to an
49  int. If "unordered" is missing, then all objects not in
50  "ordered" will be placed at the end of any list. To place at
51  the beginning of the list, give unordered=0. To insert
52  between the first and second elements, specify 1, between
53  second and third elements: specify unordered=2, and so on.
54  Specify another tiebreaker "cmp" function with "backupcmp"
55  (default: cmp).
56  @param ordering the ordering of known objects
57  @param unordered Optional: where to put other objects
58  @param backupcmp Tiebreaker comparison function."""
59  self.order=dict()
60  self.backupcmp=backupcmp
61  if unordered is None:
62  self.unordered=float('inf') # index of the location to
63  # store unordered elements
64  else:
65  self.unordered=unordered
66  # self.unordered must NOT cmp() compare to 0 against any
67  # possible index
68  i=0
69  for obj in ordering:
70  while cmp(i,self.unordered)==0:
71  i+=1
72  self.order[obj]=i
73  i+=1
74  ##@var order
75  # Internal ordering information
76  # @protected
77 
78  ##@var backupcmp
79  # Backup comparison function for tiebreaking
80  # @protected
81 
82  ##@var unordered
83  # Unordered element index
84  # @protected
85 
86  def __call__(self,a,b):
87  """!Determine the ordering of a and b
88  @param a,b the objects to order
89  @returns -1 if b>a, 1 if b<a, 0 if b and a belong in the same index."""
90  ia = self.order[a] if a in self.order else self.unordered
91  ib = self.order[b] if b in self.order else self.unordered
92  c=cmp(ia,ib)
93  if c==0:
94  c=self.backupcmp(a,b)
95  return c
96 
97 ########################################################################
98 
99 def great_arc_dist(xlon1,ylat1, xlon2,ylat2):
100  """!Great arc distance between two points on Earth.
101 
102  Calculates the great arc distance in meters between two points
103  using the Haversine method. Uses the local Earth radius at the
104  latitude half-way between the two points.
105  @param xlon1,ylat1 first point, degrees
106  @param xlon2,ylat2 second point, degrees
107  @returns distance in meters"""
108  deg2rad=math.pi/180.0
109  Requator=6378137.0
110  flattening_inv=298.247
111 
112  rlat1=float(ylat1)*deg2rad
113  rlon1=float(xlon1)*deg2rad
114  rlat2=float(ylat2)*deg2rad
115  rlon2=float(xlon2)*deg2rad
116 
117  Rearth1=Requator*(1.0-math.sin(rlat1)**2.0/flattening_inv)
118  Rearth2=Requator*(1.0-math.sin(rlat2)**2.0/flattening_inv)
119 
120  return (Rearth1+Rearth2)*math.asin(min(1.0,math.sqrt( \
121  math.sin((rlat1-rlat2)/2.0)**2.0 + \
122  math.cos(rlat1)*math.cos(rlat2)*math.sin((rlon1-rlon2)/2.0)**2.0)))
123 
124 ########################################################################
125 
126 def fcst_hr_min(time,start):
127  """!Return forecast time in hours and minutes.
128 
129  Given a forecast datetime.datetime and an analysis
130  datetime.datetime, this returns a tuple containing the forecast
131  hour and minute, rounded to the nearest integer minute.
132  @param time forecast time as a datetime.datetime
133  @param start analysis time as a datetime.datetime
134  @returns a tuple (ihours,iminutes)"""
135  dt=time - start # forecast time
136  # Convert to hours and minutes, round to nearest minute:
137  fhours=dt.days*24 + dt.seconds/3600 + dt.microseconds/3600e6
138  (fpart,ihours)=math.modf(fhours)
139  if fpart>1.:
140  assert(fpart<1.)
141  iminutes=round(fpart*60)
142  return (ihours,iminutes)
143 
144 ########################################################################
145 
146 def randint_zeromean(count,imax,randomizer=None):
147  """!Generates "count" numbers uniformly distributed between -imax
148  and imax, inclusive, with a mean of zero.
149  @param count number of numbers to return
150  @param imax maximum value of any number
151  @param randomizer the random module, or something that looks like it"""
152  imax=abs(int(imax))
153  count=int(count)
154  if randomizer is None:
155  randint=random.randint
156  else:
157  randint=randomizer.randint
158  if imax!=0 and not ( -imax < imax):
159  # Integer overflow
160  raise OverflowError(
161  'In randint_zeromean, imax=%d cannot be negated and fit '
162  'within a Python int.'%imax)
163  rand=[ randint(-imax,imax) for x in xrange(count) ]
164  cen=sum(rand)
165  while cen!=0:
166  if cen>0:
167  while True:
168  icen=randint(0,count-1)
169  if rand[icen]>-imax:
170  rand[icen]-=1
171  break
172  elif cen<0:
173  while True:
174  icen=randint(0,count-1)
175  if rand[icen]<imax:
176  rand[icen]+=1
177  break
178  cen=sum(rand)
179  assert(sum(rand)==0)
180  return rand
181 
182 ########################################################################
183 
185  """!Splits a fraction into components.
186 
187  Splits a fraction.Fraction into integer, numerator and denominator
188  parts. For example, split_fraction(Fraction(13,7)) will return
189  (1,6,7) since 1+6/7=13/7.
190 
191  @returns a tuple (integer,numerator,denominator)"""
192  i=int(f)
193  f2=f-i
194  return (i,int(f2.numerator),int(f2.denominator))
195 
196 ########################################################################
197 
198 def within_dt_epsilon(time1,time2,epsilon):
199  """!Returns True if time1 is within epsilon of time2, and False
200  otherwise.
201  @param time1,time2 the times being compared
202  @param epsilon how close they need to be in order to be
203  considered equal.
204  @returns True if the times are within epsilon of each other,
205  False if not"""
206  if not isinstance(time2,datetime.datetime):
207  dt=to_fraction(time2)
208  else:
209  if not isinstance(time1,datetime.datetime):
210  time1=to_datetime(time1)
211  dt=time2-time1
212  dt=fractions.Fraction(dt.microseconds,1000000)+\
213  dt.seconds+24*3600*dt.days
214  if not isinstance(epsilon,fractions.Fraction):
215  epsilon=to_fraction(epsilon)
216  return abs(dt) < abs(epsilon)
217 
218 ########################################################################
219 
220 def timedelta_epsilon(times,rel=None,default=None,sort=False,numerator=10):
221  """!Decides a reasonable epsilon for time equality comparisons.
222 
223  Given an iterable of datetime objects (or anything accepted by
224  to_datetime_rel), computes the minimum time difference between any
225  two adjacent times and divides it by "numerator" (default: 10).
226  The "rel" argument is the relative time when calling
227  to_datetime_rel. If unspecified, it will be the first time seen
228  (in which case that time must be acceptable to to_datetime). The
229  "default" is the return value when all elements in "times" are
230  identical, or when there is only one element. If the default is
231  unspecified and it is needed, then NoTimespan is raised. If sort
232  is specified and True, then the times will be sorted before
233  anything is done (it is False by default).
234 
235  @param times a list of example times for comparison
236  @param rel a reference time to which the times will be compared
237  @param default if too few unique times are found, this is returned
238  If that happens and no default is provided, an exception is raised
239  @param sort if True, sort times
240  @param numerator A measure of how close times should be. The least
241  time difference will be at least numerator times the epsilon
242  @returns an epsilon value"""
243  if sort: times=sorted(times)
244  if rel is not None: rel=to_timedelta(rel)
245  mindiff=None
246  prior=None
247  numerator=int(numerator)
248  for time in times:
249  if rel is None:
250  now=to_datetime(time)
251  rel=now
252  else:
253  now=to_datetime_rel(time,rel)
254  if prior is not None:
255  diff=abs(to_fraction(now-prior,negok=True) / numerator)
256  if mindiff is None or diff<mindiff and diff>0: mindiff=diff
257  prior=now
258  if mindiff is None:
259  if default is None:
261  'Too few non-identical times in input, and no default '
262  'specified. Cannot compute timespan in timedelta_epsilon.',
263  start=rel,end=None)
264  return to_timedelta(default)
265  return to_timedelta(mindiff)
266 
267 ########################################################################
268 
269 def to_fraction(a,b=None,negok=False):
270  """!Converts an object or two to a fraction.
271 
272  This routine is a wrapper around fraction.Fraction() which accepts
273  additional inputs. Fractions are needed to provide higher
274  precision in time and resolution calculations (and also to match
275  the WRF behavior). The arguments are the same as for the
276  fractions.Fraction constructor, but additional calling conventions
277  are accepted: a single float argument, a single datetime.timedelta
278  object, or a string containing an integer and a fraction. If a
279  float is provided, its denominator is restricted to be no more
280  than 1000000. If the resulting fraction is not larger than 0,
281  then InvalidTimestep is thrown unless negok=True.
282 
283  Examples:
284  @code
285  to_fraction(0.855) # 0.855 seconds
286  to_fraction(33) # 33 seconds
287  to_fraction('12/5') # 2.4 seconds
288  to_fraction('7+1/2') # 7.5 seconds
289  to_fraction(0.0000001) # ERROR! Value of the float() is less
290  # than 1e-6
291  to_fraction(1,10000000) # Valid as a fraction, even though value
292  # is less than 1e-6
293  @endcode
294  @param a,b the objects to convert
295  @param negok if True, negative numbers are okay."""
296  result=None
297  if b is not None:
298  result=fractions.Fraction(a,b)
299  elif isinstance(a,datetime.timedelta):
300  result=fractions.Fraction(a.microseconds,1000000)+\
301  a.seconds+24*3600*a.days
302  elif isinstance(a,basestring): # Catch the 1+3/7 syntax:
303  m=re.match('\s*(?P<ipart>[+-]?\d+)\s*(?P<num>[+-]\d+)\s*/\s*(?P<den>\d+)',a)
304  if(m):
305  (i,n,d)=m.groups(['ipart','num','den'])
306  result=fractions.Fraction(int(i)*int(d)+int(n),int(d))
307  elif isinstance(a,float):
308  result=fractions.Fraction.from_float(a).limit_denominator(1000000)
309  if result is None:
310  result=fractions.Fraction(a)
311  if not negok and not result>=0: # avoid < in case of NaN
313  ('to_fraction(%s,%s) resulted in a negative timestep' \
314  % (repr(a),repr(b)))
315  return result
316 
317 ########################################################################
318 
319 def to_datetime_rel(d,rel):
320  """!Converts objects to a datetime relative to another datetime.
321 
322  Given a datetime object "rel", converts "d" to a datetime. Object
323  "d" can be anything accepted by to_datetime, or anything accepted
324  as a single argument by to_timedelta. If it is a timedelta, it is
325  added to "rel" to get the final time.
326  @param d the object being converted
327  @param rel the datetime.datetime to which it is to be converted"""
328  if not isinstance(rel,datetime.datetime):
329  rel=to_datetime(rel)
330  if isinstance(d,datetime.datetime):
331  return d
332  elif isinstance(d,datetime.timedelta):
333  return rel+d
334  elif isinstance(d,basestring):
335  if(re.match('\A(?:\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d|\d{10}|\d{12})\Z',d)):
336  if len(d)==10:
337  return datetime.datetime.strptime(d,'%Y%m%d%H')
338  elif len(d)==12:
339  return datetime.datetime.strptime(d,'%Y%m%d%H%M')
340  else:
341  return datetime.datetime.strptime(d,'%Y-%m-%d %H:%M:%S')
342  return rel+to_timedelta(d)
343 
344 ########################################################################
345 
346 def to_datetime(d):
347  """!Converts the argument to a datetime.
348 
349  If the argument is already a datetime, it is simply returned.
350  Otherwise it must be a string of the format YYYYMMDDHH,
351  YYYYMMDDHHMM, or YYYY-MM-DD HH:MM:SS.
352  @param d the object being converted
353  @returns the resulting datetime.datetime object"""
354  if isinstance(d,int): d=str(d)
355  if isinstance(d,datetime.datetime):
356  return d
357  elif isinstance(d,basestring):
358  if len(d)==10:
359  return datetime.datetime.strptime(d,'%Y%m%d%H')
360  elif len(d)==12:
361  return datetime.datetime.strptime(d,'%Y%m%d%H%M')
362  else:
363  return datetime.datetime.strptime(d,'%Y-%m-%d %H:%M:%S')
364  else:
365  raise ValueError('Invalid value %s for to_datetime. Parameter '
366  'must be a datetime or a string that matches '
367  'YYYY-MM-DD HH:MM:SS.'%(repr(d),))
368 
369 ########################################################################
370 
371 def to_timedelta(a,b=None,negok=True):
372  """!Converts an object to a datetime.timedelta.
373 
374  Returns a datetime.timedelta object. If "a" is a timedelta, then
375  that value is returned. If "a" is a string of the format 08:13 or
376  08:13:12, optionally preceded by a "-" then it is interpreted as
377  HH:MM or HH:MM:SS, respectively (where a "-" negates). Otherwise,
378  the arguments are sent to the to_fraction() function, and the
379  result is converted into a timedelta.
380 
381  @param a object being converted
382  @param b second object, if a time range is sent
383  @param negok if True, negative timespans are allowed
384  @returns a datetime.timedelta"""
385  if isinstance(a,datetime.timedelta): return a
386  if isinstance(a,basestring) and b is None:
387  # 03:14 = three hours
388  try:
389  m=re.search('''(?ix) \A \s* (?P<negative>-)? 0* (?P<hours>\d+)
390  :0*(?P<minutes>\d+)
391  (?: :0*(?P<seconds>\d+(?:\.\d*)?) )?
392  \s*''', a)
393  if m:
394  (hours,minutes,seconds)=(0.,0.,0.)
395  mdict=m.groupdict()
396  if 'hours' in mdict and mdict['hours'] is not None:
397  hours=float(mdict['hours'])
398  if 'minutes' in mdict and mdict['minutes'] is not None:
399  minutes=float(mdict['minutes'])
400  if 'seconds' in mdict and mdict['seconds'] is not None:
401  seconds=float(mdict['seconds'])
402  dt=datetime.timedelta(hours=hours,minutes=minutes,
403  seconds=seconds)
404  if 'negative' in mdict and mdict['negative'] is not None \
405  and mdict['negative']=='-':
406  return -dt
407  return dt
408  except(TypeError,ValueError,AttributeError) as e:
409  # Fall back to to_fraction
410  raise #FIXME: remove this line? Why is this here? It
411  #conflicts with the description.
412  pass
413  f=to_fraction(a,b,negok=negok)
414  (fpart,ipart)=math.modf(f)
415  return datetime.timedelta(seconds=ipart,
416  microseconds=int(round(fpart*1e6)))
417 
418 ########################################################################
419 
420 def minutes_seconds_rest(fraction):
421  """!Splits the given fractions.Fraction of seconds into integer
422  minutes, seconds and fractional remainder <=0.
423 
424  @param fraction the fraction to convert, assumed to be in seconds
425  @returns a tuple (minutes,seconds,rest) as integers"""
426  fraction=to_fraction(fraction)
427  minutes=int(fraction/60)
428  seconds=int(fraction-minutes*60)
429  rest=fraction-minutes*60-seconds
430  return (minutes,seconds,rest)
431 
432 ########################################################################
433 
434 def nearest_datetime(start,target,timestep):
435  """!Return the nearest datetime.datetime to a target.
436 
437  Given a start time, a target time and a timestep, determine the
438  nearest time not earlier than the target that lies exactly on a
439  timestep. Input start and target can be anything understood by
440  to_datetime, and the timestep can be anything understood by
441  to_fraction. Return value is a datetime.datetime object.
442 
443  @param start the fixed start time of allowed return values
444  @param target the time desired
445  @param timestep the times between allowed return values"""
446  dstart=to_datetime(start)
447  dtarget=to_datetime(target)
448  dt=to_fraction(timestep)
449  how_many_dt=int(math.ceil(to_fraction(dtarget-dstart)/dt))
450  return to_datetime_rel(math.floor(how_many_dt*dt),dstart)
451 
452 ########################################################################
453 
454 def is_at_timestep(start,target,timestep):
455  """!Returns True if the target time lies exactly on a timestep, and
456  False otherwise.
457 
458  @param start the fixed start time of allowed return values
459  @param target the time desired
460  @param timestep the times between allowed return values"""
461  span=to_fraction(to_datetime(target)-to_datetime(start))
462  timesteps=span / to_fraction(timestep)
463  return timesteps-int(timesteps)<1e-5
464 
465 ########################################################################
466 
468  """!Converts a timedelta to a string
469 
470  Converts dt to a string of the format "DD:HH:MM:SS+num/den"
471  * DD - number of days
472  * HH - number of hours
473  * MM - minutes
474  * SS - seconds
475  * num/den - fractional part
476  The to_fraction is used to get the fractional part.
477 
478  @param dt anything convertible to a datetime.timedelta."""
479  fdt=to_fraction(to_timedelta(dt))
480  neg=fdt<0
481  if neg: fdt=-fdt
482  (i,n,d) = split_fraction(fdt)
483  seconds=int(i)%60
484  minutes=(int(i)/60)%60
485  hours=int(i)/3600
486  out='%02d:%02d:%02d'%(hours,minutes,seconds)
487  if n!=0:
488  out='%s+%d/%d'%(out,n,d)
489  return out
490 
491 ########################################################################
492 
493 class TimeContainer(object):
494  """!Abstract base class that maps from time to objects.
495 
496  This is the abstract base class of any class that represents a
497  mapping from a set of times to a set of data. It provides the
498  underlying implementation of TimeArray and TimeMapping. This is
499  not exported by "from hwrf.numerics import *" to prevent
500  accidental use."""
501  # Implementation notes:
502  def __init__(self,times,init=None):
503  """!TimeContainer constructor.
504 
505  Initializes the internal arrays for the given list of times,
506  which must not be empty. Note that strange, potentially bad
507  things will happen if there are duplicate times.
508 
509  Implementation notes:
510 
511  @code
512  self._times[N]: a list of times
513  self._data[N]: the data for each time
514  self._assigned[N]: True if there is data for this time,
515  False otherwise
516  @endcode
517 
518  where N is the length of the input times array. The _data
519  will be filled with init() and _assigned filled with True if
520  init is present and not None, otherwise _assigned will be
521  False.
522 
523  @param times the times to map from
524  @param init a constructor or function that creates initial
525  values for all uninitialized array elements"""
526  self._times=times
527  N=len(self._times)
528  if init is None:
529  self._assigned=[False]*N
530  self._data=[None]*N
531  else:
532  self._assigned=[True]*N
533  self._data=[init() for x in xrange(N)]
534  def at_index(self,index):
535  """!Returns the data at the given index, or raises KeyError if
536  no data exists.
537  @param index the desired index"""
538  if not self._assigned[index]: raise KeyError(index)
539  return self._data[index]
540  def index_of(self,when):
541  """!This virtual function should be implemented in a subclass.
542  It should return the index of the time to use for the given
543  time.
544  @param when the time of interest"""
545  raise NotImplementedError(
546  'TimeContainer.index_of is not implemented.')
547  @property
548  def lasttime(self):
549  """!Returns the last time in the array or list of times, even
550  if it has no data."""
551  return self._times[-1]
552  @property
553  def firsttime(self):
554  """!Returns the first time in the array or list of times, even
555  if it has no data."""
556  return self._times[0]
557  def __getitem__(self,when):
558  """!Returns the item at the latest time that is not later than
559  "when". If there is no data assigned at that time, then
560  KeyError is raised.
561  @param when the time of interest"""
562  (iwhen,index)=self.index_of(when)
563  if not self._assigned[index]:
564  raise KeyError(when)
565  return self._data[index]
566  def neartime(self,when,epsilon=None):
567  """!Returns a tuple containing the time nearest to "when"
568  without going over, and the index of that time.
569 
570  @param epsilon If specified, raise
571  hwrf.exceptions.NoNearbyValues if no times are near that
572  value.
573  @param when the time of interest"""
574  (then,index)=self.index_of(when)
575  if epsilon is not None:
576  if abs(to_fraction(then-when,negok=True)) \
577  <= to_fraction(epsilon):
578  return then
579  else:
581  '%s: nearest value not after is %s, which is not '
582  'within %s seconds.'%(str(when),str(then),
583  str(to_fraction(epsilon))))
584  else:
585  return then
586  def get(self,when,default):
587  """!Returns the item at the latest time that is not later than
588  "when."
589 
590  @param when the time of interest
591  @param default If there is no data assigned at that time then the
592  given default is returned."""
593  (when,index)=self.index_of(to_datetime(when))
594  if not self._assigned[index]:
595  return default
596  return self._data[index]
597  def __setitem__(self,when,val):
598  """!Finds the latest time that is not later than "when" in
599  self._times. Assigns "val" to that time.
600  @param when the time of interest
601  @param val the value to assign to that time"""
602  (when,index)=self.index_of(when)
603  try:
604  return self._data.__setitem__(index,val)
605  finally:
606  self._assigned[index]=True
607  def __iter__(self):
608  """!Iterates over all data."""
609  for i in xrange(len(self._times)):
610  if self._assigned[i]:
611  yield self._data[i]
612  def itervalues(self):
613  """!Iterates over data for all known times that have data."""
614  for i in xrange(len(self._times)):
615  if self._assigned[i]:
616  yield self._data[i]
617  def iterkeys(self):
618  """!Iterates over all times that have data."""
619  for i in xrange(len(self._times)):
620  if self._assigned[i]:
621  yield self._times[i]
622  def iteritems(self):
623  """!Iterates over all known times that have data, returning a
624  tuple containing the time and the data at that time."""
625  for i in xrange(len(self._times)):
626  if self._assigned[i]:
627  yield self._times[i],self._data[i]
628  def __reversed__(self):
629  """!Iterates over all known times that have data, in reverse
630  order."""
631  n=len(self._times)
632  for i in xrange(n):
633  j=n-i-1
634  if self._assigned[j]:
635  yield self._data[j]
636  def __delitem__(self,when):
637  """!Finds the latest time that is not later than "when" and
638  removes the data it is mapped to. Later calls to __getitem__,
639  get, __iter__ and so on, will not return any data for this
640  time.
641  @param when the time of disinterest"""
642  (when,index)=self.index_of(when)
643  self._assigned[index]=False
644  self._data[index]=None
645  def __contains__(self,when):
646  """!Finds the latest time that is not later than "when" in
647  self._times. Returns True if there is data mapped to that
648  time and False otherwise.
649  @param when the time of interest"""
650  try:
651  (iwhen,index)=self.index_of(when)
652  return self._assigned[index]
653  except hwrf.exceptions.NotInTimespan,KeyError:
654  return False
655  def __len__(self):
656  """!Returns the number of times that have data."""
657  return len(self._data)
658  def times(self):
659  """!Iterates over all times in this TimeContainer"""
660  for t in self._times:
661  yield t
662  def datatimes(self):
663  """!Iterates over all times in this TimeContainer that map to data."""
664  for i in xrange(len(self._times)):
665  if self._assigned[i]:
666  yield self._times[i]
667  def __str__(self):
668  """!Returns a string representation of this object."""
669  def idxstr(i):
670  t=self._times[i]
671  st=t.strftime("%Y%m%d-%H%M%S")
672  if self._assigned[i]:
673  return "%s:%s"%(st,repr(self._data[i]))
674  else:
675  return "%s:(unassigned)"%(st,)
676  contents=", ".join([ idxstr(i) for i in xrange(len(self._times)) ])
677  return "%s(%s)"% ( self.__class__.__name__, contents )
679  """!Iterates in reverse order over all times in this
680  TimeContainer that map to data."""
681  N=len(self._times)
682  for i in xrange(N):
683  j=N-i-1
684  if self._assigned[j]:
685  yield self._times[j]
686 
687 ########################################################################
688 
690  """!A time-indexed array that can only handle equally spaced times.
691 
692  This implements an array-like object that uses time as an index.
693  It recognizes only discrete times as specified by a start, an end
694  and a timestep. The end is only used as a guideline: if it does
695  not lie exactly on a timestep, the next timestep end is used.
696  Note that all methods in this class have the same meaning as the
697  built-in list class, but accept times as input, except if
698  specified otherwise. In all cases, the time "index" is anything
699  accepted by to_datetime_rel(...,self.start)."""
700  def __init__(self,start,end,timestep,init=None):
701  """!TimeArray constructor.
702 
703  @param start,end,timestep Specifies the equally-spaced times.
704  @param init The initializer for data values. This is typically
705  a constructor (dict, list, etc.) or a function that calls a
706  constructor."""
707  self._start=to_datetime(start)
708  self._end=to_datetime_rel(end,self._start)
709  dt=to_fraction(timestep)
710  self._timestep=dt
711  n=int(to_fraction(self._end-self._start)/self._timestep)+1
712  TimeContainer.__init__(self,[ self._start+to_timedelta(i*dt) for i in xrange(n) ],init=init)
713  # Public accessors. Presently these are just data values, not properties:
714  self.start=self._start
715  self.end=self._end
716  self.timestep=dt
717  ##@var start
718  # Start time. Do not modify.
719 
720  ##@var end
721  # End time. Do not modify.
722 
723  ##@var timestep
724  # Timestep between times. Do not modify.
725 
726  def index_of(self,when):
727  """!Returns the index of the specified time in the internal
728  storage arrays or raises NotInTimespan if the time is not in
729  the timespan.
730  @param when Anything accepted by
731  to_datetime_rel(when,self._start)"""
732  when=to_datetime_rel(when,self._start)
733  index=int(to_fraction(when-self._start)/self._timestep)
734  if index<0 or index>=len(self._data):
736  ('%s: not in range [%s,%s]' % (when.ctime(),
737  self._start.ctime(),self._end.ctime()) )
738  return (self._times[index],index)
739  # def __iter__(self):
740  # """Iterates over all known times."""
741  # for i in xrange(len(self._times)):
742  # yield self._times[i]
743 
744 ########################################################################
745 
747  """!Maps from an ordered list of times to arbitrary data.
748 
749  A TimeMapping is a mapping from an ordered list of times to a set
750  of data. The full set of possible times is given in the
751  constructor: it is not possible to add new times to the mapping
752  after creation of the TimeMapping. A TimeMapping is more
753  expensive than a TimeArray since every [] lookup (__getitem__) has
754  to do a binary search. However, it is more general since the
755  times do not have to be exactly at an interval.
756 
757  Note that a TimeArray can replace a TimeMapping if a small enough
758  timestep (the greatest common factor) is used since most of the
759  TimeContainer methods only work on the indices that have data.
760  However, if some timespans have dense timesteps and the rest are
761  sparse, there may be significant memory and CPU savings in using a
762  TimeMapping."""
763  def __init__(self,times,init=None):
764  """!TimeMapping constructor
765  @param times A list of times, which will be converted with to_datetime and sorted.
766  @param init The initializer for data values. This is typically
767  a constructor (dict, list, etc.) or a function that calls a
768  constructor."""
769  times=sorted([ to_datetime(x) for x in set(times)])
770  TimeContainer.__init__(self,times,init=init)
771  def index_of(self,when):
772  """!Returns the index of the specified time in the internal
773  storage arrays or raises NotInTimespan if the time is not in
774  the timespan.
775  @param when Anything accepted by
776  to_datetime_rel(when,self._start)"""
777  when=to_datetime(when)
778  N=len(self._times)
779  if N==0:
781  'TimeMapping is empty: no data is in an empty timespan.')
782 
783  # Get the bounding times:
784  iearly=0
785  early=self._times[iearly]
786  ilate=N-1
787  late=self._times[ilate]
788 
789  # See if we're outside the bounding times:
790  if when<early:
792  'Time %s is not earlier than first time (%s) in TimeMapping'
793  % ( str(when),str(early) ) )
794  elif when>=late:
795  return ( self._times[ilate], ilate )
796 
797  # Binary search for the bounding indexes:
798  while ilate>iearly+1:
799  imiddle=(iearly+ilate)/2
800  middle=self._times[imiddle]
801  if when<middle:
802  (ilate,late)=(imiddle,middle)
803  else:
804  (iearly,early)=(imiddle,middle)
805  return ( self._times[iearly], iearly )
def __init__
TimeContainer constructor.
Definition: numerics.py:502
def __init__
TimeMapping constructor.
Definition: numerics.py:763
def neartime
Returns a tuple containing the time nearest to "when" without going over, and the index of that time...
Definition: numerics.py:566
def to_timedelta
Converts an object to a datetime.timedelta.
Definition: numerics.py:371
def __len__(self)
Returns the number of times that have data.
Definition: numerics.py:655
def fcst_hr_min(time, start)
Return forecast time in hours and minutes.
Definition: numerics.py:126
def to_fraction
Converts an object or two to a fraction.
Definition: numerics.py:269
def to_datetime_rel(d, rel)
Converts objects to a datetime relative to another datetime.
Definition: numerics.py:319
def times(self)
Iterates over all times in this TimeContainer.
Definition: numerics.py:658
def split_fraction(f)
Splits a fraction into components.
Definition: numerics.py:184
def datatimes(self)
Iterates over all times in this TimeContainer that map to data.
Definition: numerics.py:662
def nearest_datetime(start, target, timestep)
Return the nearest datetime.datetime to a target.
Definition: numerics.py:434
def iteritems(self)
Iterates over all known times that have data, returning a tuple containing the time and the data at t...
Definition: numerics.py:622
def __call__(self, a, b)
Determine the ordering of a and b.
Definition: numerics.py:86
def __init__
partial_ordering constructor.
Definition: numerics.py:42
timestep
Timestep between times.
Definition: numerics.py:716
def within_dt_epsilon(time1, time2, epsilon)
Returns True if time1 is within epsilon of time2, and False otherwise.
Definition: numerics.py:198
def minutes_seconds_rest(fraction)
Splits the given fractions.Fraction of seconds into integer minutes, seconds and fractional remainder...
Definition: numerics.py:420
def timedelta_epsilon
Decides a reasonable epsilon for time equality comparisons.
Definition: numerics.py:220
def __str__(self)
Returns a string representation of this object.
Definition: numerics.py:667
def itervalues(self)
Iterates over data for all known times that have data.
Definition: numerics.py:612
def lasttime(self)
Returns the last time in the array or list of times, even if it has no data.
Definition: numerics.py:548
def firsttime(self)
Returns the first time in the array or list of times, even if it has no data.
Definition: numerics.py:553
Abstract base class that maps from time to objects.
Definition: numerics.py:493
def str_timedelta(dt)
Converts a timedelta to a string.
Definition: numerics.py:467
order
Internal ordering information.
Definition: numerics.py:59
def to_datetime(d)
Converts the argument to a datetime.
Definition: numerics.py:346
def get(self, when, default)
Returns the item at the latest time that is not later than "when.".
Definition: numerics.py:586
def randint_zeromean
Generates "count" numbers uniformly distributed between -imax and imax, inclusive, with a mean of zero.
Definition: numerics.py:146
def datatimes_reversed(self)
Iterates in reverse order over all times in this TimeContainer that map to data.
Definition: numerics.py:678
def __getitem__(self, when)
Returns the item at the latest time that is not later than "when".
Definition: numerics.py:557
Raised when an operation has a set of known times, but another provided time is not near one of those...
Definition: exceptions.py:203
Raised when a timespan was expected, but none was available.
Definition: exceptions.py:242
backupcmp
Backup comparison function for tiebreaking.
Definition: numerics.py:60
Raised when a timestep is invalid, such as a negative timestep for a situation that requires a positi...
Definition: exceptions.py:178
def __contains__(self, when)
Finds the latest time that is not later than "when" in self._times.
Definition: numerics.py:645
A time-indexed array that can only handle equally spaced times.
Definition: numerics.py:689
def __setitem__(self, when, val)
Finds the latest time that is not later than "when" in self._times.
Definition: numerics.py:597
Sorts a pre-determined list of objects, placing unknown items at a specified location.
Definition: numerics.py:21
def __delitem__(self, when)
Finds the latest time that is not later than "when" and removes the data it is mapped to...
Definition: numerics.py:636
def is_at_timestep(start, target, timestep)
Returns True if the target time lies exactly on a timestep, and False otherwise.
Definition: numerics.py:454
Maps from an ordered list of times to arbitrary data.
Definition: numerics.py:746
def __reversed__(self)
Iterates over all known times that have data, in reverse order.
Definition: numerics.py:628
Exceptions raised by the hwrf package.
Definition: exceptions.py:1
def at_index(self, index)
Returns the data at the given index, or raises KeyError if no data exists.
Definition: numerics.py:534
def index_of(self, when)
This virtual function should be implemented in a subclass.
Definition: numerics.py:540
Raised when a time is outside the range of times being processed by a function.
Definition: exceptions.py:200
unordered
Unordered element index.
Definition: numerics.py:62
def __init__
TimeArray constructor.
Definition: numerics.py:700
def index_of(self, when)
Returns the index of the specified time in the internal storage arrays or raises NotInTimespan if the...
Definition: numerics.py:771
def great_arc_dist(xlon1, ylat1, xlon2, ylat2)
Great arc distance between two points on Earth.
Definition: numerics.py:99
def iterkeys(self)
Iterates over all times that have data.
Definition: numerics.py:617
def index_of(self, when)
Returns the index of the specified time in the internal storage arrays or raises NotInTimespan if the...
Definition: numerics.py:726
def __iter__(self)
Iterates over all data.
Definition: numerics.py:607