Simple event-driven programming
Project description
Fixpy
Fixpy is an easy to use package for event-driven programming in python.
The name stems from "FIXed-point iterations" that can be elegantly implemented using this package (an example is shown below).
Further applications of this package include for example handling of User-Interfaces or interrupt calls.
Installation
Fixpy can be quickly installed with pip:
pip install fixpy
Quick-Start
In the following, some examples demonstrate the usage of this package. All examples are also available on Google Colab.
Fixpy can be used with a verbose or a shortened syntax.
First, some verbose examples are shown to explain the basic concepts of the package.
Then, the same examples are presented using the shortened syntax.
Verbose Syntax Examples
First, we show a simple example to automatically update a BMI (body mass index) computation:
from fixpy import Variable, Function
weight = Variable(70,name="weight") # introduce a new variable for body weight
height = Variable(1.8,name="height") # introduce a new variable for body height
def compute_bmi(kg,m): # function to compute bmi
return kg/m**2
bmi = Function(compute_bmi,[weight,height],name="bmi") # now, bmi gets automatically updated, if the value of weight or height changes
print(f"BMI = {bmi.get_value()}") # with get_value(), we can access the value computed in bmi
weight.set_value(80) # now, let's change the weight with set_value()
print(f"new BMI = {bmi.get_value()}") # ... as you can see, the BMI was automatically updated
bmi.plot() # plot a graph of event directions
Callback listeners can be simply added and removed as follows:
def print_bmi(new_bmi): # define a callback listener
print(f"The BMI has changed! BMI = {new_bmi}")
bmi.on_change(print_bmi) # callback listeners can be simply added with on_change()
height.set_value(1.85) # if we change for example height, this will automatically update the bmi and consequently trigger the callback
bmi.remove_callback(print_bmi) # callback listeners can be simply removed with remove_callback()
height.set_value(1.9) # now, the callback listener is not called anymore
Now, let's dive a bit deeper into the functioning of fixpy at the example of a fixed-point iteration.
def still_ok(old_value,new_value): # we can define a threshold at which a Variable does not get updated anymore (this becomes important, if you want to work with more complex variables such as vectors / matrices / tensors)
return abs(old_value-new_value) < 1e-8
x = Variable(2, name="value", still_ok = still_ok, max_recursions=10000, alpha=0.5) # furthermore, we can define a maximum number of iterations and a "low-pass" filter for updates alpha
def golden_ratio_iteration(x):
return 1/(x-1)
f = Function(golden_ratio_iteration,[x]) # f gets updated whenever x changes
x.observe(f) # using x.observe(f), x gets updated to the value of f whenever f changes
# => this results in a loop that iteratively applies f on x until still_ok or max_recursions is reached
# => this is effectively a fixed point iteration (-> therefore the name fixpy ;)
print(f"x = {x.get_value()} (this corresponds to golden ratio)")
Fixpy also works with Strings:
name = Variable("world")
hello_name = Function(lambda n: "Hello "+n+"!", [name], still_ok=None)
print(hello_name.get_value())
name.set_value("universe")
print(hello_name.get_value())
Shortened Syntax Examples
First, let's revise the BMI example:
weight = Variable(70,name="weight") # introduce a new variable for body weight
height = Variable(1.8,name="height") # introduce a new variable for body height
bmi = weight/height**2 # here, a function tree is created that computes the bmi and that gets automatically updated, if the value in weight or height changes
print(f"BMI = {bmi.x}") # instead of get_value() we can simply use x to obtain the computed value
weight.x = 80 # similarly, we can use x to set the value of a variable
print(f"new BMI = {bmi.x}") # ... as you can see, the BMI was automatically updated
bmi.plot() # plot a graph of event directions (this corresponds to the function tree mentioned above)
Using the shortened syntax, callback listeners can be simply added and removed as follows:
bmi >> (lambda new_bmi: print(f"The BMI has changed! BMI = {new_bmi}")) # instead of on_change(), we can use >>
# by the way, the following syntax would work just as well:
# (lambda new_bmi: print(f"The BMI has changed! BMI = {new_bmi}")) << bmi
height.x = 1.85 # if we change for example height, this will automatically update the bmi and consequently trigger the callback
bmi.remove_callback() # if we don't pass an argument to remove_callback(), all callbacks will be removed
height.x = 1.9
A fixed-point iteration can be described in just 2 lines of code:
x = Variable(2, name="value", still_ok = lambda old, new: abs(old-new)<1e-8, max_recursions=10000, alpha=0.5) # furthermore, we can define a maximum number of iterations and a "low-pass" filter for updates alpha
x << 1/(x-1) # instead of observe, we can use <<
# by the way, the following syntax would work just as well:
# 1/(x-1) >> x
print(f"x = {x.get_value()} (this corresponds to golden ratio)")
And this is the shortened syntax for strings:
name = Variable("world")
hello_name = "Hello "+name+"!"
print(hello_name.x)
name.x = "universe"
print(hello_name.x)
These were the most important concepts of fixpy.
More Details
Custom functions can be simply created as follows:
import numpy as np
import matplotlib.pyplot as plt
def polynome(x, *coefficients): # custom polynome function
def _p(x, *coefficients):
return sum(c*x**i for i,c in enumerate(coefficients))
return Function(_p,[x, *coefficients])
x = Variable(np.arange(-1, 3, 0.1), name="x") # btw: variables also work with numpy arrays
coefficients = [Variable(c, name=f"c{i}") for i,c in enumerate([1,0,0])]
p = polynome(x, *coefficients)
plt.plot(x.x,p.x)
plt.show()
p >> (lambda y: (plt.plot(x.x,y), plt.show())) # replot polynome automatically, if results change
Variable.set_values(coefficients,[1,1,-1]) # multiple variables can be set at the same time with set_values() => this triggers the replot callback
If you want to define your own Variables with customized functions, you can inherit from Variable as follows:
class Inversion(Variable):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
def inv(self):
return Function(lambda x: 1/x,[self])
a = Inversion(10,name="a")
print(f"a_inv = {a.inv().x}")
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.