Map: 1-in, 1-out

Notational Conventions

The j-th element of a stream s is s[j]. The suffix e to refers to functions that operate on an element of a stream, and w for functions on sliding windows of streams. For example, map_e refers to the map function element by element whereas map_w refers to functions that operate on sliding windows of the stream.

We convert a terminating function f that operates on conventional Python bounded-sized to a non-terminating function that operates on endless streams by decorating f. IoTPy has many decorators. We start with decorators map_e and map_w for procedures, and their functional variants fmap_e and fmap_w. The decorators, map_e and map_w, are used to get procedures that operate on streams whereas fmap_e and fmap_w are used to get functions that return streams.

MAP

Suppose you are given a conventional Python function, g, that terminates and operates on a conventional Python bounded-size object such as a list or an array. You want to create a procedure, f, that reads a (possibly endless) stream, x, and which does the following: Whenever a value v is appended to stream x, the procedure appends g(v) to stream y. You can define f as follows:

@map_e
def f(v): return g(v)
f(in_stream=x, out_stream=y)

The decorated function, f, has two parameters, an input stream, in_stream, and an output stream, out_stream. As more values are appended to x, more values are appended to y, and so the lengths of the streams can get arbitrarily large. Let Y[n] be the n-th value appended by the procedure to stream y; then:

Y[n] = f(x[n])

For example, given streams x and y, the decorated function double (below) appends 2*v to stream y for every value v in stream x. So, in this example Y[n] = 2*X[n].

@map_e
def double(v): return 2*v
double(in_stream=x, out_stream=y)

STATE

You can pass a state to a function by including the parameter called state in the call and providing its initial value, as in:

@map_e
def f(v, state): return g(v,state), h(v,state)
f(in_stream=x, out_stream=y, state=initial_state)

Let state[n] be the n-th value of state, for n = 0, 1, 2,… Then:

state[0] = initial_state
Y[n] = g(x[n], state[n])
state[n+1] = h(x[n], state[n])

where Y[n] is the n-th value appended by the procedure to stream y.

EXAMPLE

@map_e
def deltas(input_value, state):
  difference = input_value - state
  next_state = input_value
  return difference, next_state
deltas(in_stream=x, out_stream=y, state=0)

The above code ensures state[0] = 0 because the call to deltas has the term, state=0. When the procedure starts its n-th step, for n = 0, 1, 2, …, input_value = x[n], and state[n]=x[n-1] for n > 0, and state[0]=0.

The second value returned by the function is the next state of the procedure. state[n+1]=x[n] because the second value returned by the function is next_state which has value input_value or x[n].

The first value, difference, returned by the function is appended to the output stream. At the n-th step, the value Y[n] appended to stream y, is given by Y[n] = x[n] - state[n-1]. This is because difference = input_value - state, which is x[n] - state[n-1] on the n-th step.

Therefore, Y[0] = x[0], and Y[n] = x[n] - x[n-1].

A less verbose style of writing the above function is:

@map_e
def deltas(u, v): return u - v, u
deltas(x, y, state=0)

Note that you must explicitly use the keyword state in the call if the procedure has state. This is because state is a keyword parameter.

KEYWORD PARAMETERS OTHER THAN STATE

You can pass an arbitrary number of keyword parameters to a function as shown in the following example which has a keyword parameter called multiplicand. The call ensures y[n] = 3*x[n].

@map_e
def multiply(v, multiplicand): return v*multiplicand
multiply(in_stream=x, out_stream=y, multiplicand=3)

Note that the call to multiply must have the keyword multiplicand, though in_stream and out_stream can be positional arguments as in:

multiply(x, y, multiplicand=3)

KEYWORD PARAMETERS WITH STATE

You can have both keyword parameters and state as in the following example.

@map_e
def anomalous_change(v, state, max_change):
   delta = v - state
   next_state = v
   next_output = True if delta > max_change else False
return next_output, next_state

Given streams x and y, if you call:

anomalous_change(in_stream=x, out_stream=y, state=0, max_change=10)

Then the initial state, s[0] is 0 and the state s[n+1] after n elements of x have been read is x[n]. The output stream y contains Boolean values where y[n] is True to indicate that the change, x[n+1] - x[n], in x exceeds the keyword parameter max_change.

y[0] = (x[0] > max_change)
y[n+1] = ((x[n+1] - x[n]) > max_change)

Click here to download examples of @map_e and @fmap_e or download it from GitHub at IoTPy/examples/op/examples_map_e.py.

WINDOWS

IoTPy has multiple windowing operations. We begin with windows specified by a fixed size and fixed step size. A window is a list. (Later, we will describe windows that are NumPy arrays.) A window is specified by a window_size and step_size. The length of a window is its window_size. A sliding window is moved forward by its step_size. The j-th window specified of a stream x is:

x[j*step_size : j*step_size + window_size] for j >= 0

For example, if window_size is 3 and step_size is 2 then the sequence of windows is the following sequence of lists:

x[0, 1, 2],  x[2, 3, 4],  x[4, 5, 6], …

Use the decorator @map_w for mapping operations on windows.

@map_w
def f(window): return g(window)
f(in_stream=x, out_stream=y, window_size=w, step_size=s)

This call sets:

y[n] = g(x[n*s : n*s+w])

For example,

@map_w
def sum_window(window): return sum(window)
sum_window(in_stream=x, out_stream=y, window_size=2,      step_size=2)

sets

y[n] = x[2*n] + x[2*n+1]

You can use keywords and state for @map_w in exactly the same way as for @map_e.

Functional Form

The functional form obtained with the decorator @fmap_e can be more convenient in some cases than the procedural form obtained by @map_e, as illustrated in the following example.

@fmap_e
def f(v): return g(v)
y=f(x)

and

@map_e
def h(v): return g(v)
h(in_stream=x, out_stream=z)

The relationship between @fmap_e and @map_e is similar to the relationship between @f_add and @r_add.

  1. In the functional form with @fmap_e the output stream is created by the call to f whereas in the procedural form with @map_e , the output stream must be declared before the call to f and is passed as a parameter to f.

  2. The n-th value Z[n] appended to the output stream z by the procedure is the n-th value returned by the function f.

The functional form gives you the convenience of using functional notation such as:

z = f(g(x,y) * h(w,x,y))

where w, x, y, z are streams.

You can use keyword parameters and state with the functional form in the exactly the same way as for the procedural form.

Functional Form with Windows

The decorator @fmap_w is the functional form of @map_w in exactly the same way that @fmap_e is the functional form of @map_e.

@fmap_w
def f(window): return g(window)
y = f(x, window_size=w, step_size=s)

is the functional version of the procedure:

@map_w
def f(window): return g(window)
f(in_stream=x, out_stream=y, window_size=w, step_size=s)

Click here to download examples of @map_w and @fmap_w or download the examples from GitHub at IoTPy/examples/op/examples_map_w.py. Another simple example is in IoTPy/examples/signal_processing_examples/window_dot_product.py.

Let’s look at an example from acoustics: generating an echo: Example. Acoustics Echo.