SymPy 控制系统模块 LTI API 完全指南:传递函数、状态空间与离散化实战
【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy
导读
本文以 SymPy 官方文档 doc/src/modules/physics/control/lti.rst 为骨架,系统讲解sympy.physics.control模块中 LTI(线性时不变)系统的全部公开 API,包括连续/离散传递函数、串联并联与反馈互联、MIMO 传递函数矩阵、PID 控制器、状态空间模型以及三种经典离散化方法。读完本文,你将能够用 SymPy 以纯符号方式建模、组合、分析控制系统的传递函数与状态空间,并完成连续到离散的变换,全程无需数值近似。
一、模块概览:纯符号的控制系统工具箱
sympy.physics.control是 SymPy 的物理控制系统子模块,当前聚焦于LTI(线性时不变)系统。根据官方总述 doc/src/modules/physics/control/control.rst,该模块的核心建模对象包括:
- 连续时间传递函数:
TransferFunction,工作在拉普拉斯域(复变量s); - 离散时间传递函数:
DiscreteTransferFunction,工作在 z 域(复变量z); - 系统互联结构:
Parallel(并联,利用加法性)、Series(串联,利用乘法性)与Feedback(负反馈互联); - MIMO 扩展:以
TransferFunctionMatrix为基类的MIMOSeries、MIMOParallel、MIMOFeedback; - 状态空间模型:
StateSpace与DiscreteStateSpace,支持可控性、可观性分析以及状态空间与传递函数之间的相互转换。
该模块最大的特色是纯符号计算:它不依赖数值方法逼近解,求得的解精度高、形式紧凑,可直接用于后续的符号化简与进一步分析。整个 LTI 实现集中在 sympy/physics/control/lti.py(约 6800 行),并通过 sympy/physics/control/init.py 对外导出TransferFunction、Series、Parallel、Feedback、StateSpace等 19 个符号。
API 文档入口位于 doc/src/modules/physics/control/index.rst,除本文讲解的lti.rst外,还包含绘图模块 doc/src/modules/physics/control/control_plots.rst 与劳斯表模块 doc/src/modules/physics/control/routh_table.rst。
二、类的继承体系:LinearTimeInvariant 家族
从源码结构(sympy/physics/control/lti.py#L405-L478)可以看到清晰的继承层级:
Basic (SymPy 基类) + EvalfMixin └── LinearTimeInvariant (ABC,所有 LTI 系统的公共基类) ├── SISOLinearTimeInvariant (单输入单输出,_is_SISO = True) │ ├── TransferFunctionBase → TransferFunction / DiscreteTransferFunction │ ├── Series / Parallel / Feedback │ └── PIDController └── MIMOLinearTimeInvariant (多输入多输出,_is_SISO = False) ├── TransferFunctionMatrix ├── MIMOSeries / MIMOParallel / MIMOFeedback └── StateSpace / DiscreteStateSpace (按 D 矩阵维数自动判定 SISO/MIMO)LinearTimeInvariant(lti.py#L405)本身是抽象类,直接实例化会抛出NotImplementedError。它提供了三个关键属性:
is_continuous:系统是否为连续时间;is_SISO:是否为单输入单输出;sampling_time:采样时间(抽象属性,子类实现)。
SISOLinearTimeInvariant固定num_inputs = 1、num_outputs = 1;MIMOLinearTimeInvariant的_is_SISO = False。在_check_time_compatibility(lti.py#L499)中,系统组合前会强制校验:所有系统要么同为连续、要么同为离散,且离散系统的采样时间必须一致,否则抛出TypeError。这一兼容性检查通过_compatibility_decorator(lti.py#L525)装饰在各类互联运算上。
三、传递函数基类 TransferFunctionBase
TransferFunctionBase(lti.py#L582)是所有传递函数对象的抽象基类,官方明确说明"不应直接使用",真正的用户入口是它的两个子类。它的类文档给出了完整的数学背景:LTI 系统可由线性微分方程(连续)或线性差分方程(离散)描述,通过拉普拉斯变换或 z 变换(零初始条件下)转换为复变量域中的有理函数:
$$H(p) = \frac{Y(p)}{X(p)} = \frac{a_{n}p^{n}+a_{n-1}p^{n-1}+\dots+a_{1}p+a_{0}}{b_{m}p^{m}+b_{m-1}p^{m-1}+\dots+b_{1}p+b_{0}}$$
其中分子是输出信号的变换、分母是输入信号的变换。基类构造时的校验规则(lti.py#L698)包括:var必须是Symbol,den不能为零,分子分母必须是数或含符号的表达式。
3.1 三种便捷构造类方法
除了直接TransferFunction(num, den, var)构造外,基类提供了三个工厂类方法:
from_rational_expression(expr, var=None):直接从一个有理表达式构造。若表达式只有一个自由符号可省略var;若有多个变量而未指定var会抛出ValueError;纯数字表达式必须手动指定var(lti.py#L729):
>>> from sympy.abc import s, p, a, z >>> from sympy.physics.control.lti import TransferFunction, DiscreteTransferFunction >>> expr1 = (s + 5)/(3*s**2 + 2*s + 1) >>> tf1 = TransferFunction.from_rational_expression(expr1) TransferFunction(s + 5, 3*s**2 + 2*s + 1, s) >>> expr3 = (z + 1)/(z**2 + 2*z + 1) >>> dtf = DiscreteTransferFunction.from_rational_expression(expr3, z, sampling_time=0.1) DiscreteTransferFunction(z + 1, z**2 + 2*z + 1, z, 0.1)from_coeff_lists(num_list, den_list, var):从降幂系数列表构造(lti.py#L819):
>>> tf = TransferFunction.from_coeff_lists([1, 0, 2], [3, 2, 2, 1], s) TransferFunction(s**2 + 2, 3*s**3 + 2*s**2 + 2*s + 1, s)from_zpk(zeros, poles, gain, var):从零点、极点与增益构造(lti.py#L876),支持复数零极点与符号参数:
>>> tf2 = TransferFunction.from_zpk([0], [1-1j, 1+1j, 2], -2, s) TransferFunction(-2*s, (s - 2)*(s - 1.0 - 1.0*I)*(s - 1.0 + 1.0*I), s)3.2 核心属性与方法
| 名称 | 说明 | 源码位置 |
|---|---|---|
num/den/var | 分子多项式、分母多项式、复变量 | lti.py#L927-L985 |
poles()/zeros() | 返回极点、零点列表;高阶多项式自动用rootof表示 | lti.py#L1063-L1100 |
dc_gain() | 频率趋近 0 时的增益,纯积分器返回oo | lti.py#L1032 |
is_stable() | 通过劳斯-赫尔维茨条件判断稳定性 | lti.py#L1131 |
is_proper/is_strictly_proper/is_biproper | 判断传递函数正则性 | lti.py#L1501-L1561 |
expand() | 展开分子分母 | lti.py#L1011 |
to_expr() | 转换为有理表达式 | lti.py#L1563 |
rewrite(StateSpace) | 转换为可控规范型状态空间 | lti.py#L1771 |
注意poles()/zeros()的实现(lti.py#L41-L47):先尝试roots求重根,若求根数量不足阶数,则回退到rootof用根对象精确表示高阶多项式的根,从而保持符号精确性。
四、TransferFunction:连续时间传递函数
TransferFunction(lti.py#L1593)表示拉普拉斯域中的 LTI 系统,构造参数为num、den、var,且_is_continuous = True、sampling_time恒为 0。
类文档展示的典型用法(均可在源码 docstring 中验证):
>>> from sympy.abc import s, p, a >>> tf1 = TransferFunction(s + a, s**2 + s + 1, s) TransferFunction(a + s, s**2 + s + 1, s)- 复变量任意:
var不限于s,任何Symbol均可,如TransferFunction(a*p**3 - a*p**2 + s*p, p + a**2, p); - 取负:一元
-运算符直接作用于分子; - 乘方:
**运算符支持整数幂,包括 0 次与负次幂(tf**-1即分子分母互换); - 算术运算生成结构对象:
+、-、*分别生成未求值的Parallel与Series对象,例如tf9 * tf10返回Series(...);这些结构可用.doit()或.rewrite(TransferFunction)合并为单一传递函数(lti.py#L1707-L1713)。
TransferFunction.dc_gain()通过limit(num/den, var, 0)求极限实现;get_asymptotic_stability_conditions()则对分母多项式调用hurwitz_conditions()返回一组不等式条件(lti.py#L1756)。
五、DiscreteTransferFunction:离散时间传递函数
DiscreteTransferFunction(lti.py#L1814)表示 z 域中的 LTI 系统,构造参数为num、den、var、sampling_time。与连续版本的关键差异:
sampling_time为第四个位置参数,表示两次连续采样时刻的间隔;- 未指定时默认采样时间为 1;
- 若显式设为 0,则直接返回一个
TransferFunction实例; sampling_time既可以是数也可以是Symbol,便于做符号化的采样周期分析。
从构造函数逻辑看(create_transfer_function,lti.py#L534):sampling_time == 0返回连续对象,sampling_time > 0返回离散对象。这也是两个类之间分派的核心工厂:
>>> from sympy.abc import s, z >>> tf = create_transfer_function(s + 5, 3*s**2 + 2*s + 1, s) TransferFunction(s + 5, 3*s**2 + 2*s + 1, s) >>> dtf = create_transfer_function(z, z + 1, z, 0.1) DiscreteTransferFunction(z, z + 1, z, 0.1)六、系统互联:Series、Parallel 与 Feedback
6.1 Series(串联)与 Parallel(并联)
Series(lti.py#L2436)利用传递函数的乘法性表示级联:总传递函数等于各子系统之积;Parallel(lti.py#L3084)利用加法性表示并联:总传递函数等于各子系统之和。
两者都是SISOLinearTimeInvariant子类。构造时可以传入任意多个系统,_flatten_args会扁平化嵌套的同类型参数;所有成员必须使用同一个复变量(见LinearTimeInvariant._check_args,lti.py#L417)。调用.doit()可求值为合并后的单一传递函数。典型用法:
>>> from sympy.abc import s >>> G1 = TransferFunction(1, s + 1, s) >>> G2 = TransferFunction(s, s**2 + 2, s) >>> series = G1 * G2 # 等价于 Series(G1, G2) >>> series.doit() TransferFunction(s, (s + 1)*(s**2 + 2), s)6.2 Feedback(负反馈)
Feedback(lti.py#L3673)表示两个输入输出系统之间的负反馈互联。设前向路径为G、反馈路径为H,则闭环传递函数为:
$$\frac{G}{1 + G \cdot H}$$
其doit()方法(lti.py#L3968)默认对结果做约分(cancel=True),并支持expand选项控制是否展开分子分母。
七、PIDController:PID 控制器
PIDController(lti.py#L2273)是TransferFunction的子类,用于在拉普拉斯域表示 PID 控制器。构造参数及其默认值:
| 参数 | 含义 | 默认值 |
|---|---|---|
kp | 比例增益 | Symbol('kp') |
ki | 积分增益 | Symbol('ki') |
kd | 微分增益 | Symbol('kd') |
tf | 微分滤波时间常数(用于滤除噪声) | 0 |
var | 复频率变量 | s |
从源码(lti.py#L2335-L2340)可以看到其内部实现:构造时直接生成等效传递函数的分子kp*tf*var**2 + kp*var + ki*tf*var + ki + kd*var**2与分母tf*var**2 + var。也就是说,tf > 0时实现的是带微分滤波的实用 PID 形式;tf = 0时退化为经典 PID。
>>> from sympy import symbols >>> from sympy.physics.control.lti import PIDController >>> kp, ki, kd = symbols('kp ki kd') >>> p1 = PIDController(kp, ki, kd) PIDController(kp, ki, kd, 0, s) >>> p1.doit() TransferFunction(kd*s**2 + ki + kp*s, s, s) >>> p1.kp, p1.ki, p1.kd, p1.tf, p1.var (kp, ki, kd, 0, s)doit()方法将其转换为普通TransferFunction;各增益通过只读属性kp、ki、kd、tf、var访问。PID 对象可直接参与Series、Parallel、Feedback互联,非常适合符号化设计控制器并分析闭环系统。
八、MIMO 系统:TransferFunctionMatrix 及其互联
8.1 TransferFunctionMatrix
TransferFunctionMatrix(lti.py#L4605)是 MIMO(多输入多输出)传递函数矩阵的基类,是MIMOLinearTimeInvariant的子类。它将多个 SISO 传递函数组织为矩阵形式,其中每个元素是TransferFunction或DiscreteTransferFunction,且所有元素必须共享同一个复变量与采样时间。
>>> from sympy.abc import s >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix >>> G11 = TransferFunction(1, s + 1, s) >>> G12 = TransferFunction(s, s**2 + 2, s) >>> G21 = TransferFunction(1, s**2 + 3*s + 2, s) >>> G22 = TransferFunction(1, s + 4, s) >>> tfm = TransferFunctionMatrix([[G11, G12], [G21, G22]])8.2 MIMOSeries、MIMOParallel、MIMOFeedback
它们是Series、Parallel、Feedback的 MIMO 等价物:
MIMOSeries(lti.py#L2802):MIMO 串联,要求各系统的输入输出维数匹配;MIMOParallel(lti.py#L3403):MIMO 并联,要求输入输出维数相同;MIMOFeedback(lti.py#L4131):MIMO 反馈互联,内部用_is_invertible(lti.py#L4120)判断I + G·H是否可逆。
MIMO 系统之间做运算时同样经过_check_other_MIMO与_check_time_compatibility校验,保证维数与时间域一致。doit()方法(如 lti.py#L4475)将 MIMO 互联结构约简为单一TransferFunctionMatrix。
九、状态空间:StateSpace 与 DiscreteStateSpace
9.1 数学定义与构造
StateSpaceBase(lti.py#L5383)定义了标准状态空间模型。连续时间系统为:
$$\dot{x}(t) = Ax(t) + Bu(t), \qquad y(t) = Cx(t) + Du(t)$$
离散时间系统为:
$$x[k+1] = Ax[k] + Bu[k], \qquad y[k] = Cx[k] + Du[k]$$
四矩阵含义:A状态矩阵、B输入到状态矩阵、C状态到输出矩阵、D前馈(直通)矩阵。四个矩阵都可省略,缺省时自动补零矩阵(A缺省为 1×1 零矩阵,其余按维数推导)。构造时的形状校验(lti.py#L5455-L5475)包括:
A必须是方阵;A与B行数相同;C与D行数相同;A与C列数相同;B与D列数相同。
任何不匹配都会抛出ShapeError。系统是 SISO 还是 MIMO 由D矩阵的维数自动判定(1×1 为 SISO)。
>>> from sympy import Matrix >>> A = Matrix([[1, 0], [0, 1]]) >>> B = Matrix([1, 0]) >>> C = Matrix([1, 0]).T >>> D = Matrix([0]) >>> create_state_space(A, B, C, D) StateSpace(Matrix([[1, 0], [0, 1]]), Matrix([[1], [0]]), Matrix([[1, 0]]), Matrix([[0]]))create_state_space(A, B, C, D, sampling_time=0)(lti.py#L5333)与传递函数工厂逻辑一致:sampling_time == 0返回StateSpace,否则返回DiscreteStateSpace。
9.2 核心能力
StateSpaceBase提供矩阵访问属性state_matrix、input_matrix、output_matrix、feedforward_matrix,以及状态空间→传递函数转换(to_transfer_function/transfer_function_matrix)。MIMO 状态空间也受支持,可通过num_inputs、num_outputs、is_SISO查询结构。
两个关键的分析方法是:
is_controllable()(lti.py#L6369):通过可控性矩阵的秩判定系统能否由输入驱动至任意状态;is_observable()(lti.py#L6251):通过对偶可观性矩阵的秩判定能否从输出重构全部状态。
>>> ss = StateSpace(Matrix([[0, 1], [-2, -3]]), Matrix([0, 1]), Matrix([[1, 0]]), Matrix([0])) >>> ss.is_controllable() True >>> ss.is_observable() TrueStateSpace(lti.py#L6450)与DiscreteStateSpace(lti.py#L6644)分别是连续与离散的具体实现。传递函数→状态空间方向通过TransferFunction.rewrite(StateSpace)完成,返回可控规范型表示(lti.py#L1771);文档同时指出该转换不唯一——同一传递函数存在多个等价状态空间实现。反之,连续传递函数不能改写为离散状态空间(会抛TypeError,lti.py#L1801)。
十、连续系统离散化:gbt、bilinear、forward_diff、backward_diff
这四个顶层函数用于把连续时间传递函数H(s)离散化为 z 域传递函数,返回降幂排列的分子、分母系数列表(即H(z) = (az + b)/(cz + d)返回[a, b]、[c, d])。所有函数都要求输入为 SISO 系统,MIMO 会抛NotImplementedError。
| 函数 | 替换关系 | alpha 参数 | 源码位置 |
|---|---|---|---|
gbt(tf, sample_per, alpha) | $s(z)=\frac{z-1}{T(\alpha z + (1-\alpha))}$ | 用户指定,自动有理化 | lti.py#L49 |
bilinear(tf, sample_per) | $s(z)=\frac{2}{T}\frac{z-1}{z+1}$(双线性/塔斯廷变换) | 恒为 1/2 | lti.py#L135 |
forward_diff(tf, sample_per) | $s(z)=\frac{z-1}{T}$(前向差分) | 恒为 0 | lti.py#L173 |
backward_diff(tf, sample_per) | $s(z)=\frac{z-1}{Tz}$(后向差分) | 恒为 1 | lti.py#L211 |
实际上后三者都是gbt的特例(lti.py#L171 等),核心实现gbt会对系数做归一化(除以分母首项系数),并将alpha通过Rational(...).limit_denominator(1000)有理化以便符号化简。官方文档示例以 RL 一阶电路 $H(s)=1/(sL+R)$ 为对象:
>>> from sympy.physics.control.lti import TransferFunction, gbt >>> from sympy.abc import s, L, R, T >>> tf = TransferFunction(1, s*L + R, s) >>> numZ, denZ = gbt(tf, T, 0.5) >>> numZ [T/(2*(L + R*T/2)), T/(2*(L + R*T/2))] >>> denZ [1, (-L + R*T/2)/(L + R*T/2)]将alpha分别取 0、1、0.3 即可复现前向差分、后向差分与一般化的广义双线性结果,离散化结果以采样周期T的符号表达式给出,便于后续做符号层面的离散化误差分析。
十一、频域分析:phase_margin 与 gain_margin
模块还提供两个频域稳定性指标函数(非 autodoc 列表成员,但属于 lti 模块并随包导出):
phase_margin(system)(lti.py#L249):返回连续系统的相位裕度。实现上令s = jω代入,先求幅值等于 1(0 dB)处的穿越频率,再在该频率处计算相位并映射到(-180°, 180°]。gain_margin(system)(lti.py#L332):返回增益裕度。先求相位穿越-180°的频率,再取该处幅值的负 dB 值;无穿越频率时返回oo。
两者的共同约束:仅适用于能生成有效波特图的 SISO 连续系统;含时延项(exp项)抛NotImplementedError;若存在除变换变量外的多余自由符号则抛ValueError。
>>> from sympy.physics.control import TransferFunction, phase_margin, gain_margin >>> from sympy.abc import s >>> tf = TransferFunction(1, s**3 + 2*s**2 + s, s) >>> phase_margin(tf).n() 21.3863897518751 >>> gain_margin(tf).n() 6.02059991327962十二、测试验证与扩展阅读
模块的测试覆盖非常完整:sympy/physics/control/tests/test_lti.py(近 5000 行)逐一验证了本文涉及的各类构造方法、doit()化简、稳定性与可控/可观性判定、离散化函数以及 SISO/MIMO 互联的边界条件(如变量不一致、采样时间冲突、矩阵维数不符等异常路径),是学习 API 语义与坑点的第一手资料。
与 LTI 模型配套的还有两个模块:
- sympy/physics/control/control_plots.py:为传递函数与状态空间提供波特图、奈奎斯特图、根轨迹等绘图功能(对应文档 doc/src/modules/physics/control/control_plots.rst);
- sympy/physics/control/routh_table.py:构建劳斯表以判定多项式稳定性(对应文档 doc/src/modules/physics/control/routh_table.rst)。
总结
sympy.physics.control.lti提供了一个自成体系的纯符号 LTI 控制系统建模与分析栈:从 SISO 的TransferFunction/DiscreteTransferFunction,到Series/Parallel/Feedback互联,再到 MIMO 的TransferFunctionMatrix与状态空间的StateSpace,最后以gbt系列完成连续到离散的转换。所有结果均为精确符号表达式,可直接用于理论推导、符号化简与进一步的自定义分析,是控制系统课程、控制器设计验证与自动化教学实践的可靠工具。
【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考