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