第四部分:数据拼接、拆分与变形(工程篇)
在处理多源异构数据或构建复杂的系统仿真时,经常需要将不同维度的计算结果拼接成增广矩阵(例如状态空间中的[A∣B][A\vert{}B][A∣B]矩阵),或者提取长序列中的时间窗口。本篇重点介绍如何像拼积木一样高效地重组和切割数据块。
1. 矩阵拼接
将多个已有的数组按指定维度组合成一个更大的数组。在控制仿真中,常用于合并系统状态向量与控制输入序列。
np.concatenate: 最通用的拼接函数,允许指定任意已存在的轴(axis)进行拼接。要求除了拼接轴之外,参与拼接的所有数组在其他维度上的形状必须完全一致。importnumpyasnp# 两个批次的状态数据,每个批次包含 2 个样本,每个样本有 3 个状态量 (2, 3)batch1=np.array([[1.0,0.5,0.1],[1.2,0.4,0.2]])batch2=np.array([[0.9,0.6,0.0],[1.1,0.5,0.1]])# 按第 0 轴(行方向)拼接,合并样本,形状变为 (4, 3)combined_batches=np.concatenate((batch1,batch2),axis=0)print(combined_batches.shape)# (4, 3)np.vstack: 垂直拼接(按行堆叠)。等价于np.concatenate(..., axis=0),但语义更明确,适合处理时间序列的逐帧追加。x1=np.array([1,2,3])x2=np.array([4,5,6])# 将一维向量按行堆叠成二维矩阵V_stacked=np.vstack((x1,x2))print(V_stacked)# [[1 2 3]# [4 5 6]]np.hstack: 水平拼接(按列堆叠)。常用于构造增广矩阵。A=np.array([[1,2],[3,4]])# 系统矩阵 A (2, 2)B=np.array([[0],[1]])# 输入矩阵 B (2, 1)# 构造增广矩阵 [A | B]Augmented_M=np.hstack((A,B))print(Augmented_M)# [[1 2 0]# [3 4 1]]np.dstack: 深度拼接(按第 3 个维度叠加)。在处理多通道图像、空地协同环境的三维栅格地图或将二维平面状态堆叠至时间轴时非常有用。# 两个 2x2 的平面坐标网络grid_x=np.array([[0,1],[0,1]])grid_y=np.array([[0,0],[1,1]])# 将 X 和 Y 坐标堆叠成形状为 (2, 2, 2) 的张量coords=np.dstack((grid_x,grid_y))print(coords[0,1])# 输出第一行第二列元素的坐标 (X, Y)# [1 0]
2. 分块拼接
在设计 LQR/MPC 控制器或处理复杂系统参数时,往往需要用多个小矩阵组合成一个大矩阵。
np.block: 允许按照直观的嵌套列表形式创建分块矩阵,极大简化了复杂增广矩阵的构造代码。A=np.eye(2)# 2x2 单位阵B=np.array([[0],[1]])# 2x1 向量C=np.array([[1,0]])# 1x2 向量D=np.array([[0]])# 1x1 标量# 按照系统分块矩阵 [ A, B ]# [ C, D ] 的形式组合System_Matrix=np.block([[A,B],[C,D]])print(System_Matrix)# [[1. 0. 0.]# [0. 1. 1.]# [1. 0. 0.]]
3. 数组拆分
与拼接相对,拆分函数用于将大数组切割为多个小数组。
np.split: 通用的拆分函数,可将数组沿指定轴等分,或在指定的索引位置切断。data=np.array([10,20,30,40,50,60])# 指定索引位置拆分:在索引 2 和 5 处切断,分为 3 段chunks=np.split(data,[2,5])print(chunks)# [array([10, 20]), array([30, 40, 50]), array([60])]np.vsplit与np.hsplit: 分别对应垂直拆分(切断行)与水平拆分(切断列)。matrix=np.array([[1,2,3,4],[5,6,7,8]])# 将 4 列平均水平拆分为两个 (2, 2) 的矩阵left,right=np.hsplit(matrix,2)print("Left part:\n",left)# [[1 2]# [5 6]]
4. 排列与滚动
在无模型自适应控制 (MFAC) 算法中,常需要维护历史 I/O 数据的滑动窗口;在多智能体编队中,往往需要将期望轨迹复制给系统中的所有智能体。
np.roll: 使数组元素沿着指定轴进行循环位移。这是在 Python 中实现 FIFO(先进先出)数据缓冲区或时间序列滑动窗口的最高效方法之一,无需频繁分配内存。# 模拟一个长度为 5 的历史输入数据缓冲区 (u_{k-4} 到 u_k)buffer=np.array([1.1,1.2,1.3,1.4,1.5])# 新时刻到来,输入了新控制量 1.6new_u=1.6# 将旧数据整体向左滚动 1 位,挤掉最老的数据,并在末尾追加新数据buffer=np.roll(buffer,shift=-1)buffer[-1]=new_uprint("更新后的缓冲区:",buffer)# 更新后的缓冲区: [1.2 1.3 1.4 1.5 1.6]np.tile: 将整个数组作为一个基本单元,按照指定的次数进行瓦片式重复平铺。ref_traj=np.array([10.0,20.0])# 某个二维参考点# 为编队内的 3 个智能体分配同样的参考点 (形状变为 3x2)multi_agent_refs=np.tile(ref_traj,(3,1))print(multi_agent_refs)# [[10. 20.]# [10. 20.]# [10. 20.]]np.repeat: 将数组中的各个元素分别重复指定次数,而不是重复整个数组块。commands=np.array([0,1])# [直行, 转向]# 每个指令维持 3 个控制周期step_commands=np.repeat(commands,3)print(step_commands)# [0 0 0 1 1 1]