Skip to content

Commit

Permalink
Merge pull request #12 from ashilgard/master
Browse files Browse the repository at this point in the history
fixed documentation for simulatedtimeseries
  • Loading branch information
ashilgard authored Nov 1, 2016
2 parents 878055a + d4edab3 commit 62f12b3
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 40 deletions.
26 changes: 8 additions & 18 deletions timeseries/lazy.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
class LazyOperation():
def __init__(self, function, *args, **kwargs):
'''Inits a LazyOperation that stores the provided function and arguments'''
self._function = function
self._args = args
self._kwargs = kwargs

def eval(self):
# Recursively eval() lazy args
'''Recursively evaluates all of the lazy arguments'''
new_args = [a.eval() if isinstance(a,LazyOperation) else a for a in self._args]
new_kwargs = {k:v.eval() if isinstance(v,LazyOperation) else v for k,v in self._kwargs}
return self._function(*new_args, **new_kwargs)

# Debug:
def thunk_tree(self, indent='| '):
s = indent[:-2]+'| ['+self._function.__name__+']\n'
for a in self._args:
if isinstance(a, LazyOperation):
s += a.thunk_tree(indent=indent+'| ')
else:
s += indent+'| '+str(a)+'\n'
for k,v in self._kwargs:
if isinstance(a, LazyOperation):
s += str(k)+'='+v.thunk_tree(indent=indent+'| ')
else:
s += indent+'| '+str(k)+'='+str(v)+'\n'
return s

def lazy(function):
'''A decorator to create a lazy version of a function. Stores the function
and arguments in a thunk for later evaluation'''
def create_thunk(*args, **kwargs):
return LazyOperation(function, *args, **kwargs)
return create_thunk

@lazy
def lazy_add(a,b):
return a+b
'''Lazy addition. Stores arguments and function for later evaluation'''
return a+b

@lazy
def lazy_mul(a,b):
return a*b
'''Lazy multiplication. Stores arguments and function for later evaluation'''
return a*b
127 changes: 105 additions & 22 deletions timeseries/timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,55 @@ def itertimes(self):
'''Iterate over times'''

class SizedContainerTimeSeriesInterface(TimeSeriesInterface):

def __init__(self, time_points, data_points):
'''Raises an exception if either parameter is not a sequence
or if `time_points` and `data_points` are not the same length'''

# Raise an exception if any parameter is not a sequence
params = {'time_points': time_points,
'data_points': data_points}
for p in params:
try:
iter(params[p])
except:
raise TypeError('`%s` must be a sequence.' % p)


# Raise an exception if `time_points` and `data_points` are not the same length

if len(list(time_points)) != len(list(data_points)):
raise ValueError('`time_points` and `data_points` must have the same length.')

def __getitem__(self, key):
'''Returns the data point from the TimeSeries with index = key'''
return self._data[key]

def __setitem__(self, key, value):
'''Sets the data point from the TimeSeries with index = key to value'''

self._data[key] = value

def __repr__(self):
return str(self)

def __str__(self):
'''Prints a more user-friendly interpretation of the SizedContainerTimeSeriesInterface'''

format_str = '{}([{}])'
row_str = '[{}\t{}]'
add_str = ''

for pts in self.iteritems():
add_str += row_str.format(pts[0], pts[1])
class_name = type(self).__name__
return format_str.format(class_name, add_str)

def interpolate(self, pts):
'''Generates new interpolated values for a TimeSeries given unseen times.
Uses stationary boundary conditions: if a new time point is smaller than the
first existing time point, returns the first value; likewise for larger time points.
Args:
pts: a list of time values to create interpolated points for
Returns:
A new TimeSeries with the provided times and their interpolated values.'''
inter_pts = []
ts = list(pts)
for pt in ts:
Expand All @@ -69,16 +81,21 @@ def interpolate(self, pts):
return TimeSeries(ts, inter_pts)

def __abs__(self):
'''Returns the two-norm of the value vector of the TimeSeries'''
return math.sqrt(sum(x**2 for x in self))

def __bool__(self):
'''Checks if the value vector is of length zero. If so, returns false.
Otherwise returns true.'''
return bool(abs(self))

def _check_time_values(function):
'''A Decorator to check if the rhs is either a TimeSeries with the same
time values or a real number. If neither, raises an appropriate error'''
def _check_time_values_helper(self , rhs):
print(rhs)
try:
if isinstance(rhs, numbers.Real):
if isinstance(rhs, numbers.Real):
return function(self, rhs)
elif len(self) != len(rhs) or not all(t1 == t2 for t1, t2 in zip(self.itertimes(), rhs.itertimes())):
raise ValueError('Both time series must have the same time points.')
Expand All @@ -97,62 +114,109 @@ def __pos__(self):

@_check_time_values
def __eq__(self, other):
'''Determines if two TimeSeries are equal or if all values of a TimeSeries are
equal to a real number.
Args:
other: either a real number or another TimeSeries. If using another TimeSeries,
it must have the same time values
Returns:
True if all (time,value) tuples of the two TimeSeries are the same, or if all
values of the TimeSeries are equal to the real number.
False otherwise.'''

if isinstance(other, numbers.Real):
return (all(x == numbers.Real for x in iter(self)))
else:
return (all(x == y for x, y in zip(iter(self), iter(other))))

@_check_time_values
def __add__(self, other):
'''Adds either a real number or another TimeSeries to the TimeSeries.
Args:
other: either a real number or another TimeSeries. If using another TimeSeries,
it must have the same time values
Returns:
A new TimeSeries with the same times and either an elementwise addition
with the real number or elementwise addition with the values of the other TimeSeries'''

if isinstance(other, numbers.Real):
return TimeSeries(list(self.itertimes()), [x + other for x in iter(self)])
else:
return TimeSeries(list(self.itertimes()), [x + y for x, y in zip(iter(self), iter(other))])

@_check_time_values
def __sub__(self, other):
'''Subtracts either a real number or another TimeSeries from the TimeSeries.
Args:
other: either a real number or another TimeSeries. If using another TimeSeries,
it must have the same time values
Returns:
A new TimeSeries with the same times and either an elementwise subtraction
of the real number or elementwise subtraction of the values of the other TimeSeries'''

if isinstance(other, numbers.Real):
return TimeSeries(list(self.itertimes()), [x - other for x in iter(self)])
else:
return TimeSeries(list(self.itertimes()), [x - y for x, y in zip(iter(self), iter(other))])

@_check_time_values
def __mul__(self, other):
'''Multiplies either a real number or another TimeSeries by the TimeSeries.
Args:
other: either a real number or another TimeSeries. If using another TimeSeries,
it must have the same time values
Returns:
A new TimeSeries with the same times and either an elementwise multiplication
by the real number or elementwise multiplication by the values of the other TimeSeries'''

if isinstance(other, numbers.Real):
return TimeSeries(list(self.itertimes()), [x * other for x in iter(self)])
else:
return TimeSeries(list(self.itertimes()), [x * y for x, y in zip(iter(self), iter(other))])


def iteritems(self):
return iter(zip(self.itertimes(), iter(self)))

'''Returns an iterator over the TimeSeries times'''
return iter(zip(self.itertimes(), iter(self)))

@property
def lazy(self):
'''Returns a new LazyOperation instance using an identity function
and self as the only argument. This wraps up the TimeSeries instance
and a function which does nothing and saves them both for later.'''
return LazyOperation(lambda x: x, self)

class TimeSeries(SizedContainerTimeSeriesInterface):
def __init__(self, time_points, data_points):

'''Inits a TimeSeries with time_points and data_points.
Stores these as lists.'''
super().__init__(time_points, data_points)
self._times = list(time_points)
self._data = list(data_points)

def __len__(self):
return len(self._times)
return len(self._times)

def __iter__(self):
'''Returns an iterator over the TimeSeries values'''
return iter(self._data)

def itertimes(self):
return iter(self._times)

'''Returns an iterator over the TimeSeries times'''
return iter(self._times)

class ArrayTimeSeries(TimeSeries):

def __init__(self, time_points, data_points):
'''Inits a TimeSeries with time_points and data_points.
Stores these as numpy arrays with extra space in the length of
the provided arrays.'''

super().__init__(time_points, data_points)

self._length = len(time_points)
self._times = np.empty(self._length * 2)
self._data = np.empty(self._length * 2)
Expand All @@ -163,11 +227,13 @@ def __len__(self):
return self._length

def __getitem__(self, key):
'''Returns the data point from the TimeSeries with index = key'''
if key >= self._length:
raise IndexError('ArrayTimeSeries index out of range.')
return self._data[key]

def __setitem__(self, key, value):
'''Sets the data point from the TimeSeries with index = key to value'''
if key >= self._length:
raise IndexError('ArrayTimeSeries index out of range.')
self._data[key] = value
Expand All @@ -189,24 +255,32 @@ class StreamTimeSeriesInterface(TimeSeriesInterface):

@abc.abstractmethod
def produce(self)->list:
"intersection with another set"
'''Generate (time, value) tuples'''

class SimulatedTimeSeries(StreamTimeSeriesInterface):
'''Creates a Simulated TimeSeries with no internal storage
that yields data from a supplied generator, either with or
without times provided'''


def __init__(self, generator):
'''Inits SimulatedTimeSeries with a value or (time,value) generator'''
self._gen = generator

def __iter__(self):
'''Returns an iterator that gets a new value from produce'''
return self

def __next__(self):
return self.produce()
return self.produce()[1]

def iteritems(self):
return self.produce()[1]
'''Returns an iterator that gets a new (time,value) tuple from produce'''
yield self.produce()

def itertimes(self):
return self.produce()[0]
'''Returns an iterator that gets a new time from produce'''
yield self.produce()[0]

def __repr__(self):
format_str = '{}([{}])'
Expand All @@ -215,9 +289,18 @@ def __repr__(self):
return format_str.format(class_name, str(self._gen))

def produce(self, chunk=1):
'''Generates up to chunk (time, value) tuples. If optional time is not
provided, adds an integer timestamp to value
Args:
chunk: the number of tuples produce generates
Returns:
chunk # of (time, value) tuples'''

for i in range(chunk):
value = next(self._gen)
if type(value) == tuple:
return value
else:
return (datetime.datetime.now(), value)
return (int(datetime.datetime.now().timestamp()), value)

0 comments on commit 62f12b3

Please sign in to comment.