Tutorial - Creating SQs from Vanilla Python Functions

The first step in our application is to query our magnetometer to check if a car is near the sensor.

Let’s say the road is on a long highway where it is unexpected that the speed will change dramatically over 5 meters. We will model the inductance of our magnetometer with a negative cosine wave shifted vertically to avoid negative values. Our function for getting the magnetometer is shown below:

def sample_magnetometer(A, f, phi):
    from math import cos, pi

    return A - (A * cos(get_time() * 2 * f / pi + phi)))

Note that the sample_magnetometer has imports within the function body. These are necessary as SQs are not guaranteed to be on the same ensemble as its calling SQ, so each SQ requires its own environment and modules to be self-contained.

We won’t go into depth on get_time(), but we’ll assume get_time() will be responsible for generating increasing times so sample_magnetometer will produce different values over subsequent function calls.

As stated before, sample_magnetometer has been provided by other developers for general use. The function itself is purely written in Python without any direct mention of TTPython constructs. The simplest way to include this Python function into TTPython is using the SQify function decorator around sample_magnetometer, as shown below.

@SQify
def sample_magnetometer(A, f, phi):
    ...

@SQify takes a Python function and inserts it as a SQ into our dataflow graph. The SQ defines the basic unit of computation in TTPython. The graph execution of our program is transparent to he programmer; they do not need to worry about converting data to tokens to communicate between SQ. When the SQ has the tokens it needs to run, it will provide the values in those tokens as arguments to the supplied function.

A common error is to overuse @SQify over each separate function in an already defined Python program. This is unnecessary as @SQify treats the underlying wrapped function as a black box. The contained function should not have any mention of TTPython constructs. For example,

@SQify
def sample_magnetometer(A, f, phi):
    from math import cos, pi

    return A - (A * cos(get_time() * 2 * f / pi + phi)))

# correct
def get_time():
    ...

is correct, while

@SQify
def sample_magnetometer(A, f, phi):
    from math import cos, pi

    return A - (A * cos(get_time() * 2 * f / pi + phi)))

@SQify # incorrect, SQified functions should not call other SQified functions
def get_time():
    ...

incorrectly mixes the level of abstraction an SQ provides in TTPython.

NOTE: @SQify limits the expressiveness of Python functions it decorates. *args is ont allowed in function definitions as our graph requires a statically known amount of inputs arguments. **kwargs are allowed in function calls if they are also defined within the function definition.

We can now write a basic “Hello World” program to call sample_magnetometer once! We have informed TTPython how to include our sensing function as a SQ into our graph. Now, we need to define the graph structure for our program. To do so, we use another function decorator: @GRAPHify.

@SQify
def sample_magnetometer(A, f, phi):
    from math import cos, pi

    return A - (A * cos(get_time() * 2 * f / pi + phi)))

def get_time():
    ...

@GRAPHify
def main(trigger):
    A = 1
    f = 0.25
    phi = 0
    return sample_magnetometer(A, f, phi)

The function decorator takes the following function as the main program to run in TTPython. The Python semantics in @GRAPHify are actually quite different from the regular sense. We’ll discuss the nuances and restrictiosn of @GRAPHify later, but for now you can imagine that it behaves similarly as another Python function would. One immediate difference is that because our graphs require an input token to be called, there cannot be any parameterless function definitions. Futhermore, any function called within @GRAPHify needs to have been decorated by SQify.

We need to show a few examples * basic function being SQified * importing a file/module with an SQify’d function in it * keyword argument (must match identically, and should not be a variable (esp. not an arc)) * TT-keyword arguments for parameterizing TTPython primitives. Example with TTExecuteOnFullToken; READ_TTCLOCK is a good example in Instructions.py