简体   繁体   English

绘制微分方程时的 Bokeh JS 回调

[英]Bokeh JS callback when plotting differential equations

I'm relatively new to Python and Stackoverflow so apologies if this question has already been asked but I've searched for quite a while now and can't really find a solution to what I'm trying to do.我对 Python 和 Stackoverflow 还比较陌生,所以很抱歉,如果这个问题已经被问到,但我已经搜索了很长时间,并且无法真正找到我正在尝试做的事情的解决方案。

Problem:问题:

I've been trying to create a very basic model of the COVID-19 epidemic to familiarise myself with Python.我一直在尝试创建 COVID-19 流行病的非常基本的 model 以熟悉 Python。 My intention is to produce a basic SIR model that calculates susceptible, infected and removed individuals in a population.我的目的是生成一个基本的 SIR model 来计算人群中易感、感染和移除的个体。 So far so good.到目前为止,一切都很好。

My problem is that I would like the plot to have an interactive slider that alters one of the constants in the differential equation.我的问题是我希望 plot 有一个交互式 slider 来改变微分方程中的一个常数。

I am using Bokeh and am trying to use Javascript Callbacks for this however I am having difficulty with the Javascript.我正在使用 Bokeh 并尝试为此使用 Javascript 回调,但是我在使用 Javascript 时遇到了困难。 All examples I have seen so far use line equations where y is a function of x, and which are relatively straightforward to code.到目前为止,我看到的所有示例都使用直线方程,其中 y 是 x 的 function,并且编码相对简单。 In my case, since its a system of differential equations I'm not sure how I should be going about this.就我而言,由于它是一个微分方程系统,我不确定我应该如何处理这个问题。

I've also tried using scipy but I still encounter the same problem.我也尝试过使用 scipy 但我仍然遇到同样的问题。

Code below.代码如下。 Any help/feedback/suggestions would be greatly appreciated.任何帮助/反馈/建议将不胜感激。

Thanks!谢谢!

from bokeh.layouts import column, row
from bokeh.models import CustomJS, Slider
from bokeh.plotting import ColumnDataSource, figure, output_file, show

t = []

for i in range(200):
    t.append(i)

slst = []
ilst = []
rlst = []

s = 489599/489609
i = 10/489609
r = 0/489609
bet = 0.28
gam = 0.189

for f in range(200):
    ds = (-bet * (s * i))
    di = ((bet * (s * i)) - (gam * i))
    dr = gam * i
    s = s + (ds)
    slst.append(s)
    i = i + (di)
    ilst.append(i)
    r = r + (dr)
    rlst.append(r)

source = ColumnDataSource(data=dict(x=t, y=[], s=slst, i=ilst))

plot = figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)

callback = CustomJS(args=dict(source=source), code="""

                    ????????????

    """)

slider = Slider(start=0.1, end=4, value=1, step=.1, title="Beta ")
slider.js_on_change('value', callback)

layout = column(slider, plot)

show(layout)

Here's what I've come up with.这就是我想出的。 It's interesting to see that with high values of beta, the susceptible line goes below 0. Maybe I've made a mistake while porting your code to JavaScript - please correct me if so.有趣的是,在 beta 值较高的情况下,易感行低于 0。也许我在将您的代码移植到 JavaScript 时犯了一个错误 - 如果是,请纠正我。

from bokeh.core.property.instance import Instance
from bokeh.io import save
from bokeh.layouts import column
from bokeh.model import Model
from bokeh.models import CustomJS, Slider, Callback
from bokeh.plotting import ColumnDataSource, figure

source = ColumnDataSource(data=dict(t=[], s=[], i=[], r=[]))

plot = figure(plot_width=400, plot_height=400)
plot.line('t', 's', source=source, line_width=3, line_alpha=0.6)
plot.line('t', 'i', source=source, line_width=3, line_alpha=0.6, color='orange')
plot.line('t', 'r', source=source, line_width=3, line_alpha=0.6, color='green')

callback = CustomJS(args=dict(source=source), code="""\
    const N = 200;
    let s = 489599 / 489609;
    let i = 10 / 489609;
    let r = 0 / 489609;
    const bet = cb_obj.value;
    const gam = 0.189;
    const tlst = source.data.t = [];
    const slst = source.data.s = [];
    const ilst = source.data.i = [];
    const rlst = source.data.r = [];
    for (let t = 0; t < N; ++t) {
        s -= bet * s * i;
        i += bet * s * i - gam * i;
        r += gam * i;
        tlst.push(t);
        slst.push(s);
        ilst.push(i);
        rlst.push(r);
    }
    source.change.emit();
""")

slider = Slider(start=0.1, end=4, value=1, step=.1, title="Beta ")
slider.js_on_change('value', callback)


class IdleDocObserver(Model):
    """Work around https://github.com/bokeh/bokeh/issues/4272."""
    on_idle = Instance(Callback)

    # language=TypeScript
    __implementation__ = """\
        import {View} from "core/view"
        import {Model} from "model"
        import * as p from "core/properties"

        export class IdleDocObserverView extends View {}

        export namespace IdleDocObserver {
            export type Attrs = p.AttrsOf<Props>
            export type Props = Model.Props & {on_idle: p.Property<any>}
        }

        export interface IdleDocObserver extends IdleDocObserver.Attrs {}

        export class IdleDocObserver extends Model {
            static init_IdleDocObserver(): void {
                this.prototype.default_view = IdleDocObserverView
                this.define<IdleDocObserver.Props>({on_idle: [p.Any]})
            }

            _doc_attached(): void {
                super._doc_attached()
                const execute = () => this.on_idle!.execute(this)
                if (this.document!.is_idle)
                    execute();
                else
                    this.document!.idle.connect(execute);
            }
        }
    """


idle_doc_observer = IdleDocObserver(on_idle=CustomJS(args=dict(callback=callback, slider=slider),
                                                     code="callback.execute(slider);"))

layout = column(slider, plot)
save([idle_doc_observer, layout])

As per my comments, one can use RK4 in a javascript implementation to integrate the ODE in the html document.根据我的评论,可以在 javascript 实现中使用 RK4 将 ODE 集成到 html 文档中。 There appears no way in bokeh to implement javascript functions outside of callbacks, utility functions and common computations.散景中似乎没有办法在回调、实用函数和常见计算之外实现 javascript 函数。 So to avoid code duplication one has to make the one callback universal enough that it can serve for all slider change events.因此,为了避免代码重复,必须使一个回调足够通用,以便它可以服务于所有 slider 更改事件。 (Alternatively, one could implement a "recompute" button.) (或者,可以实现一个“重新计算”按钮。)

To look more professional, make 2 plots, one for all components, and one for I alone.为了看起来更专业,制作 2 个图,一个用于所有组件,一个用于I单独。

# Set up the plots and their data source
source = ColumnDataSource(data=dict(T=[], S=[], I=[], R=[]))

SIR_plot = figure(plot_width=400, plot_height=400)
SIR_plot.line('T', 'S', source=source, legend_label="S", line_width=3, line_alpha=0.6, color='blue')
SIR_plot.line('T', 'I', source=source, legend_label="I", line_width=3, line_alpha=0.6, color='orange')
SIR_plot.line('T', 'R', source=source, legend_label="R", line_width=3, line_alpha=0.6, color='green')

I_plot = figure(plot_width=400, plot_height=400)
I_plot.line('T', 'I', source=source, line_width=3, line_alpha=0.6, color='orange')

Next set up 4 sliders for parameters one is likely to want to influence接下来为一个可能想要影响的参数设置 4 个滑块

# declare the interactive interface elements
trans_rate = Slider(start=0.01, end=0.4, value=0.3, step=.01, title="transmission rate ")
recov_rate = Slider(start=0.01, end=0.4, value=0.1, step=.01, title="recovery rate")

I_init = Slider(start=0.01, end=0.1, value=0.05, step=.002, title="initial infected [proportion] ")
max_time = Slider(start=10, end=200, value=50, step=1, title="time range [days] ")

Now as the main change to the answer of Eugene Pakhomov, make one call-back for all sliders (see bokeh gallery slider demo) and use a vectorized RK4 method to do the ODE integration现在作为 Eugene Pakhomov 答案的主要更改,对所有滑块进行一次回调(参见散景库 slider 演示)并使用矢量化 RK4 方法进行 ODE 集成

callback = CustomJS(args=dict(source=source, I_init=I_init, max_time=max_time, 
                              trans_rate=trans_rate, recov_rate=recov_rate), 
                    code="""\
    let i = I_init.value;
    let s = 1-i;
    let r = 0;
    const bet = trans_rate.value;
    const gam = recov_rate.value;
    let tf = max_time.value;
    const dt = 0.1;
    const tlst = source.data.T = [0];
    const slst = source.data.S = [s];
    const ilst = source.data.I = [i];
    const rlst = source.data.R = [r];

    function odefunc(t,sir) {
        let tr = bet*sir[0]*sir[1];
        let rc = gam*sir[1];
        return [-tr, tr-rc, rc];
    }
    let sir = [s,i,r];
    for (let t = 0; t < tf; t+=dt) {
        sir = RK4Step(t,sir,dt);
        tlst.push(t+dt);
        slst.push(sir[0]);
        ilst.push(sir[1]);
        rlst.push(sir[2]);
    }
    source.change.emit();

    function axpy(a,x,y) { 
        // returns a*x+y for arrays x,y of the same length
        var k = y.length >>> 0;
        var res = new Array(k);
        while(k-->0) { res[k] = y[k] + a*x[k]; }
        return res;
    }

    function RK4Step(t,y,h) {
        var k0 = odefunc(t      ,               y );
        var k1 = odefunc(t+0.5*h, axpy(0.5*h,k0,y));
        var k2 = odefunc(t+0.5*h, axpy(0.5*h,k1,y));
        var k3 = odefunc(t+    h, axpy(    h,k2,y));
        // ynext = y+h/6*(k0+2*k1+2*k2+k3);
        return axpy(h/6,axpy(1,k0,axpy(2,k1,axpy(2,k2,k3))),y);
    }

""")
trans_rate.js_on_change('value', callback)
recov_rate.js_on_change('value', callback)

I_init.js_on_change('value', callback)
max_time.js_on_change('value', callback)

Finally, string all together in some layout最后,以某种布局将所有内容串在一起

# generate the layout

parameters_panel = column(trans_rate, recov_rate)
initials_panel = column(I_init,max_time)

plots = row(SIR_plot, I_plot)
inputs = row(parameters_panel, initials_panel)

simulation = column(plots, inputs)

show(simulation)

To avoid the initial empty plots I refer to the answer of Eugene Pakhomov, as it is the plots appear after the first slider is moved.为了避免最初的空图,我参考了 Eugene Pakhomov 的答案,因为它是在移动第一个 slider 之后出现的图。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM