用 python 求拥塞控制模型欧拉数值解

昨天使用 scipy 的 odeint 模拟了 E_best 的微分方程组模型(参见 用 python scipy 库模拟拥塞控制模型),但我觉得那个模型中处理 z 时不够优雅,只是一个负反馈,并未体现 “排队时延与 buffer 占用率成比例增长” 的事实,所以今天我直接用 inflight-in-buffer 重新建模,就优雅多了。

设 x 为 E = bw / delay,y 为 inflight-in-buffer 但不包括余量,w 为到达瓶颈的速率(等价于 sender 的 pacing rate 但又不是,具体为何,看我前面的文章),z 为排队时延,并且有以下参数,R 为传播时延,C 为瓶颈带宽,I 为 inflight 余量。新模型如下:

d x 1 d t = y 1 + I z z + R − x 1 \dfrac{dx_1}{dt}=\dfrac{\frac{y_1+I}{z}}{z+R}-x_1 dtdx1=z+Rzy1+Ix1 【E = bw / delay 表达式】
d w 1 d t = C ⋅ y 1 + I y 1 + y 2 + y 3 + 3 ⋅ I − w 1 \dfrac{dw_1}{dt}=C\cdot\dfrac{y_1+I}{y_1+y_2+y_3+3\cdot I}-w_1 dtdw1=Cy1+y2+y3+3Iy1+Iw1 【w = C * buffer 占率】
d y 1 d t = w 1 ⋅ z − y 1 \dfrac{dy_1}{dt}=w_1\cdot z-y_1 dtdy1=w1zy1 【bdp 公式】

d x 2 d t = y 2 + I z z + R − x 2 \dfrac{dx_2}{dt}=\dfrac{\frac{y_2+I}{z}}{z+R}-x_2 dtdx2=z+Rzy2+Ix2
d w 2 d t = C ⋅ y 2 + I y 1 + y 2 + y 3 + 3 ⋅ I − w 2 \dfrac{dw_2}{dt}=C\cdot\dfrac{y_2+I}{y_1+y_2+y_3+3\cdot I}-w_2 dtdw2=Cy1+y2+y3+3Iy2+Iw2
d y 2 d t = w 2 ⋅ z − y 2 \dfrac{dy_2}{dt}=w_2\cdot z-y_2 dtdy2=w2zy2

d x 3 d t = y 3 + I z z + R − x 3 \dfrac{dx_3}{dt}=\dfrac{\frac{y_3+I}{z}}{z+R}-x_3 dtdx3=z+Rzy3+Ix3
d w 3 d t = C ⋅ y 3 + I y 1 + y 2 + y 3 + 3 ⋅ I − w 3 \dfrac{dw_3}{dt}=C\cdot\dfrac{y_3+I}{y_1+y_2+y_3+3\cdot I}-w_3 dtdw3=Cy1+y2+y3+3Iy3+Iw3
d y 3 d t = w 3 ⋅ z − y 3 \dfrac{dy_3}{dt}=w_3\cdot z-y_3 dtdy3=w3zy3

d z d t = β ⋅ ( y 1 + y 2 + y 3 ) \dfrac{dz}{dt}=\beta\cdot(y_1+y_2+y_3) dtdz=β(y1+y2+y3) 【排队时延与 buffer 占率成正比】

基于这个新模型,我准备用欧拉法求数值解,先简单介绍欧拉法。给定一个常微分方程:

d y d t = f ( t , y ) , y ( t 0 ) = y 0 \frac{dy}{dt} = f(t, y), \quad y(t_0) = y_0 dtdy=f(t,y),y(t0)=y0

欧拉法求解步骤如下:

  • 离散:选一个时间步长 h,定义离散时间点 t n = t 0 + n h ( n = 0 , 1 , 2 , … ) t_n = t_0 + nh \quad (n = 0, 1, 2, \ldots) tn=t0+nh(n=0,1,2,)
  • 更新:使用欧拉法的更新公式: y n + 1 = y n + h ⋅ f ( t n , y n ) y_{n+1} = y_n + h \cdot f(t_n, y_n) yn+1=yn+hf(tn,yn)
  • 迭代:从初始条件开始,迭代计算 y n + 1 y_{n+1} yn+1 直到达到所需的时间范围。

我一开始硬编码了一个 python matplotlib 实现,非常不雅,如果涉及共享瓶颈的流太多,欧拉法的写法将非常令人烦躁,所以不得不重构代码。

但我真不会编程,稍微会一点,就这个代码吭哧折腾快一下午,但还是没有做到可扩展:

#!/opt/homebrew/bin/python3

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import MultipleLocator

class Model:
    def __init__(self, initial_conditions, T, dt, a, b, c, d, R, C, I):
        self.T = T
        self.dt = dt
        self.a = a
        self.b = b
        self.c = c
        self.d = d
        self.R = R
        self.C = C
        self.I = I
        self.len = (len(initial_conditions) - 1) / 3
        self.times = np.arange(0, T + dt, dt)

        # 初始化状态
        self.states = {name: np.zeros_like(self.times) for name in initial_conditions.keys()}
        for name, value in initial_conditions.items():
            self.states[name][0] = value

    def update(self):
        for n in range(1, len(self.times)):
            for i in range(1, int(self.len) + 1):  # 假设有三个 x, y, w
                x_name = f'x{i}'
                y_name = f'y{i}'
                w_name = f'w{i}'

                self.states[x_name][n] = self.states[x_name][n - 1] + self.dt * (
                    (self.states[y_name][n - 1] + self.I) / self.states['z'][n - 1] /
                    (self.states['z'][n - 1] + self.R) - self.states[x_name][n - 1]
                )

                self.states[w_name][n] = self.states[w_name][n - 1] + self.dt * (
                    self.a * self.C * (self.states[y_name][n - 1] + self.I) /
                    (self.states['y1'][n - 1] + self.states['y2'][n - 1] + self.states['y3'][n - 1] + 3 * self.I) - self.states[w_name][n - 1]
                )

                self.states[y_name][n] = self.states[y_name][n - 1] + self.dt * (
                    self.states[w_name][n] * self.states['z'][n] - self.states[y_name][n - 1]
                )
                print("y1", self.states[y_name][n]);

            self.states['z'][n] = self.states['z'][n - 1] + self.dt * (
                self.states['y1'][n - 1] + self.states['y2'][n - 1] + self.states['y3'][n - 1]
            )

    def plot(self):
        plt.figure(figsize = (12, 6))
        plt.subplot(2, 1, 1)
        plt.plot(self.times, self.states['z'], label = 'z(t)', linestyle = 'dotted')
        for i in range(1, int(self.len) + 1):
            plt.plot(self.times, self.states[f'y{i}'], label = f'y{i}(t)', linestyle = 'dashed')
            plt.plot(self.times, self.states[f'w{i}'], label = f'w{i}(t)', linestyle = 'dashed')
        plt.legend()

        plt.subplot(2, 1, 2)
        ax = plt.gca()
        y_major = MultipleLocator(0.2)
        ax.yaxis.set_major_locator(y_major)
        plt.ylim(top = 0.01)
        for i in range(1, int(self.len) + 1):
            plt.plot(self.times, self.states[f'x{i}'], label = f'x{i}(t)')

        plt.xlabel('time (t)')
        plt.ylabel('')
        plt.legend()
        plt.grid(linestyle = 'dotted', linewidth = 1)
        plt.tight_layout()
        plt.show()

initial_conditions = {
    'x1': 0.00002, 'y1': 6, 'x2': 0.00003, 'y2': 2,
    'x3': 0.000001, 'y3': 14, 'w1': 1, 'w2': 1,
    'w3': 3, 'z': 0.1
}

T, dt = 50, 0.1
a, b, c, d, R, C, I = 1, 1.000, 0.2, 0.03, 2, 18, 1

model = Model(initial_conditions, T, dt, a, b, c, d, R, C, I)
model.update()
model.plot()

问题在于下面这个硬编码难以重构:

self.states['y1'][n - 1] + self.states['y2'][n - 1] + self.states['y3'][n - 1] + 3 * self.I

我想改成:

sum(self.states['y'][j][n-1] for j in range(self.len)) + len * self.I

但需要重构数据存储结构,我就搞不定了。但能运行就行:
在这里插入图片描述
其实这个代码已经不错了,最开始的风格如下:

x1 = np.zeros_like(times)
y1 = np.zeros_like(times)
x2 = np.zeros_like(times)
y2 = np.zeros_like(times)
x3 = np.zeros_like(times)
y3 = np.zeros_like(times)
w1 = np.zeros_like(times)
w2 = np.zeros_like(times)
w3 = np.zeros_like(times)
z = np.zeros_like(times)

x1[0], y1[0], x2[0], y2[0], x3[0], y3[0], w1[0], w2[0], w3[0], z[0] = x10, y10, x20, y20, x30, y30, w10, w20, w30, z0
for n in range(1, len(times)):    
    x1[n] = x1[n-1] + dt * (...)
    w1[n] = w1[n-1] + dt * (...)
    y1[n] = y1[n-1] + dt * (...)
    x2[n] = x2[n-1] + dt * (...)
    w2[n] = w2[n-1] + dt * (...)
    y2[n] = y2[n-1] + dt * (...)
    x3[n] = x3[n-1] + dt * (...)
    w3[n] = w3[n-1] + dt * (...)
    y3[n] = y3[n-1] + dt * (...)
    z[n] = z[n-1] + dt * (...)
...

假努力,工整而恶心。

浙江温州皮鞋湿,下雨进水不会胖。

要用Python时滞微分方程的数值,可以使用scipy.integrate库中的delayed函数。具体步骤如下: 1. 导入必要的库: ```python import numpy as np from scipy.integrate import solve_delayed ``` 2. 定义时滞微分方程: ```python def f(t, y, yd): tau = 1.0 dydt = -y + yd(t - tau) return dydt ``` 其中,t表示时间,y表示未知函数,yd表示时滞函数,tau表示时间延迟。 3. 定义初始条件和时间网格: ```python y0 = [1.0] tmax = 10.0 dt = 0.01 tgrid = np.arange(0, tmax+dt, dt) ``` 其中,y0表示y在t=0时的值,tmax表示模拟的最长时间,dt表示时间步长,tgrid表示时间网格。 4. 定义时滞函数: ```python def yd(u): return np.interp(u, tgrid, yhist) ``` 其中,u表示时滞时间,yhist表示y在之前时间的值。 5. 方程: ```python sol = solve_delayed(f, y0, tgrid, yd) ``` 其中,sol是一个对象,包含了数值和其他信息。 6. 提取数值: ```python y = sol.y[0] ``` 其中,sol.y是一个数组,包含了数值。 完整代码如下: ```python import numpy as np from scipy.integrate import solve_delayed def f(t, y, yd): tau = 1.0 dydt = -y + yd(t - tau) return dydt y0 = [1.0] tmax = 10.0 dt = 0.01 tgrid = np.arange(0, tmax+dt, dt) def yd(u): return np.interp(u, tgrid, yhist) yhist = np.zeros_like(tgrid) yhist[0] = y0[0] for i in range(1, len(tgrid)): yhist[i] = yhist[i-1] + dt * (-yhist[i-1]) sol = solve_delayed(f, y0, tgrid, yd) y = sol.y[0] import matplotlib.pyplot as plt plt.plot(tgrid, y, label='y') plt.legend() plt.show() ``` 这个例子中,时滞函数是y在之前时间的值,因此我们先用欧拉出y在所有时间点的值,然后用np.interp函数将其转换为时滞函数。最后,我们时滞微分方程并绘制数值
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值