SymPy 多体动力学 System 与 SymbolicSystem:从模型装配到运动方程的符号化构建
【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy
导读
本文围绕 SymPy 物理力学模块(sympy.physics.mechanics)中两套系统级建模 API——System与SymbolicSystem——展开。System是一个面向现代多体动力学工作流的容器类:它统一存放刚体/质点、铰链(Joint)、约束、载荷与执行器,并能在几行代码内自动装配广义坐标、广义速度与运动学微分方程,最终调用KanesMethod或LagrangesMethod后端生成运动方程;SymbolicSystem则是一个面向控制与仿真集成的符号系统封装,支持三种运动方程表达形式(显式合并、隐式合并、隐式分离),可直接产出状态空间所需的M x' = F结构。读完本文,你将掌握两套 API 的完整构造方法、属性语义、运动方程形成流程与校验规则,并能够结合实际源码与测试用例进行二次开发与集成。文章内容以仓库文档页 doc/src/modules/physics/mechanics/api/system.rst 所自动生成的类文档为主体,辅以 sympy/physics/mechanics/system.py 的实现细节。
一、System:现代多体系统的统一容器
1.1 设计定位
System继承自MethodBase(定义于 sympy/physics/mechanics/method.py),其类文档指出:一个System实例存储与模型关联的各类对象,包括**物体(bodies)、铰链(joints)、约束(constraints)**及其他相关信息;当组件之间的所有关系定义完成后,System便可借助KanesMethod之类的后端来形成运动方程。它的设计目标是与第三方库兼容,从而获得更大的灵活性和更好的工具集成能力。
从基类MethodBase的抽象属性(frame、q、u、bodies、loads、holonomic_constraints、nonholonomic_constraints、velocity_constraints、acceleration_constraints、mass_matrix、forcing、mass_matrix_full、forcing_full)可以看出,任何运动方程方法都围绕惯性参考系、广义坐标、广义速度、物体、载荷与约束这些共性概念展开。System正是在这一抽象之上,把"装配模型"与"选择方程形成方法"解耦:建模时只描述系统结构,推导时再指定后端。
1.2 构造与牛顿参考体
System的构造函数签名与参数语义如下(源码见 system.py):
frame : ReferenceFrame, optional——系统的惯性参考系;不提供时自动创建一个名为inertial_frame的新参考系。fixed_point : Point, optional——惯性参考系中的一个固定点;不提供时自动创建名为inertial_point的点,并将其在惯性系中的速度置零(set_vel(self._frame, 0))。- 若传入的
frame不是ReferenceFrame或fixed_point不是Point,会抛出TypeError。 - 初始状态下,所有广义坐标、广义速度、辅助速度、kdes 与约束矩阵均为空矩阵(
ImmutableMatrix(1, 0, [])),各对象容器为空列表,eom_method为None。
更常用的构造方式是类方法from_newtonian(newtonian)(见 system.py):以某个牛顿参考物体为基准构造系统,取该物体的frame作为惯性系、其masscenter作为固定点,并自动把该物体加入系统。注意:如果传入的是Particle,会抛出TypeError,因为质点没有自带参考系,不能充当牛顿参考体。
from sympy.physics.mechanics import ( mechanics_printing, dynamicsymbols, RigidBody, Particle, ReferenceFrame, PrismaticJoint, PinJoint, System) mechanics_printing(pretty_print=False) g, l = symbols('g l') F = dynamicsymbols('F') rail = RigidBody('rail') cart = RigidBody('cart') bob = Particle('bob') bob_frame = ReferenceFrame('bob_frame') system = System.from_newtonian(rail) # 同时自动添加 rail print(system.bodies[0]) # railSystem暴露的属性frame、fixed_point、x、y、z分别对应惯性参考系、固定点以及惯性系中固定的三个单位矢量,方便在施加力与约束时直接引用。
1.3 通过铰链自动装配运动学
System的核心便利之处在于add_joints(*joints)(见 system.py):一次性加入一个或多个Joint子类实例,同时自动收集每个铰链关联的广义坐标、广义速度、kdes 与父/子物体,并去重后加入系统。源码中通过四个OrderedSet分别收集joint.coordinates、joint.speeds、joint.kdes和(joint.parent, joint.child),再与系统已有内容求差集后调用add_coordinates、add_speeds、add_kdes、add_bodies。
值得注意的是,kdes 总是会被添加,因此用户不应在添加铰链之前手动添加这些运动学微分方程,否则可能造成重复。
system.add_joints( PrismaticJoint('slider', rail, cart, joint_axis=rail.x), PinJoint('pin', cart, bob, joint_axis=cart.z, child_interframe=bob_frame, child_point=l * bob_frame.y) ) system.joints # (PrismaticJoint: slider parent: rail child: cart, # PinJoint: pin parent: cart child: bob) system.q # Matrix([[q_slider], [q_pin]]) system.u # Matrix([[u_slider], [u_pin]]) system.kdes # Matrix([[u_slider - q_slider'], [u_pin - q_pin']]]) [body.name for body in system.bodies] # ['rail', 'cart', 'bob']添加铰链后,q、u等属性会自动反映新加入的坐标与速度。add_joints的实现说明:建模顺序无关紧要,System负责保证装配一致性。
1.4 载荷、重力与执行器
系统装配完成后即可施加力载荷:
add_loads(*loads)(system.py):每个载荷会经过_parse_load解析校验后存入_loads,支持的载荷形式为(作用点, 力矢量)或(参考系, 力矩矢量)。apply_uniform_gravity(acceleration)(system.py):内部调用gravity(acceleration, *self.bodies)生成作用于所有物体的均布重力载荷,再通过add_loads加入。add_actuators(*actuators)(system.py):加入ActuatorBase子类实例(如力执行器、扭矩执行器)。在form_eoms中,执行器的to_loads()结果会与loads合并后传给后端。
system.apply_uniform_gravity(-g * system.y) system.add_loads((cart.masscenter, F * rail.x)) system.loads # ((rail_masscenter, - g*rail_mass*rail_frame.y), # (cart_masscenter, - cart_mass*g*rail_frame.y), # (bob_masscenter, - bob_mass*g*rail_frame.y), # (cart_masscenter, F*rail_frame.x))add_loads每次都会对载荷做解析校验,若传入形式不符合约定会直接报错,从源头避免"坏载荷"进入方程推导。
1.5 约束、kdes 与坐标分级
System将广义坐标与广义速度划分为独立/依赖两组,并通过下述属性与方法维护:
add_coordinates(*coordinates, independent=True)与add_speeds(*speeds, independent=True):追加广义坐标/速度,independent可传单个 bool 或与数量等长的 bool 列表。add_auxiliary_speeds(*speeds):追加辅助广义速度(用于凯恩方法中的非贡献力)。add_kdes(*kdes):追加运动学微分方程(表达式按"等于零矩阵"语义存储)。add_holonomic_constraints(*constraints)/add_nonholonomic_constraints(*constraints):追加完整/非完整约束,同样按"表达式 = 0"存储。
这些添加方法都带有_reset_eom_method装饰器(定义于 system.py):只要系统结构被修改,已形成的_eom_method就会被重置为None,强制用户在结构变更后重新形成运动方程,避免使用过期方程。
派生属性与存储语义(见 system.py):
q = q_ind 叠加 q_dep(col_join),u = u_ind 叠加 u_dep。velocity_constraints:默认不单独存储,而是实时计算为holonomic_constraints.diff(t)与nonholonomic_constraints的纵向拼接;一旦用户显式设置则使用用户值。这保证了"速度约束自动由完整约束求导 + 非完整约束拼接"这一约定。acceleration_constraints:实时计算为velocity_constraints.diff(t)。eom_method:当前已形成的后端对象,初始为None。
在添加铰链示例中,system.add_joints(...)之后q、u、kdes均已就绪;当需要引入依赖关系时,可手工调整分组:
system.add_holonomic_constraints( bob.masscenter.pos_from(rail.masscenter).dot(system.x) ) system.q_ind = system.get_joint('pin').coordinates system.q_dep = system.get_joint('slider').coordinates system.u_ind = system.get_joint('pin').speeds system.u_dep = system.get_joint('slider').speedsget_joint(name)与get_body(name)(system.py)按名称从系统中取回对象,名称不存在时返回None。
1.6 形成运动方程:form_eoms与后端选择
form_eoms(eom_method=KanesMethod, **kwargs)(system.py)是System的推导入口:
- 默认使用
KanesMethod;也可传入LagrangesMethod。 - 内部先汇总载荷:
loads + sum(act.to_loads() for act in actuators)。 - 对
KanesMethod路径,自动填充frame、q_ind、u_ind、kd_eqs、q_dependent、u_dependent、configuration_constraints、velocity_constraints、u_auxiliary、forcelist、bodies、explicit_kinematics=False等关键字参数。这些是不允许被用户覆盖的保留参数,传入同名 kwargs 会抛出ValueError。 - 对
LagrangesMethod路径,自动填充frame、qs、forcelist、bodies、hol_coneqs、nonhol_coneqs,并且若未显式提供Lagrangian,会调用Lagrangian(frame, *bodies)自动构造拉格朗日量。 - 其他类则抛出
NotImplementedError。
形成后的运动方程通过以下属性读取(均转发至后端对象):
mass_matrix/forcing:动力学方程中的M_d与f_d,满足M_d u' = f_d。mass_matrix_full/forcing_full:附加 kdes 后的全量形式,满足M_m x' = f_m,其中x为堆叠q与u的状态向量。rhs(inv_method=None, **kwargs)(system.py):返回显式右端rhs = Inv(M) F,inv_method可指定 SymPy 矩阵求逆方法。
system.validate_system() system.form_eoms() # Matrix([[bob_mass*l*u_pin**2*sin(q_pin) - bob_mass*l*cos(q_pin)*u_pin' # - (bob_mass + cart_mass)*u_slider' + F], # [-bob_mass*g*l*sin(q_pin) - bob_mass*l**2*u_pin' # - bob_mass*l*cos(q_pin)*u_slider']]) simplify(system.mass_matrix) # Matrix([[ bob_mass + cart_mass, bob_mass*l*cos(q_pin)], # [bob_mass*l*cos(q_pin), bob_mass*l**2]]) system.forcing # Matrix([[bob_mass*l*u_pin**2*sin(q_pin) + F], # [ -bob_mass*g*l*sin(q_pin)]])在form_eoms的 docstring 中还给出了一个单自由度弹簧-质量-阻尼器的例子:显式传入LagrangesMethod后端,并在Particle上设置potential_energy后直接调用system.form_eoms(LagrangesMethod)与system.rhs()。测试用例 test_system_class.py 与 test_system.py 覆盖了默认/指定后端、带约束系统、坐标分组等多种路径。
1.7 系统校验:validate_system
validate_system(eom_method=KanesMethod, check_duplicates=False)(system.py)在形成方程前做一组"常见错误"检查,发现问题时抛出ValueError并汇总所有消息:
- 依赖广义坐标数应等于完整约束数(
n_q_dep == n_hc)。 - 所有铰链用到的广义坐标/速度/kdes 必须已被系统收录。
- 采用
KanesMethod时:- 依赖广义速度数应等于速度约束数(
n_u_dep == n_vc); - 广义坐标数应 ≤ 广义速度数;
- 广义速度数应等于 kdes 数。
- 依赖广义速度数应等于速度约束数(
- 采用
LagrangesMethod时:- 不允许出现不是广义坐标导数的广义速度;
- 不支持辅助速度(
u_aux必须为空)。
该方法的 docstring 特别说明:此方法不保证向后兼容,未来可能变严或变松,但一个定义良好的系统应当始终通过全部检查。测试文件 test_system_class.py 中的test_empty_system、test_filled_system均调用validate_system()验证空系统与填充系统的合法性。
1.8 属性变更与方程失效机制
System中几乎所有属性 setter(bodies、joints、loads、actuators、q_ind、q_dep、u_ind、u_dep、u_aux、kdes、各类约束)都叠加了_reset_eom_method装饰器。这构成一个简单而重要的失效机制:任何结构变更都会使已形成的运动方程作废,保证mass_matrix、forcing等结果永远与当前系统结构一致。此外,对象类型检查由_check_objects(system.py)负责——非预期类型抛TypeError,重复添加抛ValueError。
二、SymbolicSystem:面向控制与仿真的符号系统封装
2.1 三种运动方程表达形式
SymbolicSystem(system.py)以符号形式集中保存一个系统的运动方程与物体/载荷信息,其类文档定义了三种方程描述方式:
| 编号 | 形式 | 方程 |
|---|---|---|
| [1] | 显式合并(combined explicit) | x' = F_1(x, t, r, p) |
| [2] | 隐式合并(combined implicit) | M_2(x, p) x' = F_2(x, t, r, p) |
| [3] | 隐式分离(separated implicit) | M_3(q, p) u' = F_3(q, u, t, r, p),q' = G(q, u, t, r, p) |
其中x为状态(例如[q, u]),t为时间,r为指定(外生)输入,p为常数,q为广义坐标,u为广义速度;F_1/F_2/F_3为相应右端,M_2/M_3为相应质量矩阵,G为运动学方程显式右端。
2.2 构造参数与自动形式判别
构造签名:
SymbolicSystem(coord_states, right_hand_side, speeds=None, mass_matrix=None, coordinate_derivatives=None, alg_con=None, output_eqns={}, coord_idxs=None, speed_idxs=None, bodies=None, loads=None)各参数语义(见 system.py):
coord_states:有序的关于时间的函数集合。若同时给出speeds,则本参数被当作广义坐标;否则被当作状态。right_hand_side:方程右端,具体形式由是否给出mass_matrix/coordinate_derivatives决定。speeds:广义速度集合;给出后coord_states视为坐标。mass_matrix:隐式形式([2]/[3])的质量矩阵。给出了coordinate_derivatives则判为形式 [3],否则判为形式 [2]。coordinate_derivatives:运动学方程显式右端,给出即判为形式 [3]。alg_con:方程中代数约束所在行的索引。若以形式 [3] 输入,索引指向mass_matrix/right_hand_side组合,构造时会自动加上len(coordinate_derivatives)偏移,使其匹配合并后的矩阵。output_eqns:需要跟踪的输出方程字典,键为名称、值为符号表达式。coord_idxs/speed_idxs:当coord_states实为状态时,指明其中哪些索引对应广义坐标/广义速度。bodies/loads:可选的物体与载荷集合,载荷形式为(作用点, 力矢量)与(参考系, 力矩矢量)。
源码中的形式判别逻辑(system.py):先看coordinate_derivatives是否为None,再检查mass_matrix是否给出,从而唯一确定 [3]、[2] 或 [1] 三种形式,并只填充相应的内部属性,其余置None。
2.3 属性矩阵家族
SymbolicSystem的属性围绕三种形式组织(见 system.py):
coordinates/speeds/states:广义坐标、广义速度与状态矩阵;未指定时访问coordinates/speeds会抛AttributeError。dyn_implicit_mat/dyn_implicit_rhs:形式 [3] 中动力学方程的M_3与F_3。comb_implicit_mat/comb_implicit_rhs:形式 [2] 中合并方程的M_2与F_2。若以形式 [3] 输入,这两个属性会按需惰性组装:comb_implicit_mat由单位阵eye(num_kin_eqns)与dyn_implicit_mat组成分块对角阵,comb_implicit_rhs由kin_explicit_rhs与dyn_implicit_rhs纵向拼接(system.py)。kin_explicit_rhs:形式 [3] 中运动学方程右端G。comb_explicit_rhs:形式 [1] 的显式右端,需先调用compute_explicit_form()生成——该方法通过M.LUsolve(F)求解(system.py),源码注释提醒该计算"可能耗时"。alg_con:代数约束行索引列表;存在代数约束意味着必须使用 DAE 求解器而非 ODE 求解器(见属性文档)。output_eqns/bodies/loads:输出方程、物体与载荷。
此外还有两个实用方法:
dynamic_symbols()(system.py):返回所有依赖于时间的符号元组,用于确定状态变量。constant_symbols()(system.py):返回所有不依赖时间的常数符号元组(剔除时间符号t),用于参数辨识与数值代入。
2.4 完整示例:单摆的三种形式
类文档给出的单摆示例以手动方式将方程送入SymbolicSystem。系统由相对竖直方向的摆角theta与广义速度omega = theta_dot描述:
from sympy import Matrix, sin, symbols from sympy.physics.mechanics import dynamicsymbols, SymbolicSystem l, m, g = symbols('l m g') theta, omega = dynamicsymbols('theta omega') kin_explicit_rhs = Matrix([omega]) dyn_implicit_mat = Matrix([l**2 * m]) dyn_implicit_rhs = Matrix([-g * l * m * sin(theta)]) # 形式 [3]:分离的隐式形式 symsystem = SymbolicSystem([theta], dyn_implicit_rhs, [omega], dyn_implicit_mat)此时可通过symsystem.dyn_implicit_mat、symsystem.dyn_implicit_rhs、symsystem.kin_explicit_rhs直接读取,或通过comb_implicit_mat、comb_implicit_rhs访问按需组装的合并形式;如需显式右端,调用symsystem.compute_explicit_form()后访问comb_explicit_rhs。
测试文件 test_system.py 以"x-y 坐标单摆"系统对三种形式逐一验证:test_form_1(显式合并)、test_form_2(隐式合并)、test_form_3(隐式分离),并覆盖了alg_con偏移、output_eqns、coord_idxs/speed_idxs、bodies/loads以及dynamic_symbols/constant_symbols的返回内容,可作为该 API 用法的权威参考(test_system.py)。
三、两套 API 的选型与衔接建议
结合 system.py 的实现可以归纳两者的分工:
System面向"从物理模型到运动方程":通过from_newtonian+add_joints快速装配,自动维护坐标/速度/kdes/约束/物体的一致性,form_eoms内部完成载荷合并、Lagrangian自动构造等繁琐工作,并提供validate_system在推导前拦截常见错误。适合用铰链/刚体/质点建立机械系统并推导M u' = f的动力学建模场景。SymbolicSystem面向"从方程到仿真/控制":输入已是整理好的运动方程(无论来自System.form_eoms的mass_matrix/forcing,还是手动推导),输出统一为三种标准形式中的一种,配合dynamic_symbols/constant_symbols便于数值积分、线性化与控制器设计。其alg_con属性直接提示应选用 DAE 求解器。
两套 API 在架构上是互补的:System生成方程后,其mass_matrix/forcing或mass_matrix_full/forcing_full恰好可以作为SymbolicSystem形式 [2]/[3] 的输入;MethodBase中定义的constraints_jacobian(由linear_eq_to_matrix(velocity_constraints, u)计算,见 method.py)等属性则为两类封装提供了统一的底层接口。
四、相关源码与测试导航
- 类定义与完整 docstring:sympy/physics/mechanics/system.py
- 抽象基类与
rhs/constraints_jacobian默认实现:sympy/physics/mechanics/method.py - 后端实现:sympy/physics/mechanics/kane.py、sympy/physics/mechanics/lagrange.py
- 铰链与物体:sympy/physics/mechanics/joint.py、sympy/physics/mechanics/rigidbody.py、sympy/physics/mechanics/particle.py
- 载荷解析与重力辅助:sympy/physics/mechanics/loads.py
- 自动化 API 文档源:doc/src/modules/physics/mechanics/api/system.rst
- 测试用例:sympy/physics/mechanics/tests/test_system_class.py(
System全面测试)、sympy/physics/mechanics/tests/test_system.py(SymbolicSystem三种形式测试)
若需在本机运行文中示例,请在安装 SymPy 的开发环境中执行python -c "import sympy; print(sympy.__version__)"确认版本后,将示例代码粘贴到 Python 交互环境运行;System类位于sympy.physics.mechanics命名空间下,可直接从sympy.physics.mechanics import System, SymbolicSystem导入。
【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考