实验 07 · MATLAB + 状态空间
MATLAB 状态空间迭代
把一维数据向量扩展为二维状态,用 Python 参考结果检查 MATLAB 矩阵递推。
完整讲义
Lab 07:MATLAB 状态空间迭代
Lab 6 已经在熟悉的数据集上练习了 MATLAB 脚本、数组索引、逐元素运算、结果导出和人工 验证。本实验保留这些工具,只改变数学对象:从一组互不影响的数据,过渡到随时间演化的 二维状态。你将第一次使用矩阵乘法表达耦合递推,并用独立 Python 结果检查 MATLAB 实现。
开始前复用 Lab 4 的 Git 检查点流程:用 git status 确认工作区干净;完成跨语言验证后, 按相同方法提交 Lab 7 检查点。
学习目标
- 把 \(\boldsymbol{x}(k+1)=A\boldsymbol{x}(k)\) 翻译成可运行程序。
- 区分数据向量与状态向量,理解状态为何需要用列向量表示。
- 理解 MATLAB 中矩阵乘法、列索引和预分配的作用。
- 用独立的 Python 结果检查 MATLAB 实现。
- 生成可供后续实验复用的数据表、图像和实验记录。
从数据向量到状态向量
Lab 6 的 values 是一个 1 × 6 行向量,每个位置保存一条独立观测。本实验的 x0 是 一个 2 × 1 列向量,两个分量共同描述同一时刻的系统状态。两者都用数组保存,但数组方向 和运算含义不同:
| Lab 6 中的写法 | MATLAB 写法与差别 |
|---|---|
values = [2, 4, 5, 7, 11, 13] |
x0 = [2; -1];逗号分隔列,分号开始新的一行 |
values(1) 取第一条数据 |
x(:, 1) 取第一个时刻的全部状态分量 |
centeredValues .^ 2 逐元素平方 |
A * x0 用矩阵乘法组合两个状态分量 |
| 数据位置不表示时间递推 | x(:, k + 1) 由 x(:, k) 计算,列位置对应时间 |
表达式 A * x0 不是把两个数组对应元素相乘,而是按照矩阵的行与列形成两个新的状态分量。 这一步同时复用了 Lab 6 对数组形状的检查,并引入了状态空间模型所需的新运算。
先在 MATLAB 的 Files 面板中打开 labs/lab07/,然后在 Command Window 输入:
A = [0.9, 0.1; -0.2, 0.8];
x0 = [2; -1];
x1 = A * x0
输出应为列向量 [1.7; -1.2]。这一步只用于熟悉矩阵输入和命令窗口;正式结果仍必须由 state_iteration.m 从头生成,不能依赖工作区中手动留下的变量。
状态空间中的“状态”
“状态”不是某个固定的物理量,而是在模型给定后,决定未来演化所需的信息。如果两个 历史过程在当前时刻具有相同状态,并且以后受到相同输入,那么状态空间模型会给出相同的 后续演化。本实验没有外部输入,因此当前的 \(\boldsymbol{x}(k)\) 和矩阵 \(A\) 已足以确定 \(\boldsymbol{x}(k+1)\)。
二维状态 \(\boldsymbol{x}(k)=[x_1(k),x_2(k)]^{\mathsf T}\) 可以表示位置与速度、两个温度,或经过 坐标变换得到的两个数学变量。变量的实际含义由模型定义;“二维”只说明需要两个数共同描述 当前状态。
对一般矩阵 \(A=[a_{ij}]\),第 \(i\) 行给出第 i 个下一状态的计算规则,第 \(j\) 列表示 第 j 个当前状态如何进入各个更新式:
\[ x_i(k+1)=\sum_j a_{ij}x_j(k). \]
因此本实验的矩阵不是四个互不相关的数字,而是两条耦合更新式:
\[ \begin{aligned} x_1(k+1)&=0.9x_1(k)+0.1x_2(k),\\ x_2(k+1)&=-0.2x_1(k)+0.8x_2(k). \end{aligned} \]
目录结构
在现有课程项目中创建如下结构。Starter 压缩包提供两个源文件的起点;实验输出由你运行程序后生成。
labs/lab07/
python_state_reference.py
state_iteration.m
results/
python-state-reference.csv
state-history.csv
figures/
state-history.png
notes.md
可以从课程总览下载 Starter 文件,只把 starters/lab07/ 中的文件复制到当前实验目录。
小知识:状态不是系统的全部历史
状态不是把过去发生的一切全部保存下来,而是在模型成立时足以决定未来演化的信息。选择什么 量作为状态取决于模型;状态选得不足,未来就无法只由当前状态确定,选得过多则会带来冗余。
理论问题
考虑离散状态方程:
\[ \boldsymbol{x}(k+1)=A\boldsymbol{x}(k),\qquad \boldsymbol{x}(0)=\begin{bmatrix}2\\-1\end{bmatrix},\qquad A=\begin{bmatrix}0.9&0.1\\-0.2&0.8\end{bmatrix}. \]
\(\boldsymbol{x}(k)\) 是二维列向量。每一步都用同一个线性映射 \(A\) 生成下一状态。这个模型很简单,但已经包含后续控制实验的基本骨架:状态、系统矩阵、初值和时间索引。
先手算第一步:
\[ \boldsymbol{x}(1)=A\boldsymbol{x}(0) =\begin{bmatrix}1.7\\-1.2\end{bmatrix}. \]
这一行手算结果是程序的第一个检查点。若首步就不一致,应先检查矩阵、向量方向和索引,而不是继续画图。
公式到代码
Python 常用一维列表表示向量,而 MATLAB 的控制论代码通常明确使用列向量:
A = [0.9, 0.1; -0.2, 0.8];
x0 = [2; -1];
把全部状态预先放在一个 2 × (stepCount + 1) 矩阵中:
x = zeros(2, stepCount + 1);
x(:, 1) = x0;
for k = 1:stepCount
x(:, k + 1) = A * x(:, k);
end
这里 x(:, k) 表示第 k 列。数学中的 \(\boldsymbol{x}(0)\) 存在 MATLAB 第 1 列,数学中的 \(\boldsymbol{x}(k)\) 对应 MATLAB 第 k+1 列。这种偏移必须在笔记中写清楚。
预分配不是为了改变数学结果,而是提前固定数据形状,避免循环不断扩展数组,也使“每列是一时刻的状态”成为明确约定。
从状态矩阵到可复现结果
Lab 6 已经区分 Command Window、脚本、工作区和磁盘文件。这里沿用同一约定:Command Window 只用于首步试算和形状检查,正式轨迹必须由脚本生成。关闭 MATLAB 或执行 clear 后,工作区 变量会消失,但 .m 脚本、CSV 和图像仍保存在磁盘上。
本实验的完整数据流是:
A、x0 和 stepCount
↓ 状态递推
状态矩阵 x
↓ table / writetable
results/state-history.csv
↓ plot / exportgraphics
figures/state-history.png
mfilename("fullpath") 取得当前脚本的位置,fullfile 按 Windows 路径规则组合目录和文件名; writetable 把带列名的数据表写成 CSV,exportgraphics 把图窗保存成可用于报告的图像。这些 函数把计算与输出位置写进脚本,使结果不依赖 MATLAB 从哪个目录启动。
MATLAB 实验
- 先运行
python_state_reference.py,记录首步和第 12 步状态,并确认它生成results/python-state-reference.csv。 - 在 MATLAB 中打开并运行一次 Starter,观察
TODO尚未完成时的输出。 - 完成
state_iteration.m中的状态循环。 - 在命令窗口检查
size(x)是否为2 × 13。 - 用
writetable把k、x1、x2写入results/state-history.csv。 - 绘制两个状态分量随
k变化的曲线,补充坐标名、图例和标题。 - 保存
figures/state-history.png,执行clear后重新运行一次脚本。
不要把命令窗口里人工修改过的变量当成程序输入。脚本应从矩阵和初值开始独立生成全部结果。
数学验证
至少完成四项检查:
| 检查 | 方法 | 能发现的问题 |
|---|---|---|
| 首步检查 | 比较 x(:,2) 与 [1.7; -1.2] |
矩阵抄写、向量方向或索引错误 |
| 形状检查 | assert(isequal(size(x), [2, 13])) |
状态历史的行列约定错误 |
| 跨语言检查 | 比较 Python 与 MATLAB 第 12 步 | 循环次数或更新次序错误 |
| 递推残差 | 计算 norm(x(:,k+1)-A*x(:,k)) |
输出与公式不一致 |
递推残差 \(\lVert\boldsymbol{x}(k+1)-A\boldsymbol{x}(k)\rVert_2\) 接近机器精度,只能说明保存的轨迹满足这条递推关系;它不能单独证明所有初值下的长期性质。
结果与记录
把 state-history.csv 看成原始数值结果,把 state-history.png 看成快速预览。notes.md 写四段短说明:模型、参数、验证方法和限制。图注可以写成:
> 二维离散系统在给定初值下的 12 步状态轨迹。MATLAB 结果与独立 Python 实现逐点一致。
避免写“系统已经被证明稳定”。当前实验只观察一个矩阵、一个初值和有限步数。
OpenCode 审计
复用 Lab 4 的 OpenCode 工作流。输入 /models,确认当前模型是 DeepSeek V4 Pro;按 Tab 进入 Plan 模式后再进行只读检查。先独立完成首步计算,再使用中文提示词:
请检查 labs/lab07/state_iteration.m。
背景:数学模型是 x(k+1)=A*x(k),每一列保存一个时刻的二维状态。
请完成以下工作:
1. 解释每段 MATLAB 代码对应哪一个数学对象;
2. 检查数学索引 k 与 MATLAB 列索引之间是否偏移正确;
3. 检查状态矩阵如何经过 table、writetable 和 exportgraphics 生成 CSV 与图像;
4. 只提出必要修改,不要改变给定矩阵和初值;
5. 说明这些数值检查能验证什么、不能证明什么。
如果 OpenCode 只说“代码看起来正确”,继续追问它给出具体的首步数值、数组尺寸和残差 表达式。接受计划后按 Tab 进入 Build 模式。修改完成后,在 VS Code 的第二个 PowerShell 终端运行 git status 和 git diff -- labs\lab07,再决定是否保留改动。
完成标准
- Python 与 MATLAB 的第 12 步结果在显示精度内一致。
- 两个 CSV 都有 13 行数据,图像可由脚本重新生成。
- 能根据矩阵的一行解释一个下一状态如何由当前状态计算。
- 能区分脚本、工作区、CSV 和图像各自保存的内容。
notes.md能准确区分公式实现、数值验证和稳定性证明。- 提交 Lab 7 检查点后,
git status显示工作区干净。