1 """!Time manipulation and other numerical routines.
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
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']
16 import fractions,math,datetime,re,random
22 """!Sorts a pre-determined list of objects, placing unknown items
23 at a specified location.
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:
32 p=partial_ordering([3,2,1]) # list is ordered as [3,2,1] with
33 # everything else after that
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
42 def __init__(self,ordering,unordered=None,backupcmp=cmp):
43 """!partial_ordering constructor.
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"
56 @param ordering the ordering of known objects
57 @param unordered Optional: where to put other objects
58 @param backupcmp Tiebreaker comparison function."""
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."""
100 """!Great arc distance between two points on Earth.
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
110 flattening_inv=298.247
112 rlat1=float(ylat1)*deg2rad
113 rlon1=float(xlon1)*deg2rad
114 rlat2=float(ylat2)*deg2rad
115 rlon2=float(xlon2)*deg2rad
117 Rearth1=Requator*(1.0-math.sin(rlat1)**2.0/flattening_inv)
118 Rearth2=Requator*(1.0-math.sin(rlat2)**2.0/flattening_inv)
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)))
127 """!Return forecast time in hours and minutes.
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)"""
137 fhours=dt.days*24 + dt.seconds/3600 + dt.microseconds/3600e6
138 (fpart,ihours)=math.modf(fhours)
141 iminutes=round(fpart*60)
142 return (ihours,iminutes)
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"""
154 if randomizer
is None:
155 randint=random.randint
157 randint=randomizer.randint
158 if imax!=0
and not ( -imax < imax):
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) ]
168 icen=randint(0,count-1)
174 icen=randint(0,count-1)
185 """!Splits a fraction into components.
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.
191 @returns a tuple (integer,numerator,denominator)"""
194 return (i,int(f2.numerator),int(f2.denominator))
199 """!Returns True if time1 is within epsilon of time2, and False
201 @param time1,time2 the times being compared
202 @param epsilon how close they need to be in order to be
204 @returns True if the times are within epsilon of each other,
206 if not isinstance(time2,datetime.datetime):
209 if not isinstance(time1,datetime.datetime):
212 dt=fractions.Fraction(dt.microseconds,1000000)+\
213 dt.seconds+24*3600*dt.days
214 if not isinstance(epsilon,fractions.Fraction):
216 return abs(dt) < abs(epsilon)
221 """!Decides a reasonable epsilon for time equality comparisons.
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).
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)
247 numerator=int(numerator)
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
261 'Too few non-identical times in input, and no default '
262 'specified. Cannot compute timespan in timedelta_epsilon.',
270 """!Converts an object or two to a fraction.
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.
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
291 to_fraction(1,10000000) # Valid as a fraction, even though value
294 @param a,b the objects to convert
295 @param negok if True, negative numbers are okay."""
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):
303 m=re.match(
'\s*(?P<ipart>[+-]?\d+)\s*(?P<num>[+-]\d+)\s*/\s*(?P<den>\d+)',a)
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)
310 result=fractions.Fraction(a)
311 if not negok
and not result>=0:
313 (
'to_fraction(%s,%s) resulted in a negative timestep' \
320 """!Converts objects to a datetime relative to another datetime.
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):
330 if isinstance(d,datetime.datetime):
332 elif isinstance(d,datetime.timedelta):
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)):
337 return datetime.datetime.strptime(d,
'%Y%m%d%H')
339 return datetime.datetime.strptime(d,
'%Y%m%d%H%M')
341 return datetime.datetime.strptime(d,
'%Y-%m-%d %H:%M:%S')
347 """!Converts the argument to a datetime.
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):
357 elif isinstance(d,basestring):
359 return datetime.datetime.strptime(d,
'%Y%m%d%H')
361 return datetime.datetime.strptime(d,
'%Y%m%d%H%M')
363 return datetime.datetime.strptime(d,
'%Y-%m-%d %H:%M:%S')
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),))
372 """!Converts an object to a datetime.timedelta.
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.
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:
389 m=re.search(
'''(?ix) \A \s* (?P<negative>-)? 0* (?P<hours>\d+)
391 (?: :0*(?P<seconds>\d+(?:\.\d*)?) )?
394 (hours,minutes,seconds)=(0.,0.,0.)
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,
404 if 'negative' in mdict
and mdict[
'negative']
is not None \
405 and mdict[
'negative']==
'-':
408 except(TypeError,ValueError,AttributeError)
as e:
414 (fpart,ipart)=math.modf(f)
415 return datetime.timedelta(seconds=ipart,
416 microseconds=int(round(fpart*1e6)))
421 """!Splits the given fractions.Fraction of seconds into integer
422 minutes, seconds and fractional remainder <=0.
424 @param fraction the fraction to convert, assumed to be in seconds
425 @returns a tuple (minutes,seconds,rest) as integers"""
427 minutes=int(fraction/60)
428 seconds=int(fraction-minutes*60)
429 rest=fraction-minutes*60-seconds
430 return (minutes,seconds,rest)
435 """!Return the nearest datetime.datetime to a target.
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.
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"""
449 how_many_dt=int(math.ceil(
to_fraction(dtarget-dstart)/dt))
455 """!Returns True if the target time lies exactly on a timestep, and
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"""
463 return timesteps-int(timesteps)<1e-5
468 """!Converts a timedelta to a string
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
475 * num/den - fractional part
476 The to_fraction is used to get the fractional part.
478 @param dt anything convertible to a datetime.timedelta."""
484 minutes=(int(i)/60)%60
486 out=
'%02d:%02d:%02d'%(hours,minutes,seconds)
488 out=
'%s+%d/%d'%(out,n,d)
494 """!Abstract base class that maps from time to objects.
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
503 """!TimeContainer constructor.
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.
509 Implementation notes:
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,
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
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"""
533 self.
_data=[init()
for x
in xrange(N)]
535 """!Returns the data at the given index, or raises KeyError if
537 @param index the desired index"""
538 if not self.
_assigned[index]:
raise KeyError(index)
539 return self.
_data[index]
541 """!This virtual function should be implemented in a subclass.
542 It should return the index of the time to use for the given
544 @param when the time of interest"""
545 raise NotImplementedError(
546 'TimeContainer.index_of is not implemented.')
549 """!Returns the last time in the array or list of times, even
550 if it has no data."""
554 """!Returns the first time in the array or list of times, even
555 if it has no data."""
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
561 @param when the time of interest"""
565 return self.
_data[index]
567 """!Returns a tuple containing the time nearest to "when"
568 without going over, and the index of that time.
570 @param epsilon If specified, raise
571 hwrf.exceptions.NoNearbyValues if no times are near that
573 @param when the time of interest"""
575 if epsilon
is not None:
581 '%s: nearest value not after is %s, which is not '
582 'within %s seconds.'%(str(when),str(then),
586 def get(self,when,default):
587 """!Returns the item at the latest time that is not later than
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."""
596 return self.
_data[index]
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"""
604 return self._data.__setitem__(index,val)
608 """!Iterates over all data."""
609 for i
in xrange(len(self.
_times)):
613 """!Iterates over data for all known times that have data."""
614 for i
in xrange(len(self.
_times)):
618 """!Iterates over all times that have data."""
619 for i
in xrange(len(self.
_times)):
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)):
629 """!Iterates over all known times that have data, in reverse
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
641 @param when the time of disinterest"""
644 self.
_data[index]=
None
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"""
656 """!Returns the number of times that have data."""
657 return len(self.
_data)
659 """!Iterates over all times in this TimeContainer"""
663 """!Iterates over all times in this TimeContainer that map to data."""
664 for i
in xrange(len(self.
_times)):
668 """!Returns a string representation of this object."""
671 st=t.strftime(
"%Y%m%d-%H%M%S")
673 return "%s:%s"%(st,repr(self.
_data[i]))
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."""
690 """!A time-indexed array that can only handle equally spaced times.
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)."""
701 """!TimeArray constructor.
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
712 TimeContainer.__init__(self,[ self.
_start+
to_timedelta(i*dt)
for i
in xrange(n) ],init=init)
727 """!Returns the index of the specified time in the internal
728 storage arrays or raises NotInTimespan if the time is not in
730 @param when Anything accepted by
731 to_datetime_rel(when,self._start)"""
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)
747 """!Maps from an ordered list of times to arbitrary data.
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.
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
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
769 times=sorted([
to_datetime(x)
for x
in set(times)])
770 TimeContainer.__init__(self,times,init=init)
772 """!Returns the index of the specified time in the internal
773 storage arrays or raises NotInTimespan if the time is not in
775 @param when Anything accepted by
776 to_datetime_rel(when,self._start)"""
781 'TimeMapping is empty: no data is in an empty timespan.')
792 'Time %s is not earlier than first time (%s) in TimeMapping'
793 % ( str(when),str(early) ) )
795 return ( self.
_times[ilate], ilate )
798 while ilate>iearly+1:
799 imiddle=(iearly+ilate)/2
800 middle=self.
_times[imiddle]
802 (ilate,late)=(imiddle,middle)
804 (iearly,early)=(imiddle,middle)
805 return ( self.
_times[iearly], iearly )
def __init__
TimeContainer constructor.
def __init__
TimeMapping constructor.
def neartime
Returns a tuple containing the time nearest to "when" without going over, and the index of that time...
def to_timedelta
Converts an object to a datetime.timedelta.
def __len__(self)
Returns the number of times that have data.
def fcst_hr_min(time, start)
Return forecast time in hours and minutes.
def to_fraction
Converts an object or two to a fraction.
def to_datetime_rel(d, rel)
Converts objects to a datetime relative to another datetime.
def times(self)
Iterates over all times in this TimeContainer.
def split_fraction(f)
Splits a fraction into components.
def datatimes(self)
Iterates over all times in this TimeContainer that map to data.
def nearest_datetime(start, target, timestep)
Return the nearest datetime.datetime to a target.
def iteritems(self)
Iterates over all known times that have data, returning a tuple containing the time and the data at t...
def __call__(self, a, b)
Determine the ordering of a and b.
def __init__
partial_ordering constructor.
timestep
Timestep between times.
def within_dt_epsilon(time1, time2, epsilon)
Returns True if time1 is within epsilon of time2, and False otherwise.
def minutes_seconds_rest(fraction)
Splits the given fractions.Fraction of seconds into integer minutes, seconds and fractional remainder...
def timedelta_epsilon
Decides a reasonable epsilon for time equality comparisons.
def __str__(self)
Returns a string representation of this object.
def itervalues(self)
Iterates over data for all known times that have data.
def lasttime(self)
Returns the last time in the array or list of times, even if it has no data.
def firsttime(self)
Returns the first time in the array or list of times, even if it has no data.
Abstract base class that maps from time to objects.
def str_timedelta(dt)
Converts a timedelta to a string.
order
Internal ordering information.
def to_datetime(d)
Converts the argument to a datetime.
def get(self, when, default)
Returns the item at the latest time that is not later than "when.".
def randint_zeromean
Generates "count" numbers uniformly distributed between -imax and imax, inclusive, with a mean of zero.
def datatimes_reversed(self)
Iterates in reverse order over all times in this TimeContainer that map to data.
def __getitem__(self, when)
Returns the item at the latest time that is not later than "when".
Raised when an operation has a set of known times, but another provided time is not near one of those...
Raised when a timespan was expected, but none was available.
backupcmp
Backup comparison function for tiebreaking.
Raised when a timestep is invalid, such as a negative timestep for a situation that requires a positi...
def __contains__(self, when)
Finds the latest time that is not later than "when" in self._times.
A time-indexed array that can only handle equally spaced times.
def __setitem__(self, when, val)
Finds the latest time that is not later than "when" in self._times.
Sorts a pre-determined list of objects, placing unknown items at a specified location.
def __delitem__(self, when)
Finds the latest time that is not later than "when" and removes the data it is mapped to...
def is_at_timestep(start, target, timestep)
Returns True if the target time lies exactly on a timestep, and False otherwise.
Maps from an ordered list of times to arbitrary data.
def __reversed__(self)
Iterates over all known times that have data, in reverse order.
Exceptions raised by the hwrf package.
def at_index(self, index)
Returns the data at the given index, or raises KeyError if no data exists.
def index_of(self, when)
This virtual function should be implemented in a subclass.
Raised when a time is outside the range of times being processed by a function.
unordered
Unordered element index.
def __init__
TimeArray constructor.
def index_of(self, when)
Returns the index of the specified time in the internal storage arrays or raises NotInTimespan if the...
def great_arc_dist(xlon1, ylat1, xlon2, ylat2)
Great arc distance between two points on Earth.
def iterkeys(self)
Iterates over all times that have data.
def index_of(self, when)
Returns the index of the specified time in the internal storage arrays or raises NotInTimespan if the...
def __iter__(self)
Iterates over all data.