模块的基本功能

介绍

本教程试图概述SymPy中关于多项式的功能。所有代码示例假设:

>>> from sympy import *
>>> x, y, z = symbols('x,y,z')
>>> init_printing(use_unicode=False)

基本概念

多项式

给定一组符号 \((x_i)\) ,或其他合适的对象,包括数字,通过重复加法、减法和乘法从它们派生出的表达式称为 生成元 \(x_i\) 中的多项式表达式。

根据分配律,可以在加法和减法之前进行乘法运算。由此得到的生成器乘积称为*单项式*。它们通常写成 \(x_1^{\nu_1}x_2^{\nu_2}\cdots x_n^{\nu_n}\) 的形式,其中指数 \(\nu_i\) 是非负整数。通常简写为 \(x^\nu\),其中 \(x = (x_1, x_2, \ldots, x_n)\) 表示生成器族,\(\nu = (\nu_1, \nu_2, \ldots, \nu_n)\) 是指数族。

当所有具有相同指数的单项式被合并时,多项式表达式变为乘积 \(c_ u x^ u\) 的和,称为多项式的 ,其中 系数 \(c_ u\) 是整数。如果某些 \(x_i\) 是显式数字,它们被纳入系数中,不被视为生成元。这样的系数通常是理性数、实数或复数。一些符号数,例如 pi ,可以是系数或生成元。

一个多项式表达式,如果它是不同单项式的和,则由其系数族 \((c_ u)\) 唯一确定。这样的表达式通常被称为 多项式,尽管更准确地说,这个名字在给定生成元后代表的是系数族。SymPy 默认将多项式实现为字典,其中单项式作为键,系数作为值。另一种实现方式是系数的嵌套列表。

所有以生成元 \(x_i\) 为变量的整系数多项式的集合是一个*环*,即,其元素的和、差与积仍然是同一生成元下的多项式。这个环记作 \(\mathbb{Z}[x_1, x_2, \ldots, x_n]\)\(\mathbb{Z}[(x_i)]\),并称为*以 \(x_i\) 为变量的整系数多项式环*。

更一般地,多项式的系数可以是任何交换环 \(A\) 中的元素,相应的多项式环则记作 \(A[x_1, x_2, \dots, x_n]\)。环 \(A\) 也可以是一个多项式环。在 SymPy 中,系数环被称为多项式环的 domain,并且可以通过关键字参数指定。默认情况下,它由多项式参数的系数决定。

Polynomial expressions can be transformed into polynomials by the method sympy.core.expr.Expr.as_poly:

>>> e = (x + y)*(y - 2*z)
>>> e.as_poly()
Poly(x*y - 2*x*z + y**2 - 2*y*z, x, y, z, domain='ZZ')

如果一个多项式表达式包含非整数的数字,它们被视为系数,并且系数环相应地扩展。特别是,整数除法会导致有理系数:

>>> e = (3*x/2 + y)*(z - 1)
>>> e.as_poly()
Poly(3/2*x*z - 3/2*x + y*z - y, x, y, z, domain='QQ')

符号数被视为生成器,除非它们被明确排除,在这种情况下,它们被附加到系数环中:

>>> e = (x + 2*pi)*y
>>> e.as_poly()
Poly(x*y + 2*y*pi, x, y, pi, domain='ZZ')
>>> e.as_poly(x, y)
Poly(x*y + 2*pi*y, x, y, domain='ZZ[pi]')

或者,可以通过关键字参数指定系数域:

>>> e = (x + 2*pi)*y
>>> e.as_poly(domain=ZZ[pi])
Poly(x*y + 2*pi*y, x, y, domain='ZZ[pi]')

需要注意的是,多项式环 \(\mathbb{Z}[\pi][x, y]\)\(x\)\(y\) 中,系数在 \(\mathbb{Z}[\pi]\) 中,在数学上等价于 \(\mathbb{Z}[\pi, x, y]\),只是它们的实现方式不同。

如果一个表达式包含生成器的函数,而不是它们的正整数幂,这些将被解释为新的生成器:

>>> e = x*sin(y) - y
>>> e.as_poly()
Poly(x*(sin(y)) - y, x, y, sin(y), domain='ZZ')

由于 \(y\)\(\sin(y)\) 在代数上是独立的,它们都可以作为多项式中的生成元出现。然而,多项式表达式不得包含生成元的负幂:

>>> e = x - 1/x
>>> e.as_poly()
Poly(x - (1/x), x, 1/x, domain='ZZ')

重要的是要认识到,生成器 \(x\)\(1/x = x^{-1}\) 被视为代数上独立的变量。特别是,它们的乘积不等于1。因此,即使当前实现中不会引发错误,也应避免在分母中使用生成器。这种行为是不理想的,未来可能会发生变化。类似的问题也出现在生成器的分数次幂中。例如,\(x\)\(\sqrt x = x^{1/2}\) 不被认为是代数相关的。

如果表达式中存在代数数,可以通过设置关键字 extension 将其附加到系数环中:

>>> e = x + sqrt(2)
>>> e.as_poly()
Poly(x + (sqrt(2)), x, sqrt(2), domain='ZZ')
>>> e.as_poly(extension=True)
Poly(x + sqrt(2), x, domain='QQ<sqrt(2)>')

在默认设置 extension=False 下,\(x\)\(\sqrt 2\) 都被错误地认为是代数独立变量。在扩展域 \(\mathbb{Q}(\sqrt 2)\) 中的系数下,平方根被正确地视为代数数。当涉及代数数时,强烈建议设置 extension=True,尽管在当前实现中并未强制要求。

可除性

第四种有理运算,除法,或反向乘法,通常在环中是不可能的。如果 \(a\)\(b\) 是环 \(A\) 中的两个元素,那么可能存在环 \(A\) 中的第三个元素 \(q\) 使得 \(a = bq\)。实际上,可能存在多个这样的元素。

如果对于某些 \(q'\)\(A\) 中也有 \(a = bq'\),那么 \(b(q - q') = 0\)。因此,\(b\)\(q - q'\) 为零,或者它们都是 零因子,即乘积为零的非零元素。

整环

没有零因子的交换环被称为 整环 。大多数常见的环,如整数环、域以及整环上的多项式环,都是整环。

假设现在 \(A\) 是一个整环,并考虑其非零元素的集合 \(P\) ,它在乘法下是封闭的。如果 \(a\)\(b\)\(P\) 中,并且存在一个元素 \(q\)\(P\) 中使得 \(a = bq\) ,那么 \(q\) 是唯一的,称为 \(a\) 除以 \(b\)\(a/b\)。此外,可以说

  • \(a\) 可以被 \(b\) 整除

  • \(b\)\(a\)因数

  • \(a\)\(b\)倍数

  • \(b\)\(a\)因子

\(P\) 中的元素 \(a\)\(1\) 的除数当且仅当它在 \(A\) 中是*可逆的*,其逆为 \(a^{-1} = 1/a\)。这样的元素被称为*单位*。整数环的单位是 \(1\)\(-1\)。域上多项式环中的可逆元素是非零常数多项式。

如果 \(P\) 中的两个元素 \(a\)\(b\) 互为可除,那么商 \(a/b\) 是可逆的,其逆为 \(b/a\),或者等价地,\(b = ua\),其中 \(u\) 是一个单位。这样的元素被称为是彼此的 相伴相伴元。整数 \(n\) 的相伴元是 \(n\)\(-n\)。在一个域上的多项式环中,一个多项式的相伴元是它的常数倍。

\(P\) 的每个元素都可以被其伴随元素和单位整除。如果一个元素没有其他除数且不是单位,则它是 不可约 的。整数环中的不可约元素是素数 \(p\) 及其相反数 \(-p\)。在域中,每个非零元素都是可逆的,并且没有不可约元素。

阶乘域

在整数环中,每个非零元素都可以表示为不可约元素的乘积,并可选地乘以单位 \(\pm 1\)。此外,任何两个这样的乘积具有相同数量的不可约因子,这些因子在适当的顺序下是彼此相关的。具有这种性质的整环称为*因子分解唯一*,或*唯一分解域*。除了整数环,所有域上的多项式环也是因子分解唯一的,更一般地,任何因子分解唯一域上的多项式环也是如此。域是平凡的因子分解唯一,因为只有单位。因子分解唯一域中的不可约元素通常称为*素数*。

一组整数只有有限个公约数,其中最大的一个能被所有公约数整除。更一般地,给定一个整环中的一组非零元素 \((a_i)\),一个公约数 \(d\) 被称为这组元素的*最大公约数*,简称*gcd*,如果它是所有公约数的倍数。一个最大公约数,如果存在,通常不是唯一的;它的所有相关元素具有相同的性质。如果没有混淆的风险,它表示为 \(d = \gcd(a_1,\ldots,a_n)\)。一个*最小公倍数*,或*lcm*,定义为一组元素 \((a_i)\) 的公倍数 \(m\),它能被所有公倍数整除。它表示为 \(m = \operatorname{lcm}(a_1,\dots,a_n)\)

在因子域中,最大公约数总是存在。它们至少在原理上可以通过将家族中的每个元素分解为素数幂和一个可选单位的乘积来找到,并且对于每个素数,取在因数分解中出现的最小幂。这些素数幂的乘积就是最大公约数。最小公倍数可以通过相同的因数分解得到,即每个素数的最大幂的乘积。

欧几里得域

计算最大公约数的实用算法可以在 欧几里得域 中实现。它们是整环,可以赋予一个函数 \(w\),将非零元素分配给每个非负整数,并具有以下性质:

如果 \(a\)\(b\) 不为零,则存在 \(q\)\(r\) 满足 除法恒等式

\(a = qb + r\)

使得要么 \(r = 0\) 或者 \(w(r) < w(b)\)

整数环和域上的一元多项式环都是欧几里得域,其中 \(w(a) = |a|\) 分别对应 \(w(a) = \deg(a)\)

整数的除法恒等式在 Python 中作为内置函数 divmod 实现,该函数也可以应用于 SymPy 整数:

>>> divmod(Integer(53), Integer(7))
(7, 4)

对于多项式,SymPy 中通过函数 div() 给出了除法恒等式:

>>> f = 5*x**2 + 10*x + 3
>>> g = 2*x + 2

>>> q, r = div(f, g, domain='QQ')
>>> q
5*x   5
--- + -
 2    2
>>> r
-2
>>> (q*g + r).expand()
   2
5*x  + 10*x + 3

除法恒等式可以用来确定欧几里得域中元素的可除性。如果在除法恒等式中 \(r = 0\),那么 \(a\) 可以被 \(b\) 整除。相反,如果对于某个元素 \(c\)\(a = cb\),那么 \((c - q)b = r\)。由此可得,如果 \(w\) 具有额外的性质,则 \(c = q\)\(r = 0\)

如果 \(a\)\(b\) 均不为零,则 \(w(ab) \ge w(b)\)

这可以通过上述函数来实现。(并且总是可以通过取 \(x e 0\)\(w(xa)\) 的最小值来重新定义 \(w(a)\)。)

除法恒等式的主要应用是通过 欧几里得算法 高效计算最大公约数。它适用于欧几里得域中的两个元素。多个元素的最大公约数可以通过迭代获得。

在SymPy中,计算整数最大公约数的函数目前是 igcd():

>>> igcd(2, 4)
2
>>> igcd(5, 10, 15)
5

对于域上的单变量多项式,该函数具有其通用名称 gcd(),并且返回的多项式是首一的:

>>> f = 4*x**2 - 1
>>> g = 8*x**3 + 1
>>> gcd(f, g, domain=QQ)
x + 1/2

多项式的可除性

整数环上的单变量多项式环 \(A = \mathbb{Z}[x]\) 不是欧几里得环,但它仍然是唯一分解环。为了理解这一点,可以考虑 \(A\) 中的可除性。

\(f\)\(g\)\(A\) 中的两个非零多项式。如果 \(f\)\(A\) 中能被 \(g\) 整除,那么在有理系数多项式环 \(B = \mathbb{Q}[x]\) 中也能被 \(g\) 整除。由于 \(B\) 是欧几里得环,这可以通过除法定理来确定。

反之,假设对于 \(B\) 中的某个多项式 \(h\),有 \(f = gh\)。那么 \(f\)\(A\) 中能被 \(g\) 整除当且仅当 \(h\) 的系数是整数。要确定这种情况何时成立,需要考虑系数的可除性。

For a polynomial \(f\) in \(A\), let \(c\) be the greatest common divisor of its coefficients. Then \(f\) is divisible by the constant polynomial \(c\) in \(A\), and the quotient \(f/c= p\) is a polynomial whose coefficients are integers that have no common divisor apart from the units. Such polynomials are called primitive. A polynomial with rational coefficients can also be written as \(f = cp\), where \(c\) is a rational number and \(p\) is a primitive polynomial. The constant \(c\) is called the content of \(f\), and \(p\) is its primitive part. These components can be found by the method sympy.core.expr.Expr.as_content_primitive:

>>> f = 6*x**2 - 3*x + 9
>>> c, p = f.as_content_primitive()
>>> c, p
       2
(3, 2*x  - x + 3)
>>> f = x**2/3 - x/2 + 1
>>> c, p = f.as_content_primitive()
>>> c, p
         2
(1/6, 2*x  - 3*x + 6)

\(f\), \(f'\) 为内容分别为 \(c\), \(c'\) 和本原部分分别为 \(p\), \(p'\) 的多项式。则 \(ff' = (cc')(pp')\),其中乘积 \(pp'\) 根据 高斯引理 是本原的。由此可得

多项式乘积的内容是它们内容的乘积,乘积的原始部分是原始部分的乘积。

回到环 \(\mathbb{Z}[x]\) 中的可除性,假设 \(f\)\(g\) 是两个具有整数系数的多项式,使得在 \(\mathbb{Q}[x]\) 中的除法恒等式产生等式 \(f = gh\),其中 \(h\) 是具有有理系数的多项式。那么 \(f\) 的内容等于 \(g\) 的内容乘以 \(h\) 的内容。由于 \(h\) 具有整数系数当且仅当其内容是整数,我们得到以下准则:

\(f\) 在环 \(\mathbb{Z}[x]\) 中能被 \(g\) 整除当且仅当

  1. \(\mathbb{Q}[x]\) 中,\(f\) 可以被 \(g\) 整除,并且

  2. \(\mathbb{Z}\) 中,\(f\) 的内容可以被 \(g\) 的内容整除。

如果 \(f = cp\)\(\mathbb{Z}[x]\) 中不可约,那么 \(c\)\(p\) 必须是单位。如果 \(p\) 不是单位,它也必须在 \(\mathbb{Q}[x]\) 中不可约。因为如果它是两个多项式的乘积,它也是它们原始部分的乘积,其中之一必须是单位。因此,\(\mathbb{Z}[x]\) 中有两种不可约元素:

  1. \(\mathbb{Z}\) 的素数,以及

  2. \(\mathbb{Q}[x]\) 中不可约的原始多项式。

由此可知,\(\mathbb{Z}[x]\) 中的每个多项式都是不可约元素的乘积。只需分别对其内容和本原部分进行因式分解即可。这些乘积本质上是唯一的;因此 \(\mathbb{Z}[x]\) 也是因子分解的。

另一个重要结果是,在 \(\mathbb{Z}[x]\) 中的两个多项式的最大公约数可以通过分别对它们在欧几里得域 \(\mathbb{Z}\)\(\mathbb{Q}[x]\) 中的内容和本原部分应用欧几里得算法来高效地找到。这在 SymPy 中也有实现:

>>> f = 4*x**2 - 1
>>> g = 8*x**3 + 1
>>> gcd(f, g)
2*x + 1
>>> gcd(6*f, 3*g)
6*x + 3

基本功能

这些函数提供了处理以 SymPy 表达式形式存在的多项式的不同算法,如符号、求和等。

Division

函数 div() 提供了多项式的带余除法。也就是说,对于多项式 fg,它计算 qr,使得 \(f = g \cdot q + r\)\(\deg(r) < \deg(q)\)。对于系数在域中的单变量多项式,例如有理数,qr 是唯一这样定义的:

>>> f = 5*x**2 + 10*x + 3
>>> g = 2*x + 2

>>> q, r = div(f, g, domain='QQ')
>>> q
5*x   5
--- + -
 2    2
>>> r
-2
>>> (q*g + r).expand()
   2
5*x  + 10*x + 3

如你所见,q 有一个非整数系数。如果你想只在整数系数的多项式环中进行除法,你可以指定一个额外的参数:

>>> q, r = div(f, g, domain='ZZ')
>>> q
0
>>> r
   2
5*x  + 10*x + 3

但请注意,这个环不再是欧几里得的,余数的次数不一定小于 f 的次数。因为 2 不除 5,\(2 x\) 不除 \(5 x^2\),即使次数较小。但:

>>> g = 5*x + 1

>>> q, r = div(f, g, domain='ZZ')
>>> q
x
>>> r
9*x + 3
>>> (q*g + r).expand()
   2
5*x  + 10*x + 3

这也适用于具有多个变量的多项式:

>>> f = x*y + y*z
>>> g = 3*x + 3*z

>>> q, r = div(f, g, domain='QQ')
>>> q
y
-
3
>>> r
0

在最后的例子中,假设所有三个变量 xyz 都是多项式的变量。但如果你有一些不相关的常数作为系数,你可以明确指定变量:

>>> a, b, c = symbols('a,b,c')
>>> f = a*x**2 + b*x + c
>>> g = 3*x + 2
>>> q, r = div(f, g, domain='QQ')
>>> q
a*x   2*a   b
--- - --- + -
 3     9    3

>>> r
4*a   2*b
--- - --- + c
 9     3

GCD 和 LCM

在除法中,还包括计算最大公约数和最小公倍数。

当多项式具有整数系数时,内容的最大公约数也会被考虑:

>>> f = (12*x + 12)*x
>>> g = 16*x**2
>>> gcd(f, g)
4*x

但如果多项式有有理系数,则返回的多项式是首一的:

>>> f = 3*x**2/2
>>> g = 9*x/4
>>> gcd(f, g)
x

它也适用于多个变量。在这种情况下,默认情况下,变量按字母顺序排列,这会影响前导系数:

>>> f = x*y/2 + y**2
>>> g = 3*x + 6*y

>>> gcd(f, g)
x + 2*y

lcm 与 gcd 相关联,可以通过另一个来计算:

>>> f = x*y**2 + x**2*y
>>> g = x**2*y**2
>>> gcd(f, g)
x*y
>>> lcm(f, g)
 3  2    2  3
x *y  + x *y
>>> (f*g).expand()
 4  3    3  4
x *y  + x *y
>>> (gcd(f, g, x, y)*lcm(f, g, x, y)).expand()
 4  3    3  4
x *y  + x *y

无平方因子分解

单变量多项式的无平方分解是所有因子(不一定是不可约的)的乘积,这些因子的次数为1、2等。:

>>> f = 2*x**2 + 5*x**3 + 4*x**4 + x**5

>>> sqf_list(f)
                   2
(1, [(x + 2, 1), (x  + x, 2)])

>>> sqf(f)
                2
        / 2    \
(x + 2)*\x  + x/

因式分解

此函数提供具有有理系数的一元和多元多项式的因式分解:

>>> factor(x**4/2 + 5*x**3/12 - x**2/3)
 2
x *(2*x - 1)*(3*x + 4)
----------------------
          12

>>> factor(x**2 + 4*x*y + 4*y**2)
         2
(x + 2*y)

Groebner 基

Buchberger 算法已实现,支持多种单项式顺序:

>>> groebner([x**2 + 1, y**4*x + x**3], x, y, order='lex')
             /[ 2       4    ]                            \
GroebnerBasis\[x  + 1, y  - 1], x, y, domain=ZZ, order=lex/


>>> groebner([x**2 + 1, y**4*x + x**3, x*y*z**3], x, y, z, order='grevlex')
             /[ 4       3   2    ]                                   \
GroebnerBasis\[y  - 1, z , x  + 1], x, y, z, domain=ZZ, order=grevlex/

解方程

我们有(不完整)的方法来找到多项式的复数根甚至符号根,并解决一些多项式方程组:

>>> from sympy import roots, solve_poly_system

>>> solve(x**3 + 2*x + 3, x)
           ____          ____
     1   \/ 11 *I  1   \/ 11 *I
[-1, - - --------, - + --------]
     2      2      2      2

>>> p = Symbol('p')
>>> q = Symbol('q')

>>> solve(x**2 + p*x + q, x)
          __________           __________
         /  2                 /  2
   p   \/  p  - 4*q     p   \/  p  - 4*q
[- - - -------------, - - + -------------]
   2         2          2         2

>>> solve_poly_system([y - x, x - 5], x, y)
[(5, 5)]

>>> solve_poly_system([y**2 - x**3 + 1, y*x], x, y)
                                   ___                 ___
                             1   \/ 3 *I         1   \/ 3 *I
[(0, -I), (0, I), (1, 0), (- - - -------, 0), (- - + -------, 0)]
                             2      2            2      2