User-defined functions

While many Python, NumPy and Matplotlib functions are pre-programmed to work directly with quib arguments (see List of quiby functions), occasionally we need to create quibs that implement other, currently non-quiby functions, either functions of external packages, or user-defined function.

Quibbler allows several ways for creating quibs that represent any arbitrary function. Below we explain and demonstrate these different ways of implementing user-defined functions.

The implementations described here are for functions that work on quib values as a whole. Quibbler also supports implementing user-defined functions that work consecutively on parts of arrays, using the NumPy syntax of np.vectorize, np.apply_along_axis (see Diverged evaluation).

Import

import pyquibbler as qb
from pyquibbler import q, quiby, iquib, Quib
qb.initialize_quibbler()
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
%matplotlib tk

An example function

We consider as an example the following user-defined function that we want to implement as a quib, with quib arguments:

def add(a, b):
    print(f'function add called with {a}, {b}')
    return a + b

Our task is to implement this function on the value of two quibs:

x = iquib(np.array([1, 2, 3]))
y = iquib(100)

The q-syntax

The Quibbler function q() creates a function quib representing any given function call. The syntax q(func, *args, **kwargs) returns a quib that implement func(*args, **kwargs).

For the example function above, we will implement:

w1 = q(add, x, y)
w1.get_value()
function add called with [1 2 3], 100
array([101, 102, 103])

The quiby syntax

The Quibbler function quiby() converts any function to a quiby function - namely to a function that can work directly on quib arguments to create a quib.

For the example function above, we will implement:

w2 = quiby(add)(x, y)
w2.get_value()
function add called with [1 2 3], 100
array([101, 102, 103])

The advatage of quiby is that it can also be used as a decorator and it allows specifying properties of the quiby function, including lazy, pass_quibs, is_random, is_graphics, is_file_loading. See documentation of quiby()).

Using quiby as a decorator

For the example above, we can implement the function add as a quiby function, using quiby as a decorator:

@quiby(is_graphics=False)
def add(a, b):
    print(f'function add called with {a}, {b}')
    return a + b
w3 = add(x, y)
w3.get_value()
function add called with [1 2 3], 100
array([101, 102, 103])

The pass_quibs property

Normally, as above, a quib calls its function with any quibs in its arguments replaced by their values. Sometimes, we may want to send the quib objects themselves to the implemented function. Transferring quibs to the function is controlled by the pass_quibs property.

Passing quibs as arguments is particularly warranted if we wish to implement inverse assignments from graphics created within the function into upstream quibs outside the function.

The following example demonstrates such use of pass_quibs=True functions. Setting pass_quibs=True, the user defined function will see actual quib arguments. Thereby, graphics built by the function can inverse assign to upstream quibs outside the function. Note that, as demonstrated, the function can also execute get_value on its quib arguments.

# Define axes:
fig = plt.figure(figsize=(4, 5))
axs = fig.gca()
axs.axis('equal')
axs.axis('square')
axs.axis([0.5, 5.5, 0.5, 5.5])

# Define a function that can make two alternative plots of the data.
@quiby(is_graphics=True, pass_quibs=True)
def plot_draggable_points(y: Quib, transpose: Quib):
    x = range(1, len(y.get_value()) + 1)
    if transpose:
        axs.plot(y, x, marker='o', picker=True)
    else:
        axs.plot(x, y, marker='o', picker=True)

y = iquib([1., 3., 4., 2., 1.])
is_transpose = iquib(False)

plot_draggable_points(y, is_transpose)

axs_widget = fig.add_axes([0.2, 0.02, 0.4, 0.16])
axs_widget.axis('off')
widgets.CheckButtons(ax=axs_widget, labels=['Transpose'], actives=[is_transpose]);
_images/User_defined_functions_pass_quibs.gif