
SymPy Autolev 解析器详解用 parse_autolev 将 Autolev 多体动力学代码转换为 SymPy Mechanics 代码【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympySymPy 的sympy/parsing/autolev模块提供了一个基于 ANTLR 的解析器能把 Autolev 4.1现已被 MotionGenesis 取代领域的符号多体动力学脚本自动转译为等价的 SymPyphysics.mechanics代码并可附带生成 PyDy 数值仿真代码。读完本文你将掌握parse_autolev()的完整用法与参数含义、一个双摆案例从.al到可运行 Python 的完整转换链路以及十余条经过源码印证的坑位与限制清单——这些是 Autolev 老用户迁移到 SymPy 时最实用的实战参考。一、解析器是什么定位、框架与底层结构Autolev 是用于符号多体动力学的领域专用语言。SymPy 的 mechanics 模块如今已经足够强大可以独立支撑完整的符号动力学建模因此这个解析器的首要目标是帮助 Autolev 的老用户熟悉 SymPy 中的多体动力学建模方式——把一段.al文件解析后你得到的不仅是结果还有一份可读的 SymPy 建模代码。从源码结构看解析器基于 ANTLR 框架构建位于 sympy/parsing/autolev核心文件分工如下文件职责init.py对外暴露入口函数parse_autolev()带完整 doctest双摆示例即来源于此_parse_autolev_antlr.py解析管线构造 Lexer → TokenStream → Parser → 解析树 → Listener 遍历Autolev.g4ANTLR 语法文件定义了 Autolev 的词法与句法规则_listener_autolev_antlr.py核心逻辑约 2000 行MyListener类负责把每类语句翻译成 SymPy 代码并累积输出_build_autolev_antlr.py维护脚本用antlr4命令从语法文件重新生成_antlr/目录下的代码test-examples/回归测试语料12 组ruletestN.al/.py规则测试 4 个完整物理算例双摆、混沌摆、质量弹簧阻尼器、非最小坐标摆解析管线与运行环境要求_parse_autolev_antlr.py 展示了完整的 ANTLR 调用链这对理解其依赖约束很关键def parse_autolev(autolev_code, include_numeric): antlr4 import_module(antlr4) if not antlr4 or not version(antlr4-python3-runtime).startswith(4.11): raise ImportError(Autolev parsing requires the antlr4 Python package, provided by pip (antlr4-python3-runtime) conda (antlr-python-runtime), version 4.11) ... lexer AutolevLexer(input_stream) token_stream antlr4.CommonTokenStream(lexer) parser AutolevParser(token_stream) tree parser.prog() my_listener MyListener(include_numeric) walker antlr4.ParseTreeWalker() walker.walk(my_listener, tree) return .join(my_listener.output_code)由此可确认三个使用前提必须安装antlr4-python3-runtimepip或antlr-python-runtimeconda且版本必须是 4.11 开头——源码中做了严格的前缀匹配校验版本不符会直接抛ImportError。输入可以是字符串也可以是任何带readlines()方法的对象文件句柄、StringIO 均可。输出是str类型的代码文本不是 SymPy 对象——你拿到的是可执行/可审阅的 Python 源码。_antlr/目录下的 lexer/parser/listener 骨架代码由 _build_autolev_antlr.py 调用antlr4命令行工具从 Autolev.g4 生成文件头明确标注 GENERATED BYsetup.py antlr, DO NOT EDIT BY HAND。语法文件本身只有百余行但足以覆盖变量声明、赋值、函数调用、质量/惯性声明、INPUT/OUTPUT/CODE数值命令与 settings 等全部顶层语句形态并且通过手工枚举的大小写字符序列如Input: (I|i)(N|n)...实现了 Autolev 命令的大小写不敏感。二、使用方式从 .al 文件到 SymPy 代码2.1 入口函数与参数 from sympy.parsing.autolev import parse_autolev sympy_code parse_autolev(open(double_pendulum.al), include_numericTrue)parse_autolev()接受两个参数见init.py 的 docstringautolev_codestr或任何带readlines()方法的对象文件句柄、StringIOinclude_numeric布尔值默认False。设为True时解析器会为 Autolev 代码中的数值化行INPUT、CODE等附加 NumPy / PyDy 数值代码。从源码看include_numeric被传入MyListener构造函数保存为self.include_numeric见 _listener_autolev_antlr.py它决定了是否在末尾输出from pydy.system import System之类的仿真代码段。另外注意MyListener.__init__会先写入固定的头部导入_me、_sm、m、_np等别名所以输出的代码一定以 import 语句开头。2.2 完整算例双摆下面这段 Autolev 代码%注释是 Autolev 自身的回显输出用于对照理解% double_pendulum.al %------------------- MOTIONVARIABLES Q{2}, U{2} CONSTANTS L,M,G NEWTONIAN N FRAMES A,B SIMPROT(N, A, 3, Q1) % - N_A [COS(Q1), -SIN(Q1), 0; SIN(Q1), COS(Q1), 0; 0, 0, 1] SIMPROT(N, B, 3, Q2) % - N_B [COS(Q2), -SIN(Q2), 0; SIN(Q2), COS(Q2), 0; 0, 0, 1] W_A_NU1*N3 % - W_A_N U1*N3 W_B_NU2*N3 % - W_B_N U2*N3 POINT O PARTICLES P,R P_O_P L*A1 % - P_O_P L*A1 P_P_R L*B1 % - P_P_R L*B1 V_O_N 0 % - V_O_N 0 V2PTS(N, A, O, P) % - V_P_N L*U1*A2 V2PTS(N, B, P, R) % - V_R_N L*U1*A2 L*U2*B2 MASS PM, RM Q1 U1 Q2 U2 GRAVITY(G*N1) % - FORCE_P G*M*N1 % - FORCE_R G*M*N1 ZERO FR() FRSTAR() % - ZERO[1] -L*M*(2*G*SIN(Q1)L*(U2^2*SIN(Q1-Q2)2*U1COS(Q1-Q2)*U2)) % - ZERO[2] -L*M*(G*SIN(Q2)-L*(U1^2*SIN(Q1-Q2)-U2-COS(Q1-Q2)*U1)) KANE() INPUT M1,G9.81,L1 INPUT Q1.1,Q2.2,U10,U20 INPUT TFINAL10, INTEGSTP.01 CODE DYNAMICS() some_filename.c解析后可得到如下 SymPy 代码文档示例中为可读性去掉了下划线别名前缀实际输出为_me/_sm等别名见init.py 中的 doctest 输出import sympy.physics.mechanics as me import sympy as sm import math as m import numpy as np q1, q2, u1, u2 me.dynamicsymbols(q1 q2 u1 u2) q1d, q2d, u1d, u2d me.dynamicsymbols(q1 q2 u1 u2, 1) l, m, g sm.symbols(l m g, realTrue) frame_n me.ReferenceFrame(n) frame_a me.ReferenceFrame(a) frame_b me.ReferenceFrame(b) frame_a.orient(frame_n, Axis, [q1, frame_n.z]) # print(frame_n.dcm(frame_a)) frame_b.orient(frame_n, Axis, [q2, frame_n.z]) # print(frame_n.dcm(frame_b)) frame_a.set_ang_vel(frame_n, u1*frame_n.z) # print(frame_a.ang_vel_in(frame_n)) frame_b.set_ang_vel(frame_n, u2*frame_n.z) # print(frame_b.ang_vel_in(frame_n)) point_o me.Point(o) particle_p me.Particle(p, me.Point(p_pt), sm.Symbol(m)) particle_r me.Particle(r, me.Point(r_pt), sm.Symbol(m)) particle_p.point.set_pos(point_o, l*frame_a.x) # print(particle_p.point.pos_from(point_o)) particle_r.point.set_pos(particle_p.point, l*frame_b.x) # print(particle_p.point.pos_from(particle_r.point)) point_o.set_vel(frame_n, 0) # print(point_o.vel(frame_n)) particle_p.point.v2pt_theory(point_o, frame_n, frame_a) # print(particle_p.point.vel(frame_n)) particle_r.point.v2pt_theory(particle_p.point, frame_n, frame_b) # print(particle_r.point.vel(frame_n)) particle_p.mass m particle_r.mass m force_p particle_p.mass*(g*frame_n.x) # print(force_p) force_r particle_r.mass*(g*frame_n.x) # print(force_r) kd_eqs [q1d - u1, q2d - u2] forceList [(particle_p.point, particle_p.mass*(g*frame_n.x)), (particle_r.point, particle_r.mass*(g*frame_n.x))] kane me.KanesMethod(frame_n, q_ind[q1, q2], u_ind[u1, u2], kd_eqskd_eqs) fr, frstar kane.kanes_equations([particle_p, particle_r], forceList) zero fr frstar # print(zero) #---------PyDy code for integration---------- from pydy.system import System sys System(kane, constants{l: 1, m: 1, g: 9.81}, specifieds{}, initial_conditions{q1: .1, q2: .2, u1: 0, u2: 0}, times np.linspace(0.0, 10, 10/.01)) y sys.integrate()几点需要特别注意注释代码不属于输出。示例中带# print(...)的行是解析器插入的提示性注释演示如何用.ang_vel_in()、.dcm()等 SymPy 方法复现 Autolev 中直接回显变量的效果。在 SymPy 中很多量如 DCM、角速度需要通过方法显式计算而不是直接打印变量。若要对输出做 Autolev 风格的化简往往还需要手工调用trigsimp()、expand()、evalf()等基础 SymPy 函数。该算例在仓库中另有真实语料可对照test-examples/pydy-example-repo/double_pendulum.al 与 test-examples/pydy-example-repo/double_pendulum.py。若你是 SymPy mechanics 新手建议配合仓库中的迁移对照指南 doc/src/explanation/modules/physics/mechanics/sympy_mechanics_for_autolev_users.rst 使用。2.3 验证机制解析器自带的回归测试sympy/parsing/tests/test_autolev.py 把 test-examples/ 下每个ruletestN.al的解析结果与同名.py基准文件做等价性比较test-examples/README.txt 也说明了这一机制。这为输出代码是否正确提供了仓库内可直接运行的验证手段。三、Gotchas转换过程中的十大坑位这是原文档信息密度最高的部分。以下逐条继承并配合源码说明。3.1 避免与 Python 保留字冲突的变量名%Autolev Code %------------ LAMBDA EIG(M)#SymPy Code #---------- lambda sm.Matrix([i.evalf() for i in (m).eigenvals().keys()])lambda是 Python 保留字这类命名会直接产生无效 Python 代码Autolev 中合法的标识符在 Python 侧必须回避。3.2 向量名与标量名不要相同Autolev 区分向量与标量但 Python 中同名会被覆盖。解析器目前允许刚体body名与标量/向量名重合但不允许标量与向量同名原文档注明这一点未来可能改变。例如%Autolev Code %------------ VARIABLES X,Y FRAMES A A X*A1 Y*A2 A XY#SymPy Code #---------- x, y me.dynamicsymbols(x y) frame_a me.ReferenceFrame(a) a x*frame_a.x y*frame_a.y a x y # 注意 frame_a 被加了前缀因此刚体/参考系名不会冲突 # 但标量 a 被向量 a 覆盖——这是当前解析器的已知行为。3.3 矩阵返回函数注意元素顺序对返回矩阵的函数尤其是特征值/特征向量要检查元素顺序SymPy 与 Autolev 的顺序可能不同%Autolev Code %------------ EIG(M, E1, E2) % - [5; 14; 13] E2ROW ROWS(E2, 1) EIGVEC VECTOR(A, E2ROW)#SymPy Code #---------- e1 sm.Matrix([i.evalf() for i in m.eigenvals().keys()]) # sm.Matrix([5;13;14]) —— 顺序不同 e2 sm.Matrix([i[2][0].evalf() for i in m.eigenvects()]).reshape(m.shape[0], m.shape[1]) e2row e2.row(0) # 结果依赖 eigenvects 中向量的顺序 eigenvec e2row[0]*a.x e2row[1]*a.y e2row[2]*a.y3.4 EVALUATE 的角度代入要显式转弧度SymPy 默认角度单位是弧度使用EVALUATE做角度代入时应写90*UNITS(deg,rad)这类形式或直接在 SymPy 代码里用np.deg2rad()。不过对由CODE命令生成的输出代码无需操心——解析器在INPUT声明中遇到deg单位时会自行处理。而DEGREES设置只在SIMPROT这类期望角度的场景下部分生效。%Autolev Code %------------ A Q1*A1 Q2*A2 B EVALUATE(A, Q1:30*UNITS(DEG,RAD))#SymPy Code #---------- a q1*a.frame_a.x q2*frame_a.y b a.subs({q1: 30*0.0174533}) # 等价写法: b a.subs({q1: np.deg2rad(30)})3.5 绝大多数 Autolev settings 不生效大部分 Autolev 设置语句没有被解析、对解析器无影响只有COMPLEX和DEGREES部分可用。其余功能建议在 SymPy / Python 中寻找替代方案。3.6 REPRESENT 命令不受支持请改用MATRIX、VECTOR或DYADIC命令。Autolev 4.1 本身也更推荐后三者解析器则完全不解析REPRESENT。3.7 变量声明不支持嵌套花括号组合像WO{3}RD{2,4}这类变量名 多组花括号的便捷声明不被支持解析器只能处理一个变量名 一对花括号 任意个号的形态。需要等价效果时请逐条手工声明。3.8 带矩阵参数的函数形式大多不可靠解析器能处理大多数命令的常规版本但带 Matrix 实参的函数形式在多数情况下不能正确解析例如M COEF([E1;E2], [U1, U2, U3]) # 期望求 E1、E2 中 U1/U2/U3 的系数建议改用常规形式手工构造矩阵% COEF([E1;E2],[U1,U2,U3]) 的替代写法 M [COEF(E1,U1),COEF(E1,U2),COEF(E1,U3) ;COEF(E2,U1),COEF(E2,U2),COEF(E2,U3)]3.9 广义坐标/速度必须用 MOTIONVARIABLES 声明解析器依赖声明方式来区分广义坐标、广义速度与普通变量以便给 Kane 方法对象传入正确参数。因此广义坐标与速度必须写在MOTIONVARIABLES声明中其余变量写普通VARIABLES声明强烈建议为坐标声明对应的速度变量并显式给出运动学微分方程kinematic differential equations。解析器虽能引入哑变量兜底处理一些不匹配的情况但 SymPy 本身是要求这些输入的旧式声明如VARIABLES U{3}同样不受支持。%Autolev Code %------------ MOTIONVARIABLES Q{2}, U{2} % ----- OTHER LINES ---- Q1 U1 Q2 U2 ----- OTHER LINES ---- ZERO FR() FRSTAR()#SymPy Code #---------- q1, q2, u1, u2 me.dynamicsymbols(q1 q2 u1 u2) q1d, q2d, u1d, u2d me.dynamicsymbols(q1 q2 u1 u2, 1) # ------- other lines ------- kd_eqs [q1d - u1, q2d - u2] kane me.KanesMethod(frame_n, q_ind[q1, q2], u_ind[u1, u2], kd_eqskd_eqs) fr, frstar kane.kanes_equations([particle_p, particle_r], forceList) zero fr frstar从 _listener_autolev_antlr.py 的实例字段也能印证这一设计q_ind、q_dep、u_ind、u_dep、kd_eqs、kane_type等字段专门用于收集 Kane 方法的参数。3.10 Kane 方程中的时间符号dynamicsymbols._t需替换为dynamicsymbols(t)PyDy 要求时间相关的 specified 被显式列出而 Autolev 会自行处理方程中游离的时间变量。因此当 Kane 方程中出现时间时需要把me.dynamicsymbols._t全部替换为me.dynamicsymbols(t)例如 mass_spring_damper.py 第 10 行的做法。其根源是 PyDy 的System类不接受dynamicsymbols._t形式的 specified。原文档也坦承这一改动并不理想未来应寻找更好的方案。3.11 符号与 RHS 表达式的语义差异解析器只对VARIABLES/MOTIONVARIABLES等声明过的变量创建 SymPy 的symbols/dynamicsymbols。对直接赋值的中间表达式如E X Y解析器不会为其新建符号只把表达式赋给 Python 变量。而被声明变量重新赋值时解析器会把表达式存入 rhs 字典而不是覆盖变量本身——这是由 Python 与 Autolev 在变量语义上的根本差异决定的。Autolev 有时能在没有显式RHS()调用的情况下自行判断该用变量名还是其右侧表达式但为了让解析器正确工作凡是意图使用某个变量的 RHS 表达式最好显式调用RHS()%Autolev Code %------------ VARIABLES X, Y E X Y X 2*Y RHS_X RHS(X) I1 X I2 Y I3 X Y INERTIA B,I1,I2,I3 % - I_B_BO I1*B1*B1 I2*B2*B2 I3*B3*B3#SymPy Code #---------- x, y me.dynamicsymbols(x y) e x y # 不会为 e 建立符号 # {x: 2*y} 这类条目被存入 rhs 字典 rhs_x 2*y i1 x # 这些同样不是新建的 SymPy 符号 i2 y i3 x y body_b.inertia (me.inertia(body_b_f, i1, i2, i3), b_cm) # 打印结果: x*b_f.x*b_f.x y*b_f.y*b_f.y (xy)*b_f.z*b_f.z # 而 Autolev 的输出中保留的是 I1, I2, I3—— # Autolev 似乎能根据上下文自行判断何时使用 I1/I2/I3 的 RHS。3.12 SOLVE 命令的解析方式及其索引陷阱SOLVE被翻译为对sm.solve()的调用后续引用被求解变量时解析器会重复调用sm.solve(...)并以下标取结果%Autolev Code %------------ SOLVE(ZERO, X, Y) A RHS(X)*2 RHS(Y)#SymPy Code #---------- print(sm.solve(zero, x, y)) # 幕后x 的 rhs 被置为 sm.solve(zero, x, y)[x] a sm.solve(zero, x, y)[x]*2 sm.solve(zero, x, y)[y]注意solve返回的字典用[x]、[y]取项并不总是可靠可能需要检查底层字典结构并手工修正索引。3.13 惯量声明与惯量函数一套需要绕开差异的规则这是最微妙的一组差异原文档列出三条规则惯量声明INERTIA B,I1,I2,I3用于设置刚体的惯量惯量赋值器I_C_D expr仅在 C 是刚体时真正设置惯量若 C 是质点则只是解析成i_c_d expri_c_d作为普通变量使用惯量读取器表达式中使用的I_C_D或INERTIA命令必须配合EXPRESS命令指定参考系因为 SymPy 计算惯量二阶张量时需要这一信息。%Autolev Code %------------ INERTIA B,I1,I2,I3 I_B_BO X*A1*A1 Y*A2*A2 % 解析器为 B 设置惯量 I_P_Q X*A1*A1 Y^2*A2*A2 % 仅解析为 i_p_q expr E1 2*EXPRESS(I_B_O, A) E2 I_P_Q E3 EXPRESS(I_P_O, A) E4 EXPRESS(INERTIA(O), A) % E1: 用 EXPRESS 包裹 I_B_O让解析器与 SymPy 计算刚体 B 关于 O 的惯量 % E2: 只是使用上面定义的张量对象 I_P_Q它并未充当赋值器不做计算 % E3: 要求解析器计算质点 P 关于 O 的惯量 % E4: 要求解析器计算所有刚体关于 O 的惯量此外还有一条顺序约束若惯量声明设置的点不是质心该点相对于质心的位置矢量赋值语句必须写在惯量声明之前否则 SymPy 会抛错%Autolev Code %------------ P_SO_O X*A1 INERTIA S_(O) I1,I2,I33.14 如何确认某个命令是否被实现并非所有 Autolev 命令都已实现解析器目前只覆盖重要命令的基本形式。验证方法打开源码文件 _listener_autolev_antlr.py在其中搜索命令名即可确认是否被处理其对应代码段也展示了该命令期望的工作形式。四、限制与已知问题清单原文档的 Limitations and Issues 一节汇总如下前五项与 Gotchas 部分重叠此处按原文列出完整清单向量名与标量名相同时会被 Python 覆盖部分便捷的变量声明形式不被解析如嵌套花括号部分返回矩阵的便捷函数形式不被解析Autolev settings 不被解析Python 中符号与 rhs 表达式的工作方式差异很大可能产生意外结果SOLVE生成代码中的字典索引在许多情况下不可靠为使 PyDy 仿真代码正常工作需要把dynamicsymbols._t改为dynamicsymbols(t)。除此之外还有若干独立问题问题说明特征向量结果不一致许多情况下 Autolev 与 SymPy 的特征向量数值不同块矩阵Block matrices不解析原文档指出若让 SymPy 矩阵接受矩阵参数会更易解决TAYLOR不可用SymPy 对应物.series()目前无法与dynamicsymbols()配合工作约束支持不完整目前仅解析DEPENDENT约束AUXILIARY约束尚待支持原文档认为难度不大应尽快完成能量/动量函数未解析SymPy 缺少NICHECK()等对应物需对 SymPy 本身做扩展数值积分仅支持无参KANE()KANE(F1,F2)这类带参形式暂不可用PyDy 仿真代码的适用面窄仅在形如ZERO FR() FRSTAR()且矩阵被求解的场合可用PyDySystem类自动取forcing_full/mass_matrix_full并求解用户无法自定义方程原文档建议为该能力提需求五、未来改进方向原文档列出的三条路线对希望贡献代码的读者有直接参考价值完成《Dynamics Online》全书算例的解析。解析器的构建参考了 Autolev 官方 Tutorial 与Dynamics Online: Theory and Implementation Using Autolev一书中的全部代码——逐段验证解析结果、按需修正规则。目前书中约至第 4 章的大部分算例已可解析2-10、2-11、第 4 章剩余部分、第 5 章以及较不重要的第 6 章尚待完成。按优先级与难度修复上节所列问题。其中许多需要修改解析器代码本身另一些则更适合通过为 SymPy 增加功能来解决。从 CST 迁移到 AST。当前解析器基于 ANTLR 构建的是一种具体语法树CST代码与 ANTLR 语法强耦合。迁移到抽象语法树AST后解析逻辑将与语法文件解耦修改语法与规则都会更容易。从 Autolev.g4 与_antlr/目录的生成式结构可以直观看到这一耦合现状语法一改就必须重跑 _build_autolev_antlr.py 重新生成解析骨架。六、快速上手清单把前文要点压缩为可执行的检查单安装antlr4-python3-runtime版本 4.11——不满足会直接ImportError把广义坐标/速度写在MOTIONVARIABLES中其余变量写VARIABLES/CONSTANTS给出Q U形式的运动学微分方程变量名避开 Python 保留字标量与向量不要重名需要 RHS 表达式时显式写RHS(...)惯量读取时显式写EXPRESS(..., 参考系)调用parse_autolev(open(file.al), include_numericTrue)审查输出的 SymPy 代码仿真部分确认 PyDySystem的initial_conditions/constants/times与INPUT声明一致并处理dynamicsymbols(t)替换对照 test-examples/ 中成对的.al/.py文件自检解析结果跑 sympy/parsing/tests/test_autolev.py 验证回归。整体而言这个解析器目前覆盖的是重要命令的基本形式它的价值不仅在于自动化转换更在于生成的代码本身就是一份 SymPy mechanics 建模的教学范例——迁移 Autolev 工作流时先读生成代码、再对照 sympy_mechanics_for_autolev_users.rst 指南是效率最高的路径。【免费下载链接】sympyA computer algebra system written in pure Python项目地址: https://gitcode.com/GitHub_Trending/sy/sympy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考