Skip to main content

Binary Delay Equation simulator

Project description

Introduction

Binary Delay Equations (BDEs) can be used to model a variety of problems. pybde allows to you write binary delay equations models in Python and simulate them.

Install pybde

pybde requires Python 3.5 or above.

You can install pybde using pip:

pip install pybde

Writing and simulating a model

The first model we will simulate has a single variable and a single delay. The single equation for the model is:

x(t) = NOT x(t-Ï„)

where x is the model variable, t is time, and Ï„ is the time delay. In our example Ï„ = 1.

To implement this model we must write a function that when given the state of the model's variables at each of the time delays returns the state of the model's variables at the current time. The argument to this function is a list of lists. If the argument is called z then z[i][j] contains the state of the j th variable at the i th delay (note that indexing starts from 0).

So in this case we can write our model in the following function:

def my_model(z):
   return [ not z[0][0] ]

To simulate this model we must provide:

  • the state of the variables at time t=0 and at any other switch points before the start of the simulation,
  • the time delays,
  • the start time for the simulation, and
  • the end time for the simulation.

Our model has only one variable and we will specify its value only at t=0 so the input states and input times will be:

input_states = [ [True] ]
input_times = [ 0 ]

We only have a single delay parameter in this model and its value is 1 so the delay_parameters list is:

delay_parameters = [ 1 ]

The start time of our simulation will be t=1 and the end time will be t=5:

start_time = 1
end_time = 5

Note that the start time must be greater than or equal to the maximum delay parameter.

Putting this altogether gives:

from pybde import BDESolver

def my_model(z):
    return [ not z[0][0] ]

def main():
    input_states = [ [True] ]
    input_times = [ 0 ]
    delay_parameters = [ 1 ]
    start_time = 1
    end_time = 5

    my_bde_solver = BDESolver(my_model, input_times, input_states, delay_parameters)
    my_bde_solver.solve(start_time, end_time)
    my_bde_solver.show_result()

if __name__ == "__main__":
    main()

This will display the following plot showing the state of the variable over the duration of the simulation.

One variable one delay output

Multiple variables and delays

In this example our model will contain two variable and two delays. The model equations are:

x1(t) = x2(t-Ï„1)

x2(t) = NOT x1(t-Ï„2)

where x1 and x2 are the model variables, t is time, and Ï„1 and Ï„2 are the time delays. In this example example Ï„1 = 1 and Ï„2 = 0.5.

We implement this model with the following function. Note that the first index specifies the delay and the second index specifies the variable. Here we have explicitly named index variables so the code looks more like the equations expressed above.

def my_two_variable_model(z):
    x1 = 0
    x2 = 1
    tau1 = 0
    tau2 = 1
    return [z[tau1][x2], not z[tau2][x1]]

We wish to start the simulation at t=2 with input states until this point as shown below:

Two variables input plot

So we specify the input times and states as:

input_times = [0, 1, 1.5]
input_states = [ [True, True], [True, False], [False, False] ]

So the full simulation is run with the following code:

from pybde import BDESolver

def my_two_variable_model(z):
    x1 = 0
    x2 = 1
    tau1 = 0
    tau2 = 1
    return [z[tau1][x2], not z[tau2][x1]]

def main():
    input_times = [0, 1, 1.5]
    input_states = [ [True, True], [True, False], [False, False] ]
    delay_parameters = [ 1, 0.5 ]
    start_time = 2
    end_time = 6

    my_bde_solver = BDESolver(my_two_variable_model, delay_parameters,  
                              input_times, input_states)
    my_bde_solver.solve(start_time, end_time)
    my_bde_solver.show_result(variable_names=['x1','x2'])

if __name__ == "__main__":
    main()

This will display the following plot showing the state of the variables over the duration of the simulation. Note how the optional argument variable_names has been passed to the show_plot function to specify the labels for the variables.

Two variables output plot

Forcing inputs

Forcing inputs are input variables that must be specified for the whole duration of the simulation. These variables' state are not determined by model equations but can be used within model equations to determine the state of other variables.

As an example of forced inputs consider the following model equations:

x1(t) = 1 if t mod 1 >= 0.5, 0 otherwise

x2(t) = x1(t-Ï„)

where x1 and x2 are model state variables, t is the time and Ï„ is the delay. In this case Ï„ is 0.3.

Here we can model x1 as a forcing input as we can define the value of x1 for the whole duration of the simulation. To specify x1 we must define the starting state and switch points for the whole simulation.

For a three second simulation this can be defined as:

forcing_times = [ 0, 0.5, 1, 1.5, 2, 2.5, 3 ]
forcing_states = [[False], [True], [False], [True], [False], [True], [False]

Given that x1 is a forcing variable the only normal variable will be x2. The following code initialised it to True:

input_times = [0]
input_states = [ [True] ]

The inputs to the simulation are shown in the following plot:

inputs plot

When using forcing inputs the state of forcing inputs are the various time delays is passed to the model function as a second argument. The model function is therefore:

def my_forced_input_model(z, forced_inputs):
    tau = 0
    x1 = 0
    return [ forced_inputs[tau][x1] ]

To run the simulation is very similar to before except the forcing inputs must be passed when constructing the BDESolver object:

my_bde_solver = BDESolver(my_forced_input_model, delay_parameters,  
                          input_times, input_states, 
                          forcing_times, forcing_states)

The whole code is:

from pybde import BDESolver

def my_forced_input_model(z, forced_inputs):
    tau = 0
    x1 = 0
    return [ forced_inputs[tau][x1] ]

def main():
    input_times = [0]
    input_states = [[True]]
    delay_parameters = [ 0.3 ]
    start_time = 0.5
    end_time = 3

    forcing_times = [0, 0.5, 1, 1.5, 2, 2.5, 3]
    forcing_states = [[False], [True], [False], [True], [False], [True], [False]]

    my_bde_solver = BDESolver(my_forced_input_model, delay_parameters,
                              input_times, input_states,
                              forcing_times, forcing_states)
    my_bde_solver.solve(start_time, end_time)

    my_bde_solver.show_result(variable_names=['x2'], forcing_variable_names=['x1'])

if __name__ == "__main__":
    main()

Running this simulation produces the following plots:

Forcing inputs output plot

Obtaining the result data

The solve method of BDESolver returns two lists which contain the switch points and variable states. The fist list contains the times of all switch points (including the input switch points and also the final time of the simulation). The second list contains lists of variable states for each switch time point.

For example, for the two variables and two delays example show above we can obtain and print the results with code such as:

result_t, result_y = my_bde_solver.solve(start_time, end_time)

print("result_t: {}".format(result_t))
print("result_y = {}".format(result_y))

This would output:

result_t: [0, 1, 1.5, 2, 3, 3.5, 4.5, 5.0, 6.0]
result_y = [[True, True], [True, False], [False, False], [False, True], [True, True], [True, False], [False, False], [False, True], [True, True]]

Which corresponds to the following plot:

Two variables output plot

Another way to print out the result is to use the print_result method which will print the result over multiple lines in a format that may be easier to read. The following code does this:

    0.00 ->     1.00 : T T 
    1.00 ->     1.50 : T F 
    1.50 ->     2.00 : F F 
    2.00 ->     3.00 : F T 
    3.00 ->     3.50 : T T 
    3.50 ->     4.50 : T F 
    4.50 ->     5.00 : F F 
    5.00 ->     6.00 : F T 
    6.00 ->     6.00 : T T 

and produces output like:

my_bde_solver.solve(start_time, end_time)
my_bde_solver.print_result()

Plotting results

As show in the above examples the result of a simulation can be plotted using matplotlib by calling BSESolver's show_result method.

Plotting the inputs

To show a plot of only the inputs to a simulation so they can be visually verified before running the simulation call the show_inputs method of the BDESolver class:

from pybde import BDESolver

def my_forced_input_model(z, forced_inputs):
    tau = 0
    x1 = 0
    return [ forced_inputs[tau][x1] ]

def main():
    input_times = [0]
    input_states = [[True]]
    delay_parameters = [ 0.3 ]
    start_time = 0.5
    end_time = 3

    forcing_times = [0, 0.5, 1, 1.5, 2, 2.5, 3]
    forcing_states = [[False], [True], [False], [True], [False], [True], [False]]

    my_bde_solver = BDESolver(my_forced_input_model, delay_parameters,
                              input_times, input_states,
                              forcing_times, forcing_states)

    my_bde_solver.show_inputs()

if __name__ == "__main__":
    main()

Saving plots to file

To construct not show the matplotlib object you can use the plot_result method. This would allow you to save the plot to file, for example:

import matplotlib.pyplot as plt

...

my_bde_solver.solve(start_time, end_time)
my_bde_solver.plot_result()
plt.savefig('my_fig.png')

Convenience functions

BDESolver provides a number of convenience functions the may be useful to users. These are discussed here.

to_plots

The static to_plots method converts switch point data into data suitable for plotting on graphs. It is used inside the show_result and plot_result methods but can also be useful to users who wish to plot results using a different plotting package or wish to have more control over the configuration of their plots. The method takes a list of switch time points and a list of lists of variable states at these time points and returns an list of time values to plot and list of lists with the variable states at these time points. Essentially, the function just replicates each value in the lists so as to produce the sharp edge plots required.

For example, using the two variables and two delays example:

    my_bde_solver = BDESolver(my_two_variable_model, delay_parameters,
                              input_times, input_states)

    result_t, result_y = my_bde_solver.solve(start_time, end_time)

    print("result_t: {}".format(result_t))
    print("result_y = {}".format(result_y))

    t_plot_data, y_plot_data = BDESolver.to_plot(result_t, result_y)

    print("t_plot_data = {}".format(t_plot_data))
    print("y_plot_data = {}".format(y_plot_data))

produces

result_t: [0, 1, 1.5, 2, 3, 3.5, 4.5, 5.0, 6.0]
result_y = [[True, True], [True, False], [False, False], [False, True], [True, True], [True, False], [False, False], [False, True], [True, True]]

t_plot_data = [0, 1, 1, 1.5, 1.5, 2, 2, 3, 3, 3.5, 3.5, 4.5, 4.5, 5.0, 5.0, 6.0, 6.0]
y_plot_data = [[True, True], [True, True], [True, False], [True, False], [False, False], [False, False], [False, True], [False, True], [True, True], [True, True], [True, False], [True, False], [False, False], [False, False], [False, True], [False, True], [True, True]]

to_logical

The static to_logical method can be used to convert a list of 1s and 0s into the list of True and False. When specifying longer input sequences it is often more readable to use 1s and 0s. For example:

input_states = [ [True, True], [True, False], [False, False] ]

Can be replaced with

input_states = BDESolver.to_logical([[1, 1], [1, 0], [0, 0]]

Logging

pybde using Python's logging library to provide some debug logging. For example, the following line can be used to turn own debug logging:

import logging

logging.basicConfig(level=logging.DEBUG)

Numerical accuracy

The implementation of pydbe has to compare possible switch times generated in different ways to see if they are the same time. For example, is t1+Ï„2 the timepoint as t2+Ï„1. To perform comparisons of floating point numbers pydbe uses math.isclose function. This function defines the acceptable accuracy using the rel_tol and abs_tol arguments. To specify non-default values for these arguments you can specify rel_tol and abs_tol arguments when constructing the BDESolver object.

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

pybde-0.3.tar.gz (14.5 kB view hashes)

Uploaded Source

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