社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

模型预测控制原理以及python代码实现

机器人规划与控制研究所 • 5 月前 • 494 次点击  

机器人规划与控制研究所 ——机器人/自动驾驶规划与控制方向综合、全面、专业的平台。4万人订阅的微信大号。点击标题下蓝字“机器人规划与控制研究所”关注,我们将为您提供有价值、有深度的延伸阅读。



模型预测控制 (MPC) 是一种复杂的控制策略,它依赖于系统的动态模型来预测其未来行为。通过在有限的时间范围内反复求解优化问题,MPC 可以计算出在满足约束条件的情况下实现预期性能所需的控制动作。在每个时间步长上,都会应用第一个控制动作,并使用更新后的测量值重复该过程,从而创建一种滚动时域方法。这使得 MPC 能够有效地处理具有多个输入和输出、约束条件甚至时间延迟的复杂系统,使其成为各行各业的强大工具。


https://ai.stanford.edu/blog/trajectory-forecasting/

在本文中,我们将研究一个 MPCC 问题,并用 Python 进行求解!我们将要解决的问题如下(摘自 Christopher Vermillion 教授 2024 年秋季 MECHENG299-006 课程“应用最优控制”的笔记,具体内容如下)


1 问题


您正在 Yonder 社区启动一项太阳能汽车服务,该社区的地势平坦。您决定派一辆汽车
参加一场 20 公里的太阳能汽车比赛。比赛在一条完全平坦的赛道上进行,太阳能资源随赛道沿途空间位置的变化而变化。为了提高汽车的性能,使其能够在太阳能资源不足的路段行驶,您还为汽车配备了电池,其总荷电状态(即电池中储存的能量)可以通过以下微分方程进行建模:


其中,Psun 是太阳能电池阵列的可用功率(该数值代表太阳能电池阵列的效率,假设其为常数),a 是飞行器的加速度。在整个问题中,我们将加速度 (a) 视为控制变量。所有其他变量定义如下:




假设太阳能资源随比赛路线上的位置 x 而变化,如下所示:




然而,假设赛车在赛道上任何给定点只能预览 500 米范围内的太阳能资源。取 v(0) = 15 米/秒,Ebat(0) = 200 kJ,实现一个
基于 MPC 的控制器,该控制器根据 Np = Nc = 50 的预测和控制范围确定 a(k),成本函数(待最小化)是穿越该范围所需的时间加上对大加速度的惩罚,即:




受以下限制:



现在让我们看看如何解决如此复杂的问题,我们通过代码段来实现,我会解释它们是如何协同工作的。首先,我们导入一些基本的 Python 库:


2 先决条件





    
import numpy as npfrom scipy.optimize import minimizefrom tqdm import tqdmimport matplotlib.pyplot as pltimport seaborn as sns

现在,让我们定义在问题陈述中发现的一些系统参数和其他基本常数。


# System parameterseta = 0.9                   # Efficiency of the motorC_rr = 0.01                 # Rolling resistance coefficientm = 500                     # Mass of the vehicle (kg)g = 9.8                     # Gravitational acceleration (m/s^2)rho = 1.2                   # Air density (kg/m^3)C_d = 0.15                  # Drag coefficientA_ref = 1                   # Reference area (m^2)dx = 10            # Distance step (m)dxc = 2                 # Distance step (m) for simulation of continuous systemxfinal = 20000              # in metresxpreview = 500              # in metres# Initial conditionsv0 = 15                     # in m/sE_bat0 = 200000             # in Jx0 = 0                      # in mt0 = 0                      # s# MPC parametersNp = 50                              # Prediction horizonNc = 50                              # Control horizonsim_steps = int(xfinal/dx)  # Total simulation steps (20 km / 10 m) = 2000 steps# Constraintsv_min, v_max = 0 , 30                # m/sE_bat_min, E_bat_max = 1000, 200000

我们制作一个函数来输出空间中太阳轮廓的值,如下所示:


# Solar power functiondef solar_power(x):    if x>=0 and x <= 5000:  # 0-5 km        return 1000    elif x>5000 and x <= 10000: # 5-10 km        return 800    elif x>10000 and x <= 15000: # 10-15 km        return 1200    else: # 15-20 km        return 1000

您可以绘制它以发现它如下所示:

太阳能概况

3 系统动力学



我们现在使用前向欧拉方案离散系统动力学如下:


通过 Q(E_bat),我们的意思是它可以是 E_bat 或 E_bat 的某些函数,通常是 RK4 更新算法等等。


# System dynamicsdef system_dynamics(X, a, P_sun):    E_bat, v = X                        # State variables    dE_bat = P_sun - (v/eta) * (m*a + C_rr*m*g + 0.5*rho*v**2*C_d*A_ref)    dv = a    return np.array([dE_bat, dv])       # Return the derivative of the state

为了在离散时间内传播系统动态,我们实施了改进的 RK4 方案(4 阶龙格-库塔),如下所示:


就所有实际目的而言,任何数值方案都有效(甚至像前向欧拉这样简单的方案,但 RK4 在准确性和效率方面表现最佳)。


# refined rk4 method for state updatedef rk4_update(X, a, dx):    E_bat, v, t, x = X    P_sun = solar_power(x)        # Update velocity using the equation of motion: v^2 = v_0^2 + 2aΔx    v_next = v + a*dx/v #can also use the other kinematic approximation = np.sqrt(v**2 + 2*a*dx + eps)        # Calculate average velocity over the step    v_avg = (v + v_next) / 2        # Update battery energy using RK4    k1


    
 = P_sun - (v/eta) * (m*a + C_rr*m*g + 0.5*rho*v**2*C_d*A_ref)    k2 = P_sun - (v_avg/eta) * (m*a + C_rr*m*g + 0.5*rho*v_avg**2*C_d*A_ref)    k3 = P_sun - (v_avg/eta) * (m*a + C_rr*m*g + 0.5*rho*v_avg**2*C_d*A_ref)    k4 = P_sun - (v_next/eta) * (m*a + C_rr*m*g + 0.5*rho*v_next**2*C_d*A_ref)        E_bat_next = E_bat + (dx/v_avg) * (k1 + 2*k2 + 2*k3 + k4) / 6        # Update time    t_next = t + 2*dx / (v + v_next)        # Update position    x_next = x + dx        return np.array([E_bat_next, v_next, t_next, x_next])

4 成本函数和约束


现在,我们定义成本函数(我们希望使用 MPC 控制输入进行优化),如问题所示:


其中 k 是外部模拟循环的索引,其中在每个 k 处,MPC 预测接下来 Np 个步骤的轨迹,因此该步骤 k 的运行成本是遍历该预测范围的预期时间以及预测范围的每个步骤 i 处的预测控制输出(i 是内部循环的索引)。


# Cost function for MPCdef cost_function(a, X):    E_bat, v, t, x = X                 # extract the state variables    t_start = t    J = 0    for i in range(Np):        X = rk4_update([E_bat, v, t, x], a[i], dx)  # simulate the system and get the next state        E_bat, v, t, x = X              # extract the state variables        J += 10 * a[i]**2               # add penalty for control signal    J += (t - t_start)                  # add penalty for time taken    return J

非线性约束可以定义如下:


# Nonlinear constraints for optimizationdef nlcon(a, X):    E_bat, v, t, x = X               # extract the state variables    c = []


    
    # Np is the prediction horizon    for i in range(Np):        X = rk4_update([E_bat, v, t, x], a[i], dx)                              # simulate the system and get the next state        E_bat, v, t, x = X                                                      # extract the state variables        c.extend([v_min - v, v - v_max, E_bat_min - E_bat, E_bat - E_bat_max])  # add constraints    return np.array(c)

注意,速度以米/秒为单位,电池能量以焦耳为单位。另请注意,约束必须在每个时间步应用,因此您可以看到,在上面代码段的 for 循环中,对于迭代索引 i 的每个值,都会附加约束。


5 主循环


1.创建变量来存储状态 E_bat、v、t 和独立变量 x 以及控制输入。

2. 我们开始循环外层循环,这里索引 k 从 0 到 N-1,注意这个 N 不等于 Np。

3. 将状态值存储在当前迭代索引 k 处。

4. 如果适用,定义控制输入的界限。

5. 定义决策变量的约束。

6. 定义目标函数并最小化。注意,在 scipy.minimize 函数中,我们发送了一个依赖于加速度的函数占位符,以及给定成本函数(请记住,成本函数是为接下来的 Np 步计算的)的约束。我们还发送了一个初始加速度的任意初始值作为优化函数的初始猜测。(检查变量a_init:我们也可以将其定义a_init为一个常量数组,从上一步获取加速度值作为热启动)

7. 我们考虑接下来 Np 步生成的控制输出的第一个控制信号(即第一步)。然后,我们用这个新的加速度值来传播状态。

8. 这里还有另一个小技巧,为了提高 MPC 模型的精度,我们可以用比外环更小的时间步长来模拟系统。因此,如果外环步长为 Δ,内环更新步长为 δ,则我们假设加速度在内环上是恒定的。我们也可以选择保持简单,只使用相同的外环时间步长进行模拟。


# Nonlinear constraints for optimizationdef nlcon(a, X):    E_bat, v, t, x = X               # extract the state variables    c = []    # Np is the prediction horizon    for i in range(Np):        X = rk4_update([E_bat, v, t, x], a[i], dx)                              # simulate the system and get the next state        E_bat, v, t, x = X                                                      # extract the state variables        c.extend([v_min - v, v - v_max, E_bat_min - E_bat, E_bat - E_bat_max])  # add constraints    return np.array(c)# Main simulation loopx_array = [x0]v_array = [v0]E_bat_array = [E_bat0]t_array = [t0]a_array = []for k in tqdm(range(sim_steps - 1)):    X = np.array([E_bat_array[k], v_array[k], t_array[k], x_array[k]])        # Solve MPC optimization problem    a_init = np.zeros(Np)                           # initial guess for control signal    bounds = [(-1010)] * Np                       cons = {'type''ineq''fun'lambda a: -nlcon(a, X)}  # nonlinear constraints    result = minimize(lambda a: cost_function(a, X), a_init, method='SLSQP', bounds=bounds, constraints=cons) # optimization        a = result.x[0]    a_array.append(a)       # only store the first control signal        # continous system simulation    # Update state using RK4 method - using smaller step size for inner loop    ac = np.ones(int(dx/dxc)) * a    for jj in range(int(dx/dxc)):        X_next = rk4_update(X, ac[jj], dxc)        X = X_next    #X_next = rk4_update(X, a, dxc) # normal - using same step as outer loop        E_bat_array.append(X_next[0])    v_array.append(X_next[1])    t_array.append(X_next[2])    x_array.append(X_next[3])        # Break if we've reached the end of the race    if x_array[-1] >= xfinal:        break

6 绘图


我们绘制控制信号输入以及输出状态变量:


# Plot results# make beautiful plots using seabornsns.set_context("talk")sns.set_style("whitegrid")sns.set_palette("husl")fig, axs = plt.subplots(31, figsize=(128))axs[0].plot(np.array(x_array[:-1])/1000, v_array[:-1])


    
axs[0].set_xlabel('Position (km)')axs[0].set_ylabel('Velocity (m/s)')axs[0].set_title('Velocity Profile')axs[1].plot(np.array(x_array[:-1])/1000, np.array(E_bat_array[:-1])/1000)axs[1].set_xlabel('Position (km)')axs[1].set_ylabel('Battery State of Charge (kJ)')axs[1].set_title('Battery State of Charge')axs[2].plot(np.array(t_array[:-1]), a_array)axs[2].set_xlabel('Position (km)')axs[2].set_ylabel('Acceleration (m/s^2)')axs[2].set_title('Acceleration Profile')plt.tight_layout()plt.show()print(f"Race completion time: {t_array[-1]:.2f} seconds")


可以看到,我们对加速度输入幅度施加了很大的代价,而从输出中我们可以看到,MPC 框架尝试使用尽可能少的控制信号。此外,速度和 E_bat 的限制也得到了满足。比赛完成时间约为:1359.39 秒。


7 直观解释



因为我们对所使用的任何控制信号都施加了严厉的惩罚:为了最大限度地减少完成比赛所需的时间,只能通过保持汽车最初的速度来最小化(这从速度图中也可以看出,因为速度大多保持在 15 米/秒左右),因此从那里以大约 15 米/秒的速度完成 20000 米的比赛,粗略估计大约需要 1333 秒 - 我们得到的结果正好与此相近。现在人们可能会想,如果给出零加速度输入,那么成本也会很小,并且车辆将以其初始速度巡航 - 但是总可用太阳能以及阻力还有其他限制,这会不断减慢车辆的速度。然而,我们的粗略估计非常接近!













Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/185643