简介

SymPy 是一个开源的符号计算Python库,SymPy采用了宽松的BSD开源协议,并且完全免费。它的目标是成为一个全功能的计算机代数系统(CAS),同时保持代码尽可能简单,以便于理解和扩展。

本学习笔记参考官方的教程:https://docs.sympy.org/latest/tutorials

安装

如果你使用Anacoda,无需安装,Anacoda内置了SymPy。

如果你使用pip:

1
pip install sympy

前置芝士

会Python就行。数学水平因人而异,根据自己的需要选用相应的功能。

什么是符号计算?

前面提到,SymPy是一个 符号计算 库,那么什么是符号计算。符号计算(Symbolic Computation),就是在计算机上用符号精确表示数学对象,而不是得到一个近似的数值解。

看一个例子:

1
2
3
>>>import math
>>>math.sqrt(8)
2.8284271247461903

Python的math库给出的计算8\sqrt 8的结果是2.82842712474619032.8284271247461903,然而实际上,这只是8\sqrt 8的一个近似值,我们知道,它的精确值是不能用有限的小数表示的。

而在SymPy中:

1
2
3
>>> from sympy import *
>>> sqrt(8)
2*sqrt(2)

SymPy给我们的结果是8=22\sqrt 8 = 2 \sqrt 2,这才是一个精确的化简的结果。在SymPy中,非完全平方数的平方根默认会被保留。

定义符号与计算

在SymPy中,符号需要在使用前用Symbol()symbols()来定义:

1
2
3
a = Symbol('a') 
x, y = symbols('x y')
# symbols接受一系列用空格或逗号隔开的符号,返回对应的变量

最好将变量名设置得与符号名一致,方便记忆和使用。除非符号中包含了Python不允许的符号,或者符号名太长,想使用一个短一点的变量名来表示。

SymPy支持直接使用+-*/**来操作这些符号。

1
2
3
4
5
6
7
8
>>> x, y = symbols("x y") 
>>> expr = x + 2*y
>>> expr
x + 2*y
>>> expr + 1 - x
2*y + 1
>>> expr / 2
x/2 + y

输入x * expr你会发现SymPy并没有为我们展开表达式为x**2 + 2*x*y,而是保留了x*(x + 2*y)的形式。因为因式分解是SymPy默认的化简操作,你也可以利用expand来展开,用factor来分解。

1
2
3
4
>>> expand(x * expr)
x**2 + 2*x*y
>>> factor(2*x**2 - 7*x - 4)
(x - 4)*(2*x + 1)

SymPy计算出了展开x(x+2y)x(x + 2y)的结果x2+2xyx^2 + 2xy,和因式分解2x27x42x^2 - 7x - 4的结果(x4)(2x+1)(x-4)(2x+1)

输出

可以通过调整一些选项改变SymPy输出的格式。

init_printing(use_unicode=True)可以允许SymPy使用Unicode输出。

计算+sin(x2)dx\int^{+\infty}_{-\infty}\sin(x^2)dx

1
2
3
4
5
6
7
8
9
10
11
>>> init_printing(use_unicode=False)   
>>> integrate(sin(x**2), (x, -oo, oo))
___ ____
\/ 2 *\/ pi
------------
2
>>> init_printing(use_unicode=True)
>>> integrate(sin(x**2), (x, -oo, oo))
√2⋅√π
─────
2

第一个输出中SymPy使用了字符画的方式画出\sqrt \quad,使用了pi表示π\pi。第二个输出使用了Unicode字符“√”和“π”。

SymPy还可以使用LaTeX\LaTeX:

1
2
>>> latex(Integral(cos(x)**2, (x, 0, pi)))
\int\limits_{0}^{\pi} \cos^{2}{\left(x \right)}\, dx

\int\limits_{0}^{\pi} \cos^{2}{\left(x \right)}\, dx使用任意一个LaTeX\LaTeX编译器编译,就可以得到下面这样漂亮的结果:

0πcos2(x)dx\int\limits_{0}^{\pi} \cos^{2}{\left(x \right)}\, dx

符号的替换

subs(),但注意subs()并不改变原表达式。

1
2
3
4
5
>>> expr = cos(x) + 1
>>> expr.subs(x, y)
cos(y) + 1
>>> expr
cos(x) + 1

也可以替换数字,进行在某一点求值的操作。

1
2
3
>>> expr = sin(2*x) + cos(2*x)
>>> expr.subs(x, pi/5)
-1/4 + sqrt(5)/4 + sqrt(sqrt(5)/8 + 5/8)

也可以替换多个符号

1
2
3
>>> expr = x**3 + 4*x*y - z
>>> expr.subs([(x, 2), (y, 4), (z, 0)])
40

字符串转表达式

sympify()(不是symplify(

1
2
3
4
5
6
>>> str_expr = "x**2 + 3*x - 1/2"
>>> expr = sympify(str_expr)
>>> expr
x**2 + 3*x - 1/2
>>> expr.subs(x, 2)
19/2

表达式转浮点数

evalf()

1
2
3
>>> expr = sqrt(8)
>>> expr.evalf()
2.82842712474619

默认情况下,会给出15位的精度,但是也可以指定所需要的精度

1
2
expr.evalf(100) 
2828427124746190097603377448419396157139343750753896146353359475981464956924214077700775068655283145

如果想要用subs()进行单点求值后再用evalf()进行数值估计,用expr.evalf(subs={<Symbol>: <Value>})是比expr.subs(<Symbol>, <Value>).evalf()更推荐的方法。因为这样会使计算更高效且稳定。

1
2
3
>>> expr = cos(2*x)
>>> expr.evalf(subs={x: 2.4})
0.0874989834394464

假如指定了chop=True,SymPy会自动移除小于期望精度的舍入误差

1
2
3
4
5
>>> one = cos(1)**2 + sin(1)**2
>>> (one - 1).evalf()
-0.e-124
>>> (one - 1).evalf(chop=True)
0

多点求值

使用SymPy对表达式做多点的求值(比如成千上万点后)是比较慢的。如果有这种需求,可以对表达式lambdify()后交给其他库处理(比如NumPy或SciPy)

1
2
3
4
5
6
7
>>> import numpy                                     
>>> a = numpy.arange(10)
>>> expr = sin(x)
>>> f = lambdify(x, expr, "numpy")
>>> f(a)
array([ 0. , 0.84147098, 0.90929743, 0.14112001, -0.7568025 ,
-0.95892427, -0.2794155 , 0.6569866 , 0.98935825, 0.41211849])

lambdify()将一个SymPy表达式转换成一个计算表达式在某点的值的函数,以便于使用其他库进行计算。