Linguage

SymPy 基础入门教程:符号计算核心功能解析

欢迎来到 SymPy 的世界!SymPy 是一个强大的 Python 库,用于进行符号数学运算。与数值计算不同,符号计算处理的是数学表达式本身,而不是它们的具体数值。本教程将带您了解 SymPy 的基本功能,并通过与我们先前讨论的钢轨动力学案例相关联,展示这些功能在实际问题中的应用。

1. SymPy 是什么?为什么要用它?

想象一下,您在纸上推导复杂的数学公式,进行微分、积分、化简…… SymPy 就是您在计算机上的“草稿纸”和“数学助手”。它可以帮助您:

对于像钢轨动力学这样涉及大量复杂公式推导的问题,手动计算不仅耗时,还容易出错。SymPy 可以自动化这个过程,提高效率和准确性。

2. 安装与基本设置

如果您还没有安装 SymPy,可以通过 Python 的包管理器 pip 轻松安装:

pip install sympy

在您的 Python 脚本或 Jupyter Notebook 中使用 SymPy 前,首先需要导入它。为了让输出的数学公式更易读(尤其是在 Jupyter 环境中),可以启用 SymPy 的“美观打印” (pretty printing) 功能。

import sympy # 导入 SymPy 库

# 初始化美观打印功能
# use_unicode=True 允许使用 Unicode 字符来显示更美观的公式
# wrap_line=False 防止长公式在某些终端或 Notebook 中被不恰当地折行
sympy.init_printing(use_unicode=True, wrap_line=False)

# 定义一个简单的辅助函数,用于在教程中打印章节标题
def print_header(title):
    print("\n" + "="*50)
    print(f" {title} ")
    print("="*50)

3. 核心概念一:符号 (Symbols)

符号是 SymPy 进行运算的基本单元。它们代表了数学表达式中的变量或常数,但没有被赋予具体的数值。

示例:

print_header("3. 核心概念一:符号 (Symbols)")

# 定义单个符号 x
x = sympy.Symbol('x')
print(f"符号 x: {x}")

# 定义时间符号 t,并假设它是实数
t = sympy.Symbol('t', real=True)
print(f"符号 t: {t}, 是实数吗? {t.is_real}")

# 一次定义多个符号,并假设它们是正实数
l, E, I_y, m_r = sympy.symbols('l E I_y m_r', positive=True, real=True)
print(f"符号 l: {l}, 是正数吗? {l.is_positive}")
print(f"符号 E: {E}, I_y: {I_y}, m_r: {m_r}")

# 定义整数符号,常用于索引或计数
NM = sympy.Symbol('NM', integer=True, positive=True) # 例如,模态数
k_idx = sympy.Symbol('k', integer=True, positive=True)  # 例如,求和索引
print(f"符号 NM: {NM}, 是整数吗? {NM.is_integer}")
print(f"符号 k_idx: {k_idx}")

与钢轨案例的联系:

在钢轨动力学推导中,我们首先定义了所有物理参数(如 l,E,Iy,mr)、独立变量(x,t)、以及用于求和和表示模态的索引(k,h,s,j,NM,Nstotal​)作为 SymPy 符号。

4. 核心概念二:构建表达式 (Expressions)

一旦定义了符号,就可以像在普通数学中一样,使用它们和标准的 Python 运算符(+, -, *, /, ** 代表乘方)来构建数学表达式。SymPy 还提供了丰富的数学函数,如 sympy.sqrt() (平方根), sympy.sin() (正弦), sympy.cos() (余弦), sympy.pi (圆周率 π) 等。

示例:

print_header("4. 核心概念二:构建表达式 (Expressions)")

# 使用之前定义的符号构建表达式
expr1 = x**2 + 2*x + 1
print("表达式 expr1:")
sympy.pprint(expr1) # pprint 是 pretty print 的缩写

# 构建更复杂的表达式,例如简化的模态振型的一部分
# Z_k(x) 类似 sqrt(2/(m_r*l)) * sin(k*pi*x/l)
# 这里我们用 k_idx 代表 k
simple_modal_part = sympy.sin(k_idx * sympy.pi * x / l)
print("\n简化的模态振型部分 sin(k*pi*x/l):")
sympy.pprint(simple_modal_part)

# 包含物理参数的表达式
stiffness_like_term = E * I_y / l**3
print("\n类刚度项 E*I_y/l^3:")
sympy.pprint(stiffness_like_term)

与钢轨案例的联系:

我们用这种方式构建了模态振型函数 Zk(x) 的完整表达式,以及后续推导中出现的各种中间表达式。

5. 核心概念三:符号函数 (Symbolic Functions)

有时,我们需要表示一个未指定具体形式的函数,或者一个依赖于某些符号变量的函数(例如 f(x) 或 q(t))。

示例:

print_header("5. 核心概念三:符号函数 (Symbolic Functions)")

# 定义一个通用的未指定函数 f(x)
f = sympy.Function('f')(x)
print(f"通用函数 f(x): {f}")

# 定义一个时间 t 的函数 q(t)
q_func_t = sympy.Function('q')(t)
print(f"时间函数 q(t): {q_func_t}")

# 定义带索引的函数 q_k(t)
q_base = sympy.IndexedBase('q') # 'q' 是这个函数序列的名称
# q_base[k_idx] 代表符号 q_k (还不是一个函数)
# sympy.Function(str(q_base[k_idx]))(t) 将 q_k 定义为 t 的函数
q_k_t = sympy.Function(str(q_base[k_idx]))(t)
print(f"带索引的函数 q_k(t): {q_k_t}")

# 我们可以创建具体的 q_1(t)
q_1_t = sympy.Function(str(q_base[1]))(t)
print(f"具体的 q_1(t): {q_1_t}")

与钢轨案例的联系:

模态坐标 qk(t) 就是一个典型的带索引的时间函数,我们用 IndexedBaseFunction 来精确表示它。轨枕基础位移 Zsbs 和车轮力 pj 也是如此。

6. 核心概念四:替换 (Substitution)

替换是符号运算中非常常用的操作,允许我们将表达式中的一个符号或子表达式替换为另一个符号或表达式。

示例:

print_header("6. 核心概念四:替换 (Substitution)")

expr_to_sub = x**2 + sympy.sin(k_idx * sympy.pi * x / l)
print("原始表达式:")
sympy.pprint(expr_to_sub)

# 将 x 替换为符号 y
y = sympy.Symbol('y')
expr_sub_x_with_y = expr_to_sub.subs({x: y})
print("\n将 x 替换为 y:")
sympy.pprint(expr_sub_x_with_y)

# 将 k_idx 替换为具体的数值 1
expr_sub_k_with_1 = expr_to_sub.subs({k_idx: 1})
print("\n将 k_idx 替换为 1:")
sympy.pprint(expr_sub_k_with_1)

# 同时替换多个符号
expr_sub_multiple = expr_to_sub.subs({x: 0, k_idx: 2, l: sympy.pi})
print(f"\n同时替换 x=0, k_idx=2, l=pi: {expr_sub_multiple}")
# 结果是 sin(2*pi*0/pi) = sin(0) = 0

与钢轨案例的联系:

在构建具体的模态振型 Z1(x),Zh(x) 时,我们就是将通用模板 Z_k_x_expr_template 中的 k_iter_sym 替换为具体的数值或索引符号。在计算轨枕力时,将通用位移表达式中的 x 替换为特定轨枕的位置 xs[s]。

7. 核心概念五:微分 (Derivatives)

符号微分是 SymPy 的核心功能之一。

示例:

print_header("7. 核心概念五:微分 (Derivatives)")

# 之前定义的函数 q_1(t) 和符号 x
# expr_for_diff = q_1_t * sympy.sin(x)
# 为了更清晰,我们重新定义一个简单的 q(t)
q = sympy.Function('q')(t)
expr_for_diff = q * sympy.cos(x * t) # 一个包含 q(t), x, t 的表达式
print("用于微分的表达式:")
sympy.pprint(expr_for_diff)

# 对 t 求一阶偏导数 (q 是 t 的函数,cos(xt) 中的 t 也要考虑)
diff_t1 = sympy.diff(expr_for_diff, t)
print("\n对 t 的一阶偏导数:")
sympy.pprint(diff_t1)

# 对 t 求二阶偏导数
diff_t2 = sympy.diff(expr_for_diff, t, 2) # 或者 sympy.diff(expr_for_diff, t, t)
print("\n对 t 的二阶偏导数:")
sympy.pprint(diff_t2)

# 对 x 求一阶偏导数
diff_x1 = sympy.diff(expr_for_diff, x)
print("\n对 x 的一阶偏导数:")
sympy.pprint(diff_x1)

# 对 x 求四阶偏导数
# 以 Z_h(x) 为例
# Z_h_x = sympy.sqrt(2 / (m_r * l)) * sympy.sin(h_idx * sympy.pi * x / l) (h_idx 未在此处定义,假设已定义或用k_idx代替)
# 假设 h_idx 已经定义,或者我们用一个具体的索引
h_idx = sympy.Symbol('h_idx', integer=True, positive=True) # 假设 h_idx
Z_h_x = sympy.sqrt(2 / (m_r * l)) * sympy.sin(h_idx * sympy.pi * x / l)
Z_h_xxxx = sympy.diff(Z_h_x, x, 4)
print("\nZ_h(x) 对 x 的四阶导数:")
sympy.pprint(Z_h_xxxx)
print("化简后:")
sympy.pprint(Z_h_xxxx.simplify()) # 正弦函数的四阶导数会回到自身乘以一个系数

与钢轨案例的联系:

这是推导中的关键步骤。我们需要计算钢轨位移 Zr(x,t) 对时间 t 的二阶导数(得到加速度项 Z̈r)和对空间 x 的四阶导数(得到弯曲刚度项 Zr′′′′​)。

8. 核心概念六:积分 (Integrals)

SymPy 可以执行符号积分(不定积分和定积分)。

示例:

print_header("8. 核心概念六:积分 (Integrals)")

# 不定积分
integral_cos_x_indef = sympy.integrate(sympy.cos(x), x)
print(f"Integral(cos(x), x) = {integral_cos_x_indef}") # 结果是 sin(x)

# 定积分
integral_x_sq_0_to_1 = sympy.integrate(x**2, (x, 0, 1))
print(f"Integral(x^2, (x, 0, 1)) = {integral_x_sq_0_to_1}") # 结果是 1/3

# 创建一个积分对象,然后计算
# 例如,计算 Integral( Z_1(x)^2 * m_r, (x, 0, l) )
# Z_1_x = sympy.sqrt(2 / (m_r * l)) * sympy.sin(1 * sympy.pi * x / l)
# (需要确保 m_r, l, x 等符号已定义)
Z_1_x = sympy.sqrt(2 / (m_r * l)) * sympy.sin(1 * sympy.pi * x / l)
integral_obj = sympy.Integral(m_r * Z_1_x**2, (x, 0, l))
print("\n表示积分的对象:")
sympy.pprint(integral_obj)

result = integral_obj.doit()
print("\n计算后的积分结果 (应为1,表示模态正交归一性):")
sympy.pprint(result.simplify())

# 包含狄拉克 Delta 函数的积分
f_of_x = sympy.Function('f')(x)
x0 = sympy.Symbol('x0')
delta_integral_example = sympy.integrate(f_of_x * sympy.DiracDelta(x - x0), (x, -sympy.oo, sympy.oo))
# sympy.oo 代表无穷大
print("\nIntegral(f(x)*DiracDelta(x-x0), (x, -oo, oo)):")
sympy.pprint(delta_integral_example) # 结果应为 f(x0)

与钢轨案例的联系:

伽辽金法的核心步骤就是将残差与权函数(模态振型 Zh(x))相乘,然后在钢轨长度 [0,l] 上进行定积分。积分运算对于处理狄拉克 δ 函数(表示点荷载)和利用模态正交性至关重要。

9. 核心概念七:求和 (Summations)

与积分类似,SymPy 可以处理符号求和。

示例:

print_header("9. 核心概念七:求和 (Summations)")

# 一个简单的求和表达式 Sum_{k=1}^{N} k*x
N_sum_limit = sympy.Symbol('N', integer=True, positive=True)
# k_idx 已经在前面定义为 sympy.Symbol('k', integer=True, positive=True)
sum_expr_obj = sympy.Sum(k_idx * x, (k_idx, 1, N_sum_limit))
print("符号求和对象 Sum_{k=1}^{N} k*x:")
sympy.pprint(sum_expr_obj)

# 如果上限是具体数字,可以尝试计算
sum_k_1_to_3_obj = sympy.Sum(k_idx**2, (k_idx, 1, 3))
print("\nSum_{k=1}^{3} k^2:")
sympy.pprint(sum_k_1_to_3_obj)
print(f"计算结果: {sum_k_1_to_3_obj.doit()}") # 1^2 + 2^2 + 3^2 = 1 + 4 + 9 = 14

与钢轨案例的联系:

钢轨的总位移 Zr(x,t) 是通过模态叠加法表示的,即对所有模态的贡献进行求和:Zr(x,t)=∑k=1NMZk(x)qk(t)。PDE 中的外力项(如轨枕力和车轮力)也涉及到对所有轨枕或车轮的求和。

10. 核心概念八:特殊函数

SymPy 内置了许多数学中的特殊函数。在我们的案例中,最重要的是:

示例:

print_header("10. 核心概念八:特殊函数")

# DiracDelta 函数
x_load_pos = sympy.Symbol('x_load')
point_load_representation = sympy.DiracDelta(x - x_load_pos)
print("用 DiracDelta 表示在 x_load_pos 处的点作用:")
sympy.pprint(point_load_representation)

# KroneckerDelta 函数
# h_idx, k_idx 假设已定义
# h_idx = sympy.Symbol('h_idx', integer=True, positive=True) # 如果未定义
# k_idx = sympy.Symbol('k_idx', integer=True, positive=True) # 如果未定义
M_hk_mass_element = sympy.KroneckerDelta(h_idx, k_idx) # 例如,质量矩阵的元素
print("\n用 KroneckerDelta 表示 M_hk:")
sympy.pprint(M_hk_mass_element)

print(f"KroneckerDelta(1, 1) = {sympy.KroneckerDelta(1, 1)}") # 结果为 1
print(f"KroneckerDelta(1, 2) = {sympy.KroneckerDelta(1, 2)}") # 结果为 0

与钢轨案例的联系:

狄拉克 δ 函数用于表示作用在钢轨特定位置的轨枕反力和车轮力。克罗内克 δ 函数则源于模态振型的正交性,使得质量矩阵和钢轨自身刚度矩阵(在模态坐标下)通常是对角阵或包含 δhk

11. 核心概念九:代数操控 (Algebraic Manipulations)

得到符号表达式后,我们经常需要对其进行代数上的操控,如化简、展开、合并同类项、提取系数等。

示例:

print_header("11. 核心概念九:代数操控 (Algebraic Manipulations)")

# 创建一个等式
eq1 = sympy.Eq(x**2 - 1, (x-1)*(x+1))
print("等式对象:")
sympy.pprint(eq1)

# 化简
expr_to_simplify = (sympy.sin(x)**2 + sympy.cos(x)**2) * (x+1)/(x+1)
print(f"\n待化简表达式: {expr_to_simplify}")
print(f"化简后: {expr_to_simplify.simplify()}") # 结果应为 1

# 展开
# y 需要先定义
y = sympy.Symbol('y')
expr_to_expand = (x + y)**3
print(f"\n待展开表达式: (x+y)**3")
sympy.pprint(expr_to_expand.expand())

# 合并同类项
# 假设有一个整理后的方程左端项,我们要按 q_k(t) 及其导数整理
# 为了演示,构造一个简单的表达式
a, b, c_const = sympy.symbols('a b c_const')
# q_1_t 已在前面定义: q_1_t = sympy.Function(str(q_base[1]))(t)
# q_base 和 t 均已定义
collected_expr_example = a * q_1_t.diff(t,2) + b * q_1_t.diff(t) + a * q_1_t.diff(t) + c_const * q_1_t
print("\n待合并同类项的表达式:")
sympy.pprint(collected_expr_example)
collected_result = sympy.collect(collected_expr_example, [q_1_t.diff(t,2), q_1_t.diff(t), q_1_t])
print("按 q_1(t) 及其导数合并后:")
sympy.pprint(collected_result)

# 提取系数
# 从 collected_result 中提取 q_1_t.diff(t) 的系数
coeff_q1_dot = collected_result.coeff(q_1_t.diff(t))
print(f"\nq_1_dot(t) 的系数: {coeff_q1_dot}") # 应该是 a + b

# 提取 q_1_t 的系数
coeff_q1 = collected_result.coeff(q_1_t)
print(f"q_1(t) 的系数: {coeff_q1}") # 应该是 c_const

与钢轨案例的联系:

在通过伽辽金法得到积分后的方程后,我们需要对其进行大量的代数整理,特别是使用 collectcoeff 来分离出 q̈k(t), q̇k(t), qk(t) 各项的系数,从而得到质量矩阵 Mhk、阻尼矩阵 Chk 和刚度矩阵 Khk 的元素。

12. 核心概念十:矩阵 (Matrices) (简介)

SymPy 提供了矩阵对象 sympy.Matrix,可以用于存储和操作符号矩阵。

示例:

print_header("12. 核心概念十:矩阵 (Matrices) (简介)")

# 创建一个简单的 2x2 符号矩阵
M11, M12, M21, M22 = sympy.symbols('M11 M12 M21 M22')
matrix_M = sympy.Matrix([
    [M11, M12],
    [M21, M22]
])
print("2x2 符号矩阵 M:")
sympy.pprint(matrix_M)

# 创建一个 2x1 的列向量 (例如,模态坐标向量)
q1_sym = sympy.Symbol('q1') # 用简单符号代替 q_1(t) 以简化显示
q2_sym = sympy.Symbol('q2')
vector_q = sympy.Matrix([
    [q1_sym],
    [q2_sym]
])
print("\n2x1 列向量 q:")
sympy.pprint(vector_q)

# 矩阵乘法 (符号层面)
product_Mq = matrix_M * vector_q
print("\n矩阵乘积 M*q:")
sympy.pprint(product_Mq)

与钢轨案例的联系:

我们的最终目标之一就是得到系统运动方程的矩阵形式 Mq̈+Cq̇+Kq=F。通过提取系数得到的 Mhk,Chk,Khk 就是这些矩阵的元素。

总结与展望

本教程介绍了 SymPy 的一些核心功能,这些功能是进行符号数学推导的基础。我们通过简单的示例解释了如何定义符号、构建表达式、进行微积分运算、处理求和与特殊函数、执行代数操控以及使用矩阵。每一个概念都与我们之前复杂的钢轨动力学推导案例中的步骤相关联。

掌握了这些基础后,您就可以开始尝试更复杂的符号推导任务了。SymPy 的功能远不止于此,它还包括求解方程、级数展开、线性代数、张量运算等等。不断实践是学习 SymPy 的最佳途径。

希望本教程能为您打开符号计算的大门!

print_header("教程结束")