SymPy Control 模块完全指南:用纯 Python 符号化建模与求解 LTI 控制系统
2026/9/15 3:33:10 网站建设 项目流程

SymPy Control 模块完全指南:用纯 Python 符号化建模与求解 LTI 控制系统

【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy

SymPy 的sympy.physics.control是一个纯 Python 实现的符号化控制系统建模与分析模块,专注于线性时不变(LTI,Linear Time-Invariant)系统:既支持 Laplace 域(s 域)的连续时间模型,也支持 z 域的离散时间模型;既能处理单输入单输出(SISO)系统,也能处理多输入多输出(MIMO)系统。读完本文,你将掌握如何用TransferFunctionStateSpaceSeries/Parallel/Feedback等核心类完成从建模仿真、稳定性分析到频域绘图(Bode、Nyquist、Nichols 等)的完整控制工程工作流,并获得精确的符号解而非数值近似。本文档以 doc/src/modules/physics/control/index.rst 及其子页面为骨架,结合 lti.py、control_plots.py、routh_table.py 源码展开。

模块定位:符号化的控制系统工具箱

根据 control.rst 的说明,该模块当前能够处理LTI 系统,并提供了两类互补的建模范式:

  • 传递函数(Transfer Function):系统输入到输出的频域表示。连续时间系统用TransferFunction在 Laplace 域(变量s)表示,离散时间系统用DiscreteTransferFunction在 z 域(变量z)表示。
  • 状态空间(State Space)StateSpaceDiscreteStateSpace分别建模连续与离散状态空间系统,支持可控性(controllability)、可观性(observability)分析,以及与传递函数表示之间的互相转换,同时完整支持 MIMO 状态空间系统。

模块的核心卖点是符号计算的精确性:与依赖数值迭代逼近的数值控制包不同,这里得到的解是高度精确的符号表达式,且形式紧凑、可直接用于后续分析。全部核心代码集中在 sympy/physics/control/lti.py(约 6800 行),绘图功能在 sympy/physics/control/control_plots.py,劳斯判据表在 sympy/physics/control/routh_table.py。

sympy.physics.control在包的__init__.py中统一导出了所有公共 API(见 sympy/physics/control/init.py),因此可直接通过如下方式导入:

from sympy.physics.control import ( TransferFunction, DiscreteTransferFunction, PIDController, Series, Parallel, Feedback, TransferFunctionMatrix, MIMOSeries, MIMOParallel, MIMOFeedback, StateSpace, DiscreteStateSpace, gbt, bilinear, forward_diff, backward_diff, phase_margin, gain_margin, RouthHurwitz, pole_zero_plot, bode_plot, step_response_plot, impulse_response_plot, ramp_response_plot, nyquist_plot, nichols_plot, )

传递函数模型:从有理多项式到频域对象

TransferFunction:连续时间传递函数

TransferFunction(num, den, var)用三个参数构造:numden分别是分子与分母多项式(可以是多项式、常数甚至其他符号表达式),var是 Laplace 变换的复变量,必须是Symbol类型(源码在 lti.py 中强制校验)。分母为零会抛出ValueError

>>> from sympy.abc import s, p, a >>> from sympy.physics.control import TransferFunction >>> tf1 = TransferFunction(s + a, s**2 + s + 1, s) >>> tf1.num a + s >>> tf1.den s**2 + s + 1 >>> tf1.var s

var不强制为s,任意Symbol都可作为变换变量,例如TransferFunction(a*p**3 - a*p**2 + s*p, p + a**2, p)。分子分母也可以是浮点数、整数等常量:TransferFunction(1/2, 4, s)

DiscreteTransferFunction:离散时间传递函数

DiscreteTransferFunction(num, den, var, sampling_time=1)是 z 域对应物,第四个参数sampling_time表示两个相邻采样时刻的时间间隔,默认为 1,可传数值或符号(如k)。注意两点约束(源码见 lti.py):

  • sampling_time == 0会被拒绝——想建连续模型请用TransferFunction类;
  • sampling_time是必要概念:离散模型的互连运算会通过_check_time_compatibility校验所有系统要么同为连续、要么同为离散且采样时间一致。
>>> from sympy.abc import z, a, k >>> from sympy.physics.control import DiscreteTransferFunction >>> dtf = DiscreteTransferFunction(z**2 + a*z, z**2 + z + 1, z, 0.1) >>> dtf.sampling_time 0.1

便捷工厂与构造辅助方法

create_transfer_function(num, den, var, sampling_time=0)是一个分派函数:sampling_time == 0返回TransferFunction,否则返回DiscreteTransferFunction(见 lti.py)。

TransferFunctionBase还提供了三个高效的类方法构造器(lti.py),非常实用:

# 1) 从有理表达式构造(自动识别变量,多变量冲突时需手动指定 var) tf1 = TransferFunction.from_rational_expression((s + 5)/(3*s**2 + 2*s + 1)) # 2) 从系数列表构造(系数按降幂排列,长度可不同) tf2 = TransferFunction.from_coeff_lists([1, 0, 2], [3, 2, 2, 1], s) # 3) 从零点、极点、增益构造(ZPK 形式,支持复数零极点) tf3 = TransferFunction.from_zpk([1, 2, 3], [6, 5, 4], 7, s)

离散版本同样提供这些类方法,并额外提供了四个从连续模型做离散化的类方法(from_gbtfrom_bilinearfrom_forward_difffrom_backward_diff),将在后文"连续到离散的转换"一节详解。

传递函数的核心分析与代数能力

从 lti.py 的TransferFunctionBase源码看,每个传递函数对象都内置了完整的分析与代数接口:

  • 结构属性numdenvaris_proper(分子次数 ≤ 分母次数)、is_strictly_properis_biproper(次数相等)。
  • 动态分析
    • poles()/zeros():求解极点与零点,对高阶多项式会退化为用rootof表示的精确根;
    • dc_gain():频率趋于 0 时的增益,纯积分器系统返回oo;连续系统取limit(num/den, var, 0),离散系统取limit(..., var, 1)
    • eval_frequency(other):在复平面任意点求系统响应,如tf.eval_frequency(I*omega)得到频响;
    • to_expr():转换回普通 SymPyExpr,便于与其余符号计算无缝衔接;
    • expand()/to_standard_form(cancel_poles_zeros=False):展开或规整为标准有理式;
    • is_stable(cancel_poles_zeros=False):基于 Hurwitz/Schur 条件判断渐近稳定性(不覆盖临界稳定情形)。
  • 代数运算符重载+-*/**、一元-。其中加减乘会产生未求值Parallel/Series对象,通过.doit().rewrite(TransferFunction)求值成单个传递函数;幂运算要求整数指数,负指数会翻转分子分母;tf1 / tf2会被解析为Series(tf1, tf2 的倒数)。例如:
>>> tf9 = TransferFunction(s + 1, s**2 + s + 1, s) >>> tf10 = TransferFunction(s - p, s + 3, s) >>> tf9 + tf10 # 未求值的并联 Parallel(...) >>> (tf9 + tf10).doit() # 求值为单个传递函数 TransferFunction(...)

系统互联:Series、Parallel 与 Feedback

这是模块的核心工程能力:把多个子系统按拓扑连接成整体。

串并联

  • Series(*systems)表示串联(乘法性质),默认evaluate=False,可传evaluate=True直接求值;要求所有子系统使用同一个复变量,否则抛ValueError(校验逻辑见 lti.py 的_check_args)。
  • Parallel(*systems)表示并联(加法性质),同样要求同变量。

两者都支持传入 SISO 状态空间对象(StateSpace),此时.doit()会返回等价的状态空间模型;也都有is_properis_strictly_properis_biproper属性(作用于求值后的结果)。

Feedback:闭环反馈

Feedback(sys1, sys2=None, sign=-1)表示两个 SISO 系统之间的闭环反馈互联(lti.py):

  • sys1:前向通路(被控对象 plant);
  • sys2:反馈通路(常为反馈控制器),缺省时自动取单位传递函数 1(单位反馈);
  • sign:反馈符号,-1为负反馈(默认),1为正反馈。
>>> from sympy.physics.control import Feedback, TransferFunction >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s) >>> controller = TransferFunction(5*s - 10, s + 7, s) >>> F1 = Feedback(plant, controller) >>> F1.sys1 # 前向通路 >>> F1.sys2 # 反馈通路 >>> F1.doit() # 求值得到闭环传递函数 TransferFunction(...)

Feedback还提供:

  • sign属性返回反馈符号,num/den属性给出闭环分子与分母结构;
  • sensitivity属性返回闭环系统的灵敏度函数1/(1 - sign·sys1·sys2)(注意:不返回互补灵敏度函数);
  • doit(cancel=False, expand=False)两个关键字分别控制是否约分公共因子、是否展开结果;
  • 当参数中含状态空间对象时,doit()返回等价StateSpacerewrite(TransferFunction)可转回传递函数。

PID 控制器:一行构造工业标准控制器

PIDController(kp, ki, kd, tf, var)TransferFunction的子类(lti.py),在 Laplace 域直接表示 PID 控制器的传递函数:

  • kpkikd:比例、积分、微分增益,缺省时分别自动取符号kpkikd
  • tf:微分滤波器时间常数(用于滤除噪声),缺省为 0
  • var:复频率变量,缺省为s
>>> from sympy import symbols >>> from sympy.physics.control import PIDController >>> kp, ki, kd = symbols('kp ki kd') >>> p1 = PIDController(kp, ki, kd) >>> p1.doit() # 转换为普通传递函数 TransferFunction(kd*s**2 + ki + kp*s, s, s) >>> p1.kp, p1.ki, p1.kd, p1.tf, p1.var

其传递函数结构为(kp·tf·s² + kp·s + ki·tf·s + ki + kd·s²) / (tf·s² + s),当tf = 0时退化为经典 PID 形式(kd·s² + kp·s + ki)/sdoit()将其转化为普通TransferFunction,方便直接接入Series/Feedback进行闭环设计。

MIMO 系统:传递函数矩阵与互联

模块通过TransferFunctionMatrix及其配套互联类完整支持多输入多输出系统。

TransferFunctionMatrix

TransferFunctionMatrix(arg)是 MIMO 传递函数模型的基类(lti.py),arg严格嵌套列表,元素为TransferFunction、SISOSeriesParallel对象。行数 = 输出数(num_outputs),列数 = 输入数(num_inputs),shape返回(num_outputs, num_inputs)

>>> from sympy import Matrix, pprint >>> from sympy.abc import s >>> from sympy.physics.control import TransferFunctionMatrix >>> M = Matrix([[s, 1/s], [1/(s+1), s]]) >>> tfm = TransferFunctionMatrix.from_Matrix(M, s) # 从普通矩阵一行代码转换 >>> pprint(tfm, use_unicode=False) [ s 1] [ - -] [ 1 s] [ ] [ 1 s] [----- -] [s + 1 1]{t}

常用操作包括:

  • from_Matrix(matrix, var, sampling_time=0):从普通 SymPy 矩阵高效构造(sampling_time非 0 时生成离散版本);
  • 索引切片:tfm[i, j]取单个传递函数,tfm[:, 0]tfm[0, :]取整列/整行并返回新的矩阵;
  • transpose():转置以交换输入输出层;
  • elem_poles()/elem_zeros():逐元素求零极点(注意:文档明确提示 MIMO 系统真正的零极点不是单个元素零极点的简单汇总);
  • eval_frequency():在每个元素上求频响,返回普通矩阵;
  • subs():支持单个与多个符号替换,且不修改原对象;
  • .doit():把矩阵内部的Series/Parallel元素化简为单个TransferFunction
  • 运算符:+/-/*产生MIMOParallel/MIMOSeries未求值对象,矩阵乘法要求维度匹配(前者输入数等于后者输出数)。

MIMO 互联类

  • MIMOSeries(*systems, evaluate=False):MIMO 串联。注意一个反直觉约定(源码 lti.py 明确说明):MIMOSeries(A, B)等价于B*A,总是逆序;相邻系统必须满足"前一系统输出数 = 后一系统输入数"。
  • MIMOParallel(*systems, evaluate=False):MIMO 并联,要求所有成员shape相同。
  • MIMOFeedback(sys1, sys2, sign=-1):MIMO 闭环反馈,要求sys1sys2的乘积为方阵且系统可逆(doit()基于灵敏度矩阵(I - sign·sys1·sys2)⁻¹·sys1求值,lti.py)。

三者均支持 MIMOStateSpace参数,doit()返回等价的状态空间实现。

状态空间模型:建模、分析与转换

StateSpace 与 DiscreteStateSpace

StateSpace(A, B, C, D)表示连续时间状态空间模型,满足:

x'(t) = A·x(t) + B·u(t) y(t) = C·x(t) + D·u(t)

DiscreteStateSpace(A, B, C, D, sampling_time=1)对应离散形式x[k+1] = A·x[k] + B·u[k]y[k] = C·x[k] + D·u[k](默认采样时间 1,同样拒绝 0)。四个矩阵均可省略——A缺省为1×1零矩阵,B/C/D自动补零到兼容维度(见 lti.py)。构造时源码会对矩阵维度做严格校验(A 必须方阵、A 行数等于 B 行数、C 行数等于 D 行数、A 列数等于 C 列数、B 列数等于 D 列数),输入非sympy矩阵会抛TypeError

>>> from sympy import Matrix >>> from sympy.physics.control import StateSpace >>> A = Matrix([[1, 2], [1, 0]]) >>> B = Matrix([1, 1]) >>> C = Matrix([[0, 1]]) >>> D = Matrix([0]) >>> ss = StateSpace(A, B, C, D) # 只给 A、B 也可,其余自动补零 >>> ss.num_states # 2 >>> ss.shape # (1, 1),即 (num_outputs, num_inputs)

状态空间分析能力

StateSpaceBase提供了完备的可控性/可观性分析工具箱(lti.py):

  • 矩阵访问A/B/C/D(别名state_matrixinput_matrixoutput_matrixfeedforward_matrix)、num_statesnum_inputsnum_outputsshape
  • 可控性controllability_matrix()controllable_subspace()uncontrollable_subspace()is_controllable()(可控矩阵秩 == 状态数);
  • 可观性observability_matrix()observable_subspace()unobservable_subspace()is_observable()
  • 标准形分解to_controllable_form()to_observable_form()(基于相似变换把 A/B、A/C 化为块三角形式分离可控/不可控、可观/不可观子系统)、apply_similarity_transform(T)
  • 稳定性条件get_asymptotic_stability_conditions(fast=False)返回 A 矩阵特征多项式 Hurwitz(连续)/Schur(离散)不等式列表,配合reduce_inequalities可解出参数稳定域,例如:
>>> k = symbols('k') >>> A = Matrix([[0, 1, 0], [0, 0, 1], [k - 1, -2*k, -1]]) >>> ss = StateSpace(A, Matrix([1, 0, 0]), Matrix([[0, 1, 0]]), Matrix([0])) >>> reduce_inequalities(ss.get_asymptotic_stability_conditions()) (1/3 < k) & (k < 1)
  • 连续系统求解StateSpace.dsolve(initial_conditions=None, input_vector=None, var=Symbol('t'))基于 ODE 系统求解器linodesolve得到输出y(t)的闭式符号解。

与传递函数的互转

  • 状态空间 → 传递函数ss.rewrite(TransferFunction)通过公式G(s) = C·(sI − A)⁻¹·B + D计算(lti.py),离散版本对应G(z)
  • 传递函数 → 状态空间tf.rewrite(StateSpace)返回可控标准形实现(_StateSpace_matrices_equivalent,要求系统 proper)。文档特别说明:该转换不唯一,同一个传递函数可有多个状态空间实现。

稳定性判据:Hurwitz/Schur 条件与劳斯-赫尔维茨表

模块提供三种互补的稳定性分析途径:

  1. is_stable()/get_asymptotic_stability_conditions()(前文已述):前者返回布尔判断,后者返回可参与符号不等式求解的条件列表。连续系统用Poly.den.hurwitz_conditions()(极点位于左半平面),离散系统用schur_conditions()(极点位于单位圆内)。fast=True时改用EXRAW域快速生成适合lambdify的大表达式。

  2. RouthHurwitz(routh_table.py):继承自MutableDenseMatrix,由特征多项式直接构造劳斯表,并自动处理两种特殊情形:

    • 首列零情形(First Column Zero Case):构造扩展劳斯表,相关信息记录在zero_col_infos属性(元组列表,含行号与连续零的个数信息);
    • 整行零情形(Full Row Zero Case):用辅助多项式(auxiliary polynomial)导数的系数替换该行,触发时zero_row_case置为True,辅助多项式本体可通过auxiliary_polynomials属性获取(其根关于原点对称,提示系统可能存在纯虚根或正负实部根)。

    稳定性判读规则:统计首列符号变化次数,每次变号对应一个正实部根:

>>> from sympy import symbols >>> from sympy.physics.control import RouthHurwitz >>> b1, b2, b3, b4 = symbols('b_1 b_2 b_3 b_4') >>> s = symbols("s") >>> p = b1*s**3 + b2*s**2 + b3*s + b4 >>> RouthHurwitz(p, s)[:, 0] # 首列即稳定性判读依据 Matrix([[b_1], [b_2], [(-b_1*b_4 + b_2*b_3)/b_2], [b_4]])
  1. 增益裕度与相位裕度phase_margin(system)gain_margin(system)(lti.py)基于 Bode 图定义计算连续系统的稳定裕度。两者仅接受 SISO LTI 系统;含时延项(表达式中出现exp)抛NotImplementedError;存在除变换变量外的多余自由符号抛ValueError

连续到离散的转换:广义双线性变换家族

模块实现了经典的控制系统离散化方法,核心是通用的gbt(generalised bilinear transformation),通过代入s(z) = (z − 1)/(T·(α·z + 1 − α))把连续传递函数H(s)离散化为H(z),其中T为采样周期、α为方法参数(lti.py)。三种特例(源码直接以不同 α 调用gbt):

函数α代入公式典型用途
bilinear(tf, T)1/2s = (2/T)·(z−1)/(z+1)双线性变换(Tustin),保稳定性
forward_diff(tf, T)0s = (z−1)/T前向差分(Euler)
backward_diff(tf, T)1s = (z−1)/(T·z)后向差分(Euler)

这些函数返回"降幂"系数列表[a, b], [c, d](对应H(z) = (az+b)/(cz+d))。更推荐直接使用DiscreteTransferFunction的类方法,一步得到离散传递函数对象:

>>> from sympy.abc import s, z, L, R, T >>> from sympy.physics.control import TransferFunction, DiscreteTransferFunction >>> tf = TransferFunction(1, s*L + R, s) # RL 一阶电路 >>> dttf = DiscreteTransferFunction.from_bilinear(tf, T, z) >>> dttf.num # T*z/(2*(L + R*T/2)) + T/(2*(L + R*T/2)) >>> dttf.sampling_time # T

类似地还有from_gbt(cont_tf, T, alpha, z)from_forward_difffrom_backward_diff

控制系统绘图:Bode、零极点、时域响应与 Nyquist/Nichols

sympy/physics/control/control_plots.py 提供了控制工程中常用的全套绘图函数。依赖说明(文档 control_plots.rst 明确):

  • 画图需要外部依赖Matplotlib(源码通过import_module('matplotlib', ...)惰性导入);
  • 只取数值数据*_numerical_data系列函数)需要NumPy

每个绘图函数都有对应的*_numerical_data版本,便于用户用其他后端自行绘图或进一步分析。所有函数仅支持SISO LTI 系统_check_system统一校验:非 SISO 抛NotImplementedError,含时延项不支持,存在多余自由符号抛ValueError,见 control_plots.py)。

零极点图(Pole-Zero Plot)

pole_zero_plot(system, pole_color='blue', pole_markersize=10, zero_color='orange', zero_markersize=7, grid=True, show_axes=False, show=True):在复平面用x标记极点、圆形标记零点。对应的pole_zero_numerical_data返回(zeros, poles)数值列表。

时域响应

  • step_response_plot(system, ...):单位阶跃响应,底层通过部分分式分解 +_fast_inverse_laplace求得符号解析解再求值(control_plots.py);
  • impulse_response_plot(system, ...):单位冲激响应;
  • ramp_response_plot(system, slope=1, ...):斜坡响应,slope默认 1 且不能为负。

三者共用参数:prec=8(坐标精度)、lower_limit=0upper_limit=10(时间范围)、color='b'grid=Trueshow_axes=Falseshow=Trueshow=False时返回 matplotlib 对象)。*_numerical_data默认自适应采样,传adaptive=Falsen可获得均匀采样。

频域绘图

  • bode_plot(system, initial_exp=-5, final_exp=5, ...):一次性绘制幅频与相频两张子图。频率轴为 10 的指数范围(默认10⁻⁵ ~ 10⁵),freq_unit可选'rad/sec'(默认)或'Hz'phase_unit可选'rad'(默认)或'deg'phase_unwrap=True时自动做相位解缠绕。也可分别调用bode_magnitude_plotbode_phase_plot
  • nyquist_plot(system, initial_omega=0.01, final_omega=100, ...):奈奎斯特图(同时绘制频率响应曲线及其镜像,基于plot_parametric);
  • nichols_plot(system, initial_omega=0.01, final_omega=100, ...):尼科尔斯图(相位-增益图,横轴为相位 [deg],纵轴为幅值 [dB])。

结语与源码导航

sympy.physics.control为控制工程师和教学场景提供了一条从"写出传递函数/状态空间"到"得到精确符号结论"的完整链路,其符号解不依赖数值近似,可直接用于参数设计(如求解稳定域不等式)与后续数学推导。若需深入了解实现细节,建议按如下路径阅读仓库源码:

  • 模型与互联核心:sympy/physics/control/lti.py(传递函数、PID、Series/Parallel/Feedback、MIMO 类、状态空间、离散化、稳定裕度);
  • 绘图函数:sympy/physics/control/control_plots.py;
  • 劳斯表:sympy/physics/control/routh_table.py;
  • 公共导出入口:sympy/physics/control/init.py;
  • 官方文档目录:doc/src/modules/physics/control/index.rst(子页面含 control.rst、lti.rst、control_plots.rst、routh_table.rst);
  • 测试用例:sympy/physics/control/tests/test_lti.py、test_control_plots.py、test_routh_table.py,可作为 API 用法的权威示例。

【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询