陷阱与误区

介绍

SymPy 运行在 Python 编程语言 下,因此有些行为可能与其他独立的计算机代数系统(如 Maple 或 Mathematica)不同。以下是您在使用 SymPy 时可能会遇到的一些陷阱和问题。另请参阅 入门教程、SymPy 文档的其余部分以及 官方 Python 教程

如果你已经熟悉C或Java,你可能也想看看这个 4分钟Python教程

忽略示例中的 #doctest: +SKIP 。这与示例的内部测试有关。

等号 (=)

单等号

等号 (=) 是赋值运算符,而不是等式。如果你想做 \(x = y\),使用 Eq(x, y) 表示等式。或者,所有表达式都假设等于零,因此你可以直接减去一边并使用 x - y

等号的正确使用是将表达式赋值给变量。

例如:

>>> from sympy.abc import x, y
>>> a = x - y
>>> print(a)
x - y

双等号

双等号 (==) 用于测试相等性。然而,这测试的是表达式的完全相等,而不是符号上的相等。例如:

>>> (x + 1)**2 == x**2 + 2*x + 1
False
>>> (x + 1)**2 == (x + 1)**2
True

如果你想测试符号的相等性,一种方法是将从另一个表达式中减去一个表达式,并通过 expand()simplify()trigsimp() 等函数运行它,看看方程是否简化为 0。

>>> from sympy import simplify, cos, sin, expand
>>> simplify((x + 1)**2 - (x**2 + 2*x + 1))
0
>>> eq = sin(2*x) - 2*sin(x)*cos(x)
>>> simplify(eq)
0
>>> expand(eq, trig=True)
0

备注

另请参阅 术语表 中的 结构相等性

变量

变量赋值不会在表达式之间创建关系

当你使用 = 进行赋值时,记住在 Python 中,就像大多数编程语言一样,如果你改变了赋值的值,变量本身不会改变。你输入的方程式使用创建时的值来“填充”值,就像常规的 Python 定义一样。它们不会被之后的更改所影响。考虑以下内容:

>>> from sympy import Symbol
>>> a = Symbol('a')  # Symbol, `a`, stored as variable "a"
>>> b = a + 1        # an expression involving `a` stored as variable "b"
>>> print(b)
a + 1
>>> a = 4            # "a" now points to literal integer 4, not Symbol('a')
>>> print(a)
4
>>> print(b)          # "b" is still pointing at the expression involving `a`
a + 1

改变数量 a 不会改变 b ;你不是在处理一组联立方程。记住,当你打印一个指向 SymPy 对象的变量时,打印出来的字符串是该对象创建时赋予的字符串;这个字符串不必与你分配给它的变量相同,这可能会有所帮助。

>>> from sympy import var
>>> r, t, d = var('rate time short_life')
>>> d = r*t
>>> print(d)
rate*time
>>> r = 80
>>> t = 2
>>> print(d)        # We haven't changed d, only r and t
rate*time
>>> d = r*t
>>> print(d)        # Now d is using the current values of r and t
160

如果你需要相互依赖的变量,你可以定义函数。使用 def 操作符。缩进函数体。更多关于定义函数的信息,请参阅 Python 文档。

>>> c, d = var('c d')
>>> print(c)
c
>>> print(d)
d
>>> def ctimesd():
...     """
...     This function returns whatever c is times whatever d is.
...     """
...     return c*d
...
>>> ctimesd()
c*d
>>> c = 2
>>> print(c)
2
>>> ctimesd()
2*d

如果你定义了一个循环关系,你将会得到一个 RuntimeError

>>> def a():
...     return b()
...
>>> def b():
...     return a()
...
>>> a() 
Traceback (most recent call last):
  File "...", line ..., in ...
    compileflags, 1) in test.globs
  File "<...>", line 1, in <module>
    a()
  File "<...>", line 2, in a
    return b()
  File "<...>", line 2, in b
    return a()
  File "<...>", line 2, in a
    return b()
...
RuntimeError: maximum recursion depth exceeded

备注

另请参阅 术语表 中的 不可变

符号

符号是变量,和其他所有变量一样,它们在使用之前需要被赋值。例如:

>>> import sympy
>>> z**2  # z is not defined yet 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined
>>> sympy.var('z')  # This is the easiest way to define z as a standard symbol
z
>>> z**2
z**2

如果你使用 isympy ,它会为你运行以下命令,为你提供一些默认的符号和函数。

>>> from __future__ import division
>>> from sympy import *
>>> x, y, z, t = symbols('x y z t')
>>> k, m, n = symbols('k m n', integer=True)
>>> f, g, h = symbols('f g h', cls=Function)

你也可以从 sympy.abc 导入常见的符号名称。

>>> from sympy.abc import w
>>> w
w
>>> import sympy
>>> dir(sympy.abc)  
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'Symbol', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'__builtins__', '__doc__', '__file__', '__name__', '__package__', '_greek',
'_latin', 'a', 'alpha', 'b', 'beta', 'c', 'chi', 'd', 'delta', 'e',
'epsilon', 'eta', 'f', 'g', 'gamma', 'h', 'i', 'iota', 'j', 'k', 'kappa',
'l', 'm', 'mu', 'n', 'nu', 'o', 'omega', 'omicron', 'p', 'phi', 'pi',
'psi', 'q', 'r', 'rho', 's', 'sigma', 't', 'tau', 'theta', 'u', 'upsilon',
'v', 'w', 'x', 'xi', 'y', 'z', 'zeta']

如果你想控制变量的假设,请使用 Symbolsymbols()。请参阅下面的 关键字参数

Lastly, it is recommended that you not use I, E, S, N, C, O, or Q for variable or symbol names, as those are used for the imaginary unit (\(i\)), the base of the natural logarithm (\(e\)), the sympify() function (see Symbolic Expressions below), numeric evaluation (N() is equivalent to evalf() ), the big O order symbol (as in \(O(n\log{n})\)), and the assumptions object that holds a list of supported ask keys (such as Q.real), respectively. You can use the mnemonic OSINEQ to remember what Symbols are defined by default in SymPy. Or better yet, always use lowercase letters for Symbol names. Python will not prevent you from overriding default SymPy names or functions, so be careful.

>>> cos(pi)  # cos and pi are a built-in sympy names.
-1
>>> pi = 3   # Notice that there is no warning for overriding pi.
>>> cos(pi)
cos(3)
>>> def cos(x):  # No warning for overriding built-in functions either.
...     return 5*x
...
>>> cos(pi)
15
>>> from sympy import cos  # reimport to restore normal behavior

要获取 SymPy 中所有默认名称的完整列表,请执行:

>>> import sympy
>>> dir(sympy)  
# A big list of all default sympy names and functions follows.
# Ignore everything that starts and ends with __.

如果你安装了 IPython 并使用 isympy,你也可以按 TAB 键来获取所有内置名称的列表并自动补全。此外,请参阅 此页面 以获取在常规 Python 控制台中获取制表符补全的技巧。

备注

另请参阅 最佳实践 页面中的 定义符号的最佳实践 部分。

函数

一个像 f(x) 这样的函数可以通过定义函数和变量来创建:

>>> from sympy import Function
>>> f = Function('f')
>>> x = Symbol('x')
>>> f(x)
f(x)

如果你将 f(x) 赋值给一个 Python 变量 \(f\),你将失去复制和粘贴该函数的能力,或者创建一个具有不同参数的函数:Function('f') 是可调用的,但 Function('f')(x) 不是:

>>> f1 = Function('f1')
>>> f2 = Function('f2')('x')
>>> f1
f1
>>> f2
f2(x)
>>> f1(1)
f1(1)
>>> f2(1)
Traceback (most recent call last):
...
TypeError: 'f2' object is not callable
>>> f2.subs(x, 1)
f2(1)

符号表达式

Python 数字 vs. SymPy 数字

SymPy uses its own classes for integers, rational numbers, and floating point numbers instead of the default Python int and float types because it allows for more control. But you have to be careful. If you type an expression that just has numbers in it, it will default to a Python expression. Use the sympify() function, or just S, to ensure that something is a SymPy expression.

>>> 6.2  # Python float. Notice the floating point accuracy problems.
6.2000000000000002
>>> type(6.2)  # <class 'float'>
<class 'float'>
>>> S(6.2)  # SymPy Float has no such problems because of arbitrary precision.
6.20000000000000
>>> type(S(6.2))
<class 'sympy.core.numbers.Float'>

If you include numbers in a SymPy expression, they will be sympified automatically, but there is one gotcha you should be aware of. If you do <number>/<number> inside of a SymPy expression, Python will evaluate the two numbers before SymPy has a chance to get to them. The solution is to sympify() one of the numbers, or use Rational (or Python’s Fraction).

>>> x**(1/2)  # evaluates to x**0 or x**0.5
x**0.5
>>> x**(S(1)/2)  # sympify one of the ints
sqrt(x)
>>> x**Rational(1, 2)  # use the Rational class
sqrt(x)

使用 1/2 的幂,你也可以使用 sqrt 的简写:

>>> sqrt(x) == x**Rational(1, 2)
True

如果两个整数之间没有直接用除号分隔,那么你就不必担心这个问题:

>>> x**(2*x/3)
x**(2*x/3)

备注

A common mistake is copying an expression that is printed and reusing it. If the expression has a Rational (i.e., <number>/<number>) in it, you will not get the same result, obtaining the Python result for the division rather than a SymPy Rational.

>>> x = Symbol('x')
>>> print(solve(7*x -22, x))
[22/7]
>>> 22/7  #copy and paste gives a float
3.142857142857143
>>> # One solution is to just assign the expression to a variable
>>> # if we need to use it again.
>>> a = solve(7*x - 22, x)[0]
>>> a
22/7

另一种解决方案是在表达式周围加上引号并通过 S() 运行它(即,将其符号化):

>>> S("22/7")
22/7

Rational only works for number/number and is only meant for rational numbers. If you want a fraction with symbols or expressions in it, just use /. If you do number/expression or expression/number, then the number will automatically be converted into a SymPy Number. You only need to be careful with number/number.

>>> Rational(2, x)
Traceback (most recent call last):
...
TypeError: invalid input: x
>>> 2/x
2/x

使用浮点数和有理数评估表达式

SymPy 跟踪 Float 对象的精度。默认精度为 15 位。当包含 Float 的表达式被求值时,结果将以 15 位精度表示,但这些数字(取决于计算中涉及的数字)可能并非全部有效。

首先要记住的是 Float 是如何创建的:它是用一个值和一个精度创建的。精度表示在评估该 ``Float``(或它出现的表达式)时使用的值的精确程度。

这些值可以作为字符串、整数、浮点数或有理数给出。

  • 字符串和整数被解释为精确值

>>> Float(100)
100.000000000000
>>> Float('100', 5)
100.00
  • 为了使精度与数字的位数匹配,可以使用空字符串作为精度

>>> Float(100, '')
100.
>>> Float('12.34')
12.3400000000000
>>> Float('12.34', '')
12.34
>>> s, r = [Float(j, 3) for j in ('0.25', Rational(1, 7))]
>>> for f in [s, r]:
...     print(f)
0.250
0.143

接下来,注意到这些值中的每一个在3位数上看起来都是正确的。但如果我们尝试将它们评估到20位数,差异将会变得明显:

0.25(精确到3位)表示一个没有重复的二进制小数;1/7在二进制和十进制中都是重复的——它不能在超过前3位数字后准确表示(正确的十进制是重复的142857):

>>> s.n(20)
0.25000000000000000000
>>> r.n(20)
0.14285278320312500000

重要的是要认识到,尽管浮点数以任意精度显示为十进制,但它实际上是以二进制存储的。一旦创建了浮点数,其二进制信息就会在给定的精度下设定。该值的精度不能随后更改;因此,1/7 在 3 位精度下,可以用二进制零填充,但这不会使其成为更精确的 1/7 值。

如果在计算中涉及不精确的低精度数值和高精度数值,evalf 引擎将提高低精度数值的精度,并得到不精确的结果。这是有限精度计算的一个特性:

>>> Float('0.1', 10) + Float('0.1', 3)
0.2000061035

尽管 evalf 引擎试图保持10位数的精度(因为这是表示的最高精度),但使用的3位数精度限制了准确性约为4位数——并非您看到的所有数字都是有效的。evalf 不会尝试跟踪有效数字的数量。

那个涉及两个不同精度数字相加的非常简单的表达式,希望能帮助你理解为什么更复杂的表达式(比如可能不会简化的三角函数表达式)即使经过正确的简化,也不会精确地计算为零。考虑这个未简化的三角恒等式,乘以一个大数:

>>> big = 12345678901234567890
>>> big_trig_identity = big*cos(x)**2 + big*sin(x)**2 - big*1
>>> abs(big_trig_identity.subs(x, .1).n(2)) > 1000
True

\(\cos\)\(\sin\) 项被计算到15位精度并与大数相乘时,它们给出的结果是一个只有15位精度(大约)的大数,当从20位大数中减去这个结果时,结果并不为零。

有三件事可以帮助你为表达式获得更精确的数值:

1) Pass the desired substitutions with the call to evaluate. By doing the subs first, the Float values cannot be updated as necessary. By passing the desired substitutions with the call to evalf the ability to re-evaluate as necessary is gained and the results are impressively better:

>>> big_trig_identity.n(2, {x: 0.1})
-0.e-91

2) Use Rationals, not Floats. During the evaluation process, the Rational can be computed to an arbitrary precision while the Float, once created – at a default of 15 digits – cannot. Compare the value of -1.4e+3 above with the nearly zero value obtained when replacing x with a Rational representing 1/10 – before the call to evaluate:

>>> big_trig_identity.subs(x, S('1/10')).n(2)
0.e-91

3) Try to simplify the expression. In this case, SymPy will recognize the trig identity and simplify it to zero so you don’t even have to evaluate it numerically:

>>> big_trig_identity.simplify()
0

表达式的不可变性

SymPy 中的表达式是不可变的,不能通过原地操作进行修改。这意味着一个函数总是会返回一个对象,而原始表达式不会被修改。以下示例片段展示了这是如何工作的:

def main():
    var('x y a b')
    expr = 3*x + 4*y
    print('original =', expr)
    expr_modified = expr.subs({x: a, y: b})
    print('modified =', expr_modified)

if __name__ == "__main__":
    main()

The output shows that the subs() function has replaced variable x with variable a, and variable y with variable b:

original = 3*x + 4*y
modified = 3*a + 4*b

The subs() function does not modify the original expression expr. Rather, a modified copy of the expression is returned. This returned object is stored in the variable expr_modified. Note that unlike C/C++ and other high-level languages, Python does not require you to declare a variable before it is used.

数学运算符

SymPy 使用与 Python 相同的默认运算符。其中大多数,如 */+-,是标准的。除了上述 Python 数字与 SymPy 数字 中讨论的整数除法外,您还应该注意不允许隐式乘法。每当您希望乘以某些内容时,都需要使用 *。此外,要使某物升幂,请使用 **,而不是许多计算机代数系统使用的 ^。括号 () 会像您通常期望的那样改变运算符优先级。

isympy 中,使用 ipython 外壳:

>>> 2x
Traceback (most recent call last):
...
SyntaxError: invalid syntax
>>> 2*x
2*x
>>> (x + 1)^2  # This is not power.  Use ** instead.
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for ^: 'Add' and 'int'
>>> (x + 1)**2
(x + 1)**2
>>> pprint(3 - x**(2*x)/(x + 1))
    2*x
   x
- ----- + 3
  x + 1

反三角函数

SymPy uses different names for some functions than most computer algebra systems. In particular, the inverse trig functions use the python names of asin, acos and so on instead of the usual arcsin and arccos. Use the methods described in Symbols above to see the names of all SymPy functions.

Sqrt 不是一个函数

与存在指数函数(exp)不同,没有 sqrt 函数。sqrt(x) 用于表示 Pow(x, S(1)/2),因此如果你想检查一个表达式中是否包含任何平方根,expr.has(sqrt) 将不起作用。你必须查找指数为二分之一的 ``Pow``(如果在分母中则为负二分之一,例如

>>> (y + sqrt(x)).find(Wild('w')**S.Half)
{sqrt(x)}
>>> (y + 1/sqrt(x)).find(Wild('w')**-S.Half)
{1/sqrt(x)}

如果你对 sqrt 的任何次方感兴趣,那么以下模式将是合适的

>>> sq = lambda s: s.is_Pow and s.exp.is_Rational and s.exp.q == 2
>>> (y + sqrt(x)**3).find(sq)
{x**(3/2)}

特殊符号

符号 [], {}, =, 和 () 在 Python 中有特殊含义,因此在 SymPy 中也是如此。有关更多信息,请参阅上述链接的 Python 文档。

列表

方括号 [] 表示一个列表。列表是一个容器,可以容纳任意数量的不同对象。列表可以包含任何内容,包括不同类型的项目。列表是可变的,这意味着您可以在创建列表后更改其元素。您也可以使用方括号访问列表中的项目,将其放在列表或列表变量之后。项目使用项目前的空格进行编号。

备注

列表索引从 0 开始。

示例:

>>> a = [x, 1]  # A simple list of two items
>>> a
[x, 1]
>>> a[0]  # This is the first item
x
>>> a[0] = 2  # You can change values of lists after they have been created
>>> print(a)
[2, 1]
>>> print(solve(x**2 + 2*x - 1, x)) # Some functions return lists
[-1 + sqrt(2), -sqrt(2) - 1]

备注

有关列表以及使用方括号表示法访问列表元素的更多信息,请参阅 Python 文档。

字典

花括号 {} 表示一个字典,或简称为 dict。字典是一个无序的、键和值不重复的列表。语法为 {key: value}。你可以使用方括号表示法访问键的值。

>>> d = {'a': 1, 'b': 2}  # A dictionary.
>>> d
{'a': 1, 'b': 2}
>>> d['a']  # How to access items in a dict
1
>>> roots((x - 1)**2*(x - 2), x)  # Some functions return dicts
{1: 2, 2: 1}
>>> # Some SymPy functions return dictionaries.  For example,
>>> # roots returns a dictionary of root:multiplicity items.
>>> roots((x - 5)**2*(x + 3), x)
{-3: 1, 5: 2}
>>> # This means that the root -3 occurs once and the root 5 occurs twice.

备注

有关字典的更多信息,请参阅 Python 文档。

元组

括号 () 除了改变运算符优先级和在函数调用中的使用(如 cos(x)),还用于元组。tuple列表 相同,只是它是不可变的。这意味着一旦创建,您无法更改它们的值。通常,您在 SymPy 中不需要使用元组,但有时输入括号比方括号更方便。

>>> t = (1, 2, x)  # Tuples are like lists
>>> t
(1, 2, x)
>>> t[0]
1
>>> t[0] = 4  # Except you cannot change them after they have been created
Traceback (most recent call last):
  File "<console>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

单元素元组与列表不同,必须包含一个逗号:

>>> (x,)
(x,)

没有逗号,一个不带逗号的单一表达式不是一个元组:

>>> (x)
x

非空元组不需要括号;逗号是必须的:

>>> x,y
(x, y)
>>> x,
(x,)

一个空的元组可以用空的圆括号创建:

>>> ()
()

如果你想在有界限的情况下进行积分,integrate 接受一个序列作为第二个参数(元组或列表也可以):

>>> integrate(x**2, (x, 0, 1))
1/3
>>> integrate(x**2, [x, 0, 1])
1/3

备注

有关元组的更多信息,请参阅 Python 文档。

关键字参数

除了上述 描述的用法 之外,等号 (=) 还用于为函数提供命名参数。任何在其参数列表中包含 key=value 的函数(参见下文如何查找此信息),默认情况下 key 将被设置为 value。您可以通过在函数调用中使用等号提供自己的值来更改键的值。此外,在参数列表中具有 ** 后跟名称的函数(通常是 **kwargs**assumptions)允许您添加任意数量的 key=value 对,这些对将根据函数进行评估。

sqrt(x**2) 不会自动简化为 x,因为默认情况下假设 x 是复数,例如,sqrt((-1)**2) == sqrt(1) == 1 != -1

>>> sqrt(x**2)
sqrt(x**2)

给符号赋予假设是使用关键字参数的一个例子:

>>> x = Symbol('x', positive=True)

由于知道 x >= 0,平方根现在将简化:

>>> sqrt(x**2)
x

powsimp 有一个默认参数 combine='all'

>>> pprint(powsimp(x**n*x**m*y**n*y**m))
     m + n
(x*y)

将 combine 设置为默认值与不设置它是一样的。

>>> pprint(powsimp(x**n*x**m*y**n*y**m, combine='all'))
     m + n
(x*y)

非默认选项是 'exp',它结合了指数…

>>> pprint(powsimp(x**n*x**m*y**n*y**m, combine='exp'))
 m + n  m + n
x     *y

…以及 ‘base’,它结合了基础。

>>> pprint(powsimp(x**n*x**m*y**n*y**m, combine='base'))
     m      n
(x*y) *(x*y)

备注

有关函数参数的更多信息,请参阅 Python 文档。

从 SymPy 内部获取帮助

help()

虽然所有文档都可以在 docs.sympy.orgSymPy Wiki 上找到,但你也可以从运行 SymPy 的 Python 解释器中获取函数信息。最简单的方法是使用 help(function),或者如果你使用的是 ipython,可以使用 function?:

In [1]: help(powsimp)  # help() works everywhere

In [2]: # But in ipython, you can also use ?, which is better because it
In [3]: # it gives you more information
In [4]: powsimp?

这些将为您提供 powsimp() 的函数参数和文档字符串。输出将如下所示:

sympy.simplify.simplify.powsimp(
expr,
deep=False,
combine='all',
force=False,
measure=<function count_ops>,
)[源代码][源代码]

通过合并具有相同底数和指数的幂来简化表达式。

示例

>>> from sympy import powsimp, exp, log, symbols
>>> from sympy.abc import x, y, z, n
>>> powsimp(x**y*x**z*y**z, combine='all')
x**(y + z)*y**z
>>> powsimp(x**y*x**z*y**z, combine='exp')
x**(y + z)*y**z
>>> powsimp(x**y*x**z*y**z, combine='base', force=True)
x**y*(x*y)**z
>>> powsimp(x**z*x**y*n**z*n**y, combine='all', force=True)
(n*x)**(y + z)
>>> powsimp(x**z*x**y*n**z*n**y, combine='exp')
n**(y + z)*x**(y + z)
>>> powsimp(x**z*x**y*n**z*n**y, combine='base', force=True)
(n*x)**y*(n*x)**z
>>> x, y = symbols('x y', positive=True)
>>> powsimp(log(exp(x)*exp(y)))
log(exp(x)*exp(y))
>>> powsimp(log(exp(x)*exp(y)), deep=True)
x + y

如果 combine=’exp’,具有多个基的部首将被合并

>>> from sympy import sqrt
>>> x, y = symbols('x y')

两个根式通过 Mul 自动连接:

>>> a=sqrt(x*sqrt(y))
>>> a*a**3 == a**4
True

但如果该根式的整数次幂已被自动展开,那么 Mul 不会合并结果因子:

>>> a**4 # auto expands to a Mul, no longer a Pow
x**2*y
>>> _*a # so Mul doesn't combine them
x**2*y*sqrt(x*sqrt(y))
>>> powsimp(_) # but powsimp will
(x*sqrt(y))**(5/2)
>>> powsimp(x*y*a) # but won't when doing so would violate assumptions
x*y*sqrt(x*sqrt(y))