《零基础:离散数据积分与 Python 实现保姆级教程》
一、 概念扫盲:什么是"离散数据"与"积分"?
1.1 什么是离散数据?
无人机上的传感器(如速度计、GPS)是离散采样的。它只能每隔一段时间(比如 0.1 秒)"拍个照",记录下那一瞬间的数据。
在 Python 中,离散数据就是两个长度一模一样的列表(数组):
- 一个是时间戳表 ttt:
[0秒, 1秒, 2秒, 3秒] - 一个是传感器读数 vvv:
[0m/s, 2m/s, 4m/s, 2m/s]
在这 1秒 到 2秒 之间发生了什么?我们完全不知道,这就叫离散数据的盲区。
1.2 积分到底是什么?(白话解释)
在工程中,积分最直观的物理意义就是:积累量。
- 数学意义:求曲线和横轴围成的面积。
- 物理意义:已知"速度",求"位移"(速度在时间上的累积);已知"功率",求"耗电量"。
哪怕你忘了牛顿-莱布尼茨公式,只要记住:积分 = 算面积 = 算累积量。
二、 核心数学原理:如何"猜"出面积?
既然点与点之间是盲区,我们怎么算总面积(总位移)呢?只能把相邻的两个点连起来,拼成一个个小几何图形来"猜"。
2.1 推导:为什么工程上最爱用"梯形法"?
假设我们从无人机上拿到了两个相邻的采样点:
- 第 1 个点:时刻 t1t_1t1,速度 v1v_1v1
- 第 2 个点:时刻 t2t_2t2,速度 v2v_2v2
这两个点在图纸上怎么连?
- 矩形法(太粗糙) :假装从 t1t_1t1 到 t2t_2t2 速度一直不变(一直是 v1v_1v1)。面积 = 宽 ×\times× 高 = (t2−t1)×v1(t_2 - t_1) \times v_1(t2−t1)×v1。这样误差极大。
- 梯形法(最实用) :用一条直线 把 (t1,v1)(t_1, v_1)(t1,v1) 和 (t2,v2)(t_2, v_2)(t2,v2) 连起来。这就形成了一个梯形。
梯形面积公式推导:
- 梯形面积 = (((上底 + 下底)×) \times)× 高 ÷2\div 2÷2
- 把梯形竖着看:
- 上底 = 时刻 t1t_1t1 的速度 v1v_1v1
- 下底 = 时刻 t2t_2t2 的速度 v2v_2v2
- 高 = 两个点之间经历的时间 Δt=t2−t1\Delta t = t_2 - t_1Δt=t2−t1
所以,这两个点之间走过的位移(小梯形面积)就是:
面积=(v1+v2)×(t2−t1)2\text{面积} = \frac{(v_1 + v_2) \times (t_2 - t_1)}{2}面积=2(v1+v2)×(t2−t1)
只要我们把所有相邻两点组成的小梯形面积全部加起来,就是总位移!这就是梯形积分法的全部数学原理。
三、 从零手写 Python 积分代码
在调用高级库之前,我们先纯手工用 Python 写一遍上述逻辑,彻底搞懂底层的 for 循环是怎么运行的。
3.1 数组索引的对应关系 (Mermaid 图解)
假设我们有 4 个数据点。在 Python 中,列表的索引(下标)是从 0 开始的。
#mermaid-svg-BhhR2zgb8ffdVGet{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BhhR2zgb8ffdVGet .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BhhR2zgb8ffdVGet .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BhhR2zgb8ffdVGet .error-icon{fill:#552222;}#mermaid-svg-BhhR2zgb8ffdVGet .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BhhR2zgb8ffdVGet .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BhhR2zgb8ffdVGet .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BhhR2zgb8ffdVGet .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BhhR2zgb8ffdVGet .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BhhR2zgb8ffdVGet .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BhhR2zgb8ffdVGet .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BhhR2zgb8ffdVGet .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BhhR2zgb8ffdVGet .marker.cross{stroke:#333333;}#mermaid-svg-BhhR2zgb8ffdVGet svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BhhR2zgb8ffdVGet p{margin:0;}#mermaid-svg-BhhR2zgb8ffdVGet .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BhhR2zgb8ffdVGet .cluster-label text{fill:#333;}#mermaid-svg-BhhR2zgb8ffdVGet .cluster-label span{color:#333;}#mermaid-svg-BhhR2zgb8ffdVGet .cluster-label span p{background-color:transparent;}#mermaid-svg-BhhR2zgb8ffdVGet .label text,#mermaid-svg-BhhR2zgb8ffdVGet span{fill:#333;color:#333;}#mermaid-svg-BhhR2zgb8ffdVGet .node rect,#mermaid-svg-BhhR2zgb8ffdVGet .node circle,#mermaid-svg-BhhR2zgb8ffdVGet .node ellipse,#mermaid-svg-BhhR2zgb8ffdVGet .node polygon,#mermaid-svg-BhhR2zgb8ffdVGet .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BhhR2zgb8ffdVGet .rough-node .label text,#mermaid-svg-BhhR2zgb8ffdVGet .node .label text,#mermaid-svg-BhhR2zgb8ffdVGet .image-shape .label,#mermaid-svg-BhhR2zgb8ffdVGet .icon-shape .label{text-anchor:middle;}#mermaid-svg-BhhR2zgb8ffdVGet .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BhhR2zgb8ffdVGet .rough-node .label,#mermaid-svg-BhhR2zgb8ffdVGet .node .label,#mermaid-svg-BhhR2zgb8ffdVGet .image-shape .label,#mermaid-svg-BhhR2zgb8ffdVGet .icon-shape .label{text-align:center;}#mermaid-svg-BhhR2zgb8ffdVGet .node.clickable{cursor:pointer;}#mermaid-svg-BhhR2zgb8ffdVGet .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BhhR2zgb8ffdVGet .arrowheadPath{fill:#333333;}#mermaid-svg-BhhR2zgb8ffdVGet .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BhhR2zgb8ffdVGet .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BhhR2zgb8ffdVGet .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BhhR2zgb8ffdVGet .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BhhR2zgb8ffdVGet .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BhhR2zgb8ffdVGet .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BhhR2zgb8ffdVGet .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BhhR2zgb8ffdVGet .cluster text{fill:#333;}#mermaid-svg-BhhR2zgb8ffdVGet .cluster span{color:#333;}#mermaid-svg-BhhR2zgb8ffdVGet div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BhhR2zgb8ffdVGet .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BhhR2zgb8ffdVGet rect.text{fill:none;stroke-width:0;}#mermaid-svg-BhhR2zgb8ffdVGet .icon-shape,#mermaid-svg-BhhR2zgb8ffdVGet .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BhhR2zgb8ffdVGet .icon-shape p,#mermaid-svg-BhhR2zgb8ffdVGet .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BhhR2zgb8ffdVGet .icon-shape .label rect,#mermaid-svg-BhhR2zgb8ffdVGet .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BhhR2zgb8ffdVGet .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BhhR2zgb8ffdVGet .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BhhR2zgb8ffdVGet :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第一块小梯形计算 (i=0)
索引 i 的含义
对应时间
对应速度
对应时间
对应速度
对应时间
对应速度
对应时间
对应速度
i = 0
t0 = 0.0s
v0 = 0.0
i = 1
t1 = 1.0s
v1 = 2.0
i = 2
t2 = 2.0s
v2 = 4.0
i = 3
t3 = 3.0s
v3 = 1.0
上底: v0
下底: v1
高: t1 - t0
公式: (上+下)*高/2
3.2 纯手工 Python 代码推导
python
# 1. 准备离散数据(一共 4 个点)
t = [0.0, 1.0, 2.0, 3.0] # 时间戳 (秒)
v = [0.0, 2.0, 4.0, 1.0] # 速度 (m/s)
# 2. 初始化总面积(总位移)为 0
total_distance = 0.0
# 3. 核心:写 for 循环遍历所有相邻的点
# 为什么是 len(t) - 1?
# 因为 4 个点只能连成 3 个梯形!如果 i 走到最后一个点,就找不到 i+1 了,会报错。
for i in range(len(t) - 1):
# 第 i 步:提取当前点和下一个点的时间与速度
t_current = t[i]
t_next = t[i+1]
v_current = v[i]
v_next = v[i+1]
# 第 i 步:套用梯形面积公式
height = t_next - t_current # 高 (时间差)
top = v_current # 上底 (当前速度)
bottom = v_next # 下底 (下一个速度)
area = (top + bottom) * height / 2.0 # 算出一个小梯形面积
# 把这块小面积累加到总面积中
total_distance = total_distance + area
# 打印过程,方便你观察
print(f"第 {i} 个梯形: 时间段 {t_current}s 到 {t_next}s, 增加位移 {area}m")
# 4. 输出最终结果
print(f"最终算出的总位移是: {total_distance} 米")
四、 掌握 Python 的"魔法工具":SciPy 库
上面写了十几行代码,在实际科研中,我们直接使用 SciPy 库,只需一行代码即可完成。
首先需要导入库:
import numpy as np
from scipy.integrate import trapezoid, cumulative_trapezoid
4.1 一步求总和:trapezoid
这个函数做的事情,和我们上面手写的 for 循环一模一样。
python
import numpy as np
from scipy.integrate import trapezoid
t = np.array([0.0, 1.0, 2.0, 3.0])
v = np.array([0.0, 2.0, 4.0, 1.0])
# 一行代码搞定总位移
total_dist = trapezoid(y=v, x=t)
print(f"SciPy 梯形积分总位移: {total_dist} 米")
# 结果同样是 6.5 米
4.2 科研重头戏:求过程轨迹 cumulative_trapezoid
在控制系统仿真中,我们不仅要知道"最后飞了多远"(一个孤立的数字 6.5),更需要知道"每一秒无人机在什么位置"(我们需要一条曲线)。
cumulative_trapezoid (累积梯形法)就是干这个的。它会把每一步累加的结果保存成一个数组。
代码段
#mermaid-svg-32nRXODz24SIQzcP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-32nRXODz24SIQzcP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-32nRXODz24SIQzcP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-32nRXODz24SIQzcP .error-icon{fill:#552222;}#mermaid-svg-32nRXODz24SIQzcP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-32nRXODz24SIQzcP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-32nRXODz24SIQzcP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-32nRXODz24SIQzcP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-32nRXODz24SIQzcP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-32nRXODz24SIQzcP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-32nRXODz24SIQzcP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-32nRXODz24SIQzcP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-32nRXODz24SIQzcP .marker.cross{stroke:#333333;}#mermaid-svg-32nRXODz24SIQzcP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-32nRXODz24SIQzcP p{margin:0;}#mermaid-svg-32nRXODz24SIQzcP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-32nRXODz24SIQzcP .cluster-label text{fill:#333;}#mermaid-svg-32nRXODz24SIQzcP .cluster-label span{color:#333;}#mermaid-svg-32nRXODz24SIQzcP .cluster-label span p{background-color:transparent;}#mermaid-svg-32nRXODz24SIQzcP .label text,#mermaid-svg-32nRXODz24SIQzcP span{fill:#333;color:#333;}#mermaid-svg-32nRXODz24SIQzcP .node rect,#mermaid-svg-32nRXODz24SIQzcP .node circle,#mermaid-svg-32nRXODz24SIQzcP .node ellipse,#mermaid-svg-32nRXODz24SIQzcP .node polygon,#mermaid-svg-32nRXODz24SIQzcP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-32nRXODz24SIQzcP .rough-node .label text,#mermaid-svg-32nRXODz24SIQzcP .node .label text,#mermaid-svg-32nRXODz24SIQzcP .image-shape .label,#mermaid-svg-32nRXODz24SIQzcP .icon-shape .label{text-anchor:middle;}#mermaid-svg-32nRXODz24SIQzcP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-32nRXODz24SIQzcP .rough-node .label,#mermaid-svg-32nRXODz24SIQzcP .node .label,#mermaid-svg-32nRXODz24SIQzcP .image-shape .label,#mermaid-svg-32nRXODz24SIQzcP .icon-shape .label{text-align:center;}#mermaid-svg-32nRXODz24SIQzcP .node.clickable{cursor:pointer;}#mermaid-svg-32nRXODz24SIQzcP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-32nRXODz24SIQzcP .arrowheadPath{fill:#333333;}#mermaid-svg-32nRXODz24SIQzcP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-32nRXODz24SIQzcP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-32nRXODz24SIQzcP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-32nRXODz24SIQzcP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-32nRXODz24SIQzcP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-32nRXODz24SIQzcP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-32nRXODz24SIQzcP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-32nRXODz24SIQzcP .cluster text{fill:#333;}#mermaid-svg-32nRXODz24SIQzcP .cluster span{color:#333;}#mermaid-svg-32nRXODz24SIQzcP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-32nRXODz24SIQzcP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-32nRXODz24SIQzcP rect.text{fill:none;stroke-width:0;}#mermaid-svg-32nRXODz24SIQzcP .icon-shape,#mermaid-svg-32nRXODz24SIQzcP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-32nRXODz24SIQzcP .icon-shape p,#mermaid-svg-32nRXODz24SIQzcP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-32nRXODz24SIQzcP .icon-shape .label rect,#mermaid-svg-32nRXODz24SIQzcP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-32nRXODz24SIQzcP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-32nRXODz24SIQzcP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-32nRXODz24SIQzcP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 累加过程 (初始位移 0)
速度数组 v (4个点)
0.0
2.0
4.0
1.0
起点: 0
0 + 梯形1面积(1.0) = 1.0
1.0 + 梯形2面积(3.0) = 4.0
4.0 + 梯形3面积(2.5) = 6.5
⚠️ 保姆级排雷:initial=0 参数为什么这么重要?
看上面的图解,4 个速度点,只能算出 3 个梯形面积。
如果不加 initial=0,输出的位置数组只有 [1.0, 4.0, 6.5] 3 个数据。
但你的时间戳 t 是 4 个点。在 Python 里,如果画图(x 和 y 长度不一致),程序会直接崩溃报错!
加上 initial=0,就是在数组最前面强行补上一个"初始位移 0"。
输出就会变成 [0.0, 1.0, 4.0, 6.5],完美与 t 的 4 个点一一对应。
五、 实战演练:多智能体无人机的一段飞行数据
现在,我们把上面的知识串起来。模拟一架无人机从静止加速起飞,然后减速悬停的过程。我们通过速度数据,反推出它的位置曲线,并画图。
python
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import cumulative_trapezoid
# 1. 制造模拟数据
# np.linspace(0, 10, 100) 的意思是:在 0 到 10 之间,均匀生成 100 个点(时间戳)
t = np.linspace(0, 10, 100)
# 假设无人机的速度是一个抛物线形:先加速到 5秒(速度最大),然后减速到 10秒(速度归零)
# 公式随意设定的,只是为了造出一个漂亮的钟形曲线
v = 10 - 0.4 * (t - 5)**2
# 2. 核心代码:对离散速度进行累积梯形积分,算出离散位置
# y 放被积变量(速度),x 放自变量(时间),务必加上 initial=0
position = cumulative_trapezoid(y=v, x=t, initial=0)
# ================= 下面全都是画图代码,模板可以直接抄 =================
# 创建一个画布,大小为 10x5 英寸
plt.figure(figsize=(10, 5))
# 画第一条线:速度曲线 (用红色虚线表示)
# label 用来在图例中显示名字
plt.plot(t, v, color='red', linestyle='--', linewidth=2, label='Velocity (m/s)')
# 画第二条线:位置曲线 (用蓝色实线表示)
plt.plot(t, position, color='blue', linestyle='-', linewidth=2, label='Position (m)')
# 给图表加上标题和坐标轴说明
plt.title("UAV Flight Data: Integration from Velocity to Position")
plt.xlabel("Time (seconds)")
plt.ylabel("Magnitude")
# 显示网格线,方便人类眼睛对齐数值
plt.grid(True)
# 显示图例 (就是图表角落里解释红线蓝线是什么的小框框)
plt.legend()
# 弹出最终的画面
plt.show()