Skip to main content

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.

Source Distribution

fixpy-0.0.6.tar.gz (8.1 kB view hashes)

Uploaded Source

Built Distribution

fixpy-0.0.6-py3-none-any.whl (7.8 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page