0

I am creating an interactive plot that will track the zeros of the equation and the slider is adjusted. I am running into an issue on updating the zeros source data.

1) How to update/recalculate the zeros?

2) Is there a more efficient was to do this?

#@title Interactive Phase Plane Plot
output_notebook()
mu = 0
x = np.linspace(-2*np.pi, 2*np.pi, 2000)
y = mu*np.sin(x)-np.sin(2*x)

The original function before slider manipulation. The next section roughly estimates zeros.

def init_fp(x):
    fp_x = []
    fp_y = []
    i = 0
    while i < len(x):
        if np.abs(y[i]) > 0.005:
            pass
        else:
            fp_x.append(x[i])
            fp_y.append(0)
        i += 1
    return fp_x, fp_y

This section creates the data source for calback manipulation in the Bokeh plot.

source = ColumnDataSource(data={
    'x' : x,
    'y' : y
})

fpx, fpy = init_fp(x)
source1 = ColumnDataSource(data={
    'fpx' : fpx,
    'fpy' : fpy
})

This defines the callback operation and inside is where I am updating the plot with the new func and recalculating the zeros.

callback_single = CustomJS(args=dict(source=source, source1=source1), code="""
        var data = source.data;
        var mu = cb_obj.value
        var x = data['x']
        var y = data['y']
        var x1 = data['xfp']
        var y1 = data['yfp']
        for (var i = 0; i < x.length; i++) {
            y[i] = mu*Math.sin(x[i])-Math.sin(2*x[i]);
        }
        source.change.emit();
        for (var i=0; i < x.length; i++){
            if (Math.fps(y[i]) < 0.05){
                x1[i] = x[i];
                y1[i] = 0;
            }
        }
        source1.change.emit();
    """)

Here is where the slider is defined and the plots are made, along with various aesthetics for the plot.

mu = Slider(start=-5, end=5, value=0, step=0.01, title="mu", callback=callback_single)
p = figure(plot_width=1000, plot_height=500)
p.line('x', 'y', source=source)
p.circle('fpx', 'fpy', source=source1)
p.xgrid.grid_line_color=None
p.ygrid.grid_line_alpha=0.8
p.xaxis.axis_label = 'Theta'
p.yaxis.axis_label = 'd Theta/dt'

t = Title()
t.text = 'Interactive Phase Plane Plot'
layout = column(p, widgetbox(mu))
p.title = t
show(layout)

1 Answer 1

1

There are a few things that can be changed in your example:

  • The fpx, fpy properties belong to source1, not to source.
  • Math.fps should be Math.abs.
  • You have a typo in the field names when retrieving x1 and y1.
  • The second loop in CustomJS callback wouldn't quite work if the number of zeros changes.

One way to rewrite the callback is as follows:

callback_single = CustomJS(args=dict(source=source, source1=source1), code="""
        const mu = cb_obj.value
        const { x } = source.data;
        const y = x.map( val => mu * Math.sin(val) - Math.sin(2*val));
        source.data = { x, y };
        const fpx = x.filter((val, ind) => Math.abs(y[ind]) < 0.05);
        const fpy = fpx.map(() => 0);         
        source1.data = { fpx, fpy };
    """)

Also, there is a way to use more idiomatic numpy in init_fp:

def init_fp(x, y):
    fp_x = x[np.abs(y) < 0.05]
    fp_y = np.zeros_like(fp_x)
    return fp_x, fp_y

Here is the complete code, I left the math of zeros as is and also changed from output_notebook to output_file:

from bokeh.io import show, output_file
from bokeh.layouts import column, widgetbox
from bokeh.models import ColumnDataSource, CustomJS, Slider, Title
from bokeh.plotting import figure
import numpy as np
output_file('zeros.html')
mu = 0
x = np.linspace(-2*np.pi, 2*np.pi, 2000)
y = mu*np.sin(x)-np.sin(2*x)

def init_fp(x, y):
    fp_x = x[np.abs(y) < 0.05]
    fp_y = np.zeros_like(fp_x)
    return fp_x, fp_y

source = ColumnDataSource(data={
    'x' : x,
    'y' : y
})

fpx, fpy = init_fp(x, y)
source1 = ColumnDataSource(data={
    'fpx' : fpx,
    'fpy' : fpy
})

callback_single = CustomJS(args=dict(source=source, source1=source1), code="""
        const mu = cb_obj.value;
        const { x } = source.data;
        const y = x.map( val => mu * Math.sin(val) - Math.sin(2*val));
        source.data = { x, y };
        const fpx = x.filter((val, ind) => Math.abs(y[ind]) < 0.05);
        const fpy = fpx.map(() => 0);
        source1.data = { fpx, fpy };
                               """)
mu = Slider(start=-5, end=5, value=0, step=0.01, title="mu", callback=callback_single)
p = figure(plot_width=1000, plot_height=500)
p.line('x', 'y', source=source)
p.circle('fpx', 'fpy', source=source1)
p.xgrid.grid_line_color=None
p.ygrid.grid_line_alpha=0.8
p.xaxis.axis_label = 'Theta'
p.yaxis.axis_label = 'd Theta/dt'

t = Title()
t.text = 'Interactive Phase Plane Plot'
layout = column(p, widgetbox(mu))
p.title = t
show(layout)
Sign up to request clarification or add additional context in comments.

1 Comment

Perfect. Thank you very much for your response. This was very informative!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.