SymPy 1.13 中文文档(五十)
AGCA - 代数几何和交换代数模块
简介
代数几何是两种地中海文化理念的融合。它是阿拉伯科学中求解方程轻松计算的超越,与希腊的位置和形状艺术的叠加。这种编织最初在欧洲土地上产生,并在国际时尚的影响下不断完善。代数几何研究几何可信和代数可能之间的微妙平衡。每当这数学跷跷板的一边超过另一边时,人们立即失去兴趣并寻找更激动人心的娱乐。
—George R. Kempf(1944 – 2002)
代数几何通过代数方法(有时反之亦然)研究几何问题。虽然这是一个相当古老的主题,但当今理解的代数几何在很大程度上是 20 世纪的发展。在如 Riemann 和 Dedekind 的思想基础上,人们意识到多项式方程组的解集(称为代数变量)的性质与在该变量上的多项式函数集的行为(称为坐标环)之间存在密切联系。
在许多几何学科中,我们可以区分局部和全局的问题(及其方法)。代数几何中的局部研究基本上等同于研究某些环、它们的理想和模。后一主题也称为交换代数。它是代数几何学家的基本局部工具集,类似于微分分析是微分几何学家的局部工具集。
有关交换代数的良好概念介绍可以参考[Atiyah69]。更注重计算的介绍,以及该模块中大多数算法所依赖的工作,可以参考[Greuel2008]。
该模块的最终目标是允许在经典情况下(在一个域上)和更现代的算术情况下表达和解决局部和全局的几何问题。然而,到目前为止,几何功能还没有实现。当前,该模块仅提供关于域上的计算交换代数的工具。
所有代码示例假定
>>> from sympy import *
>>> x, y, z = symbols('x,y,z')
>>> init_printing(use_unicode=True)
参考资料
在本节中,我们记录了 AGCA 模块的使用方法。为了方便读者,我们插入了一些定义、例子和解释。
基础环
在交换代数中,几乎所有计算都是相对于“基础环”进行的。(例如,当询问一个理想的问题时,基础环是理想的子集。)原则上,所有“多项式域”都可以用作基础环。然而,实际上,只有在域上的多项式环及其各种局部化和商环中实现了有用的功能。
正如下面的示例所示,创建你感兴趣的对象的最便捷方法是从基地领域中构建它们,然后使用各种方法从旧对象中创建新对象。例如,在有理数域(\mathbb{Q})上,在原点处创建尖点立方体(y² = x³)的局部环:
>>> lr = QQ.old_poly_ring(x, y, order="ilex") / [y**2 - x**3]
>>> lr
ℚ[x, y, order=ilex]
───────────────────
╱ 3 2╲
╲- x + y ╱
注意如何使用 Python 列表表示法作为表达理想的捷径。您可以使用convert方法将普通的 sympy 对象转换为 AGCA 模块理解的对象(尽管在许多情况下,这将自动完成 - 例如,列表被自动转换为理想,并在此过程中,符号(x)和(y)被自动转换为其他表示)。例如:
>>> X, Y = lr.convert(x), lr.convert(y) ; X
╱ 3 2╲
x + ╲- x + y ╱
>>> x**3 == y**2
False
>>> X**3 == Y**2
True
当不需要局部化时,可以使用更数学化的符号。例如,让我们创建三维仿射空间(\mathbb{A}³)的坐标环:
>>> ar = QQ.old_poly_ring(x, y, z); ar
ℚ[x, y, z]
更多细节,请参考以下类文档。注意,作为域的基础环是 AGCA 模块与其他多项式模块之间的重要重叠点。所有域都在多项式参考中有详细文档,因此我们在此仅展示一个摘要版本,包含最相关 AGCA 模块的方法。
class sympy.polys.domains.ring.Ring
表示一个环域。
free_module(rank)
生成自己的等级rank自由模块。
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).free_module(2)
QQ[x]**2
ideal(*gens)
生成一个self的理想。
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).ideal(x**2)
<x**2>
quotient_ring(e)
形成self的商环。
这里e可以是一个理想或一个可迭代对象。
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).quotient_ring(QQ.old_poly_ring(x).ideal(x**2))
QQ[x]/<x**2>
>>> QQ.old_poly_ring(x).quotient_ring([x**2])
QQ[x]/<x**2>
为此重载了除法运算符:
>>> QQ.old_poly_ring(x)/[x**2]
QQ[x]/<x**2>
sympy.polys.domains.polynomialring.PolynomialRing(domain_or_ring, symbols=None, order=None)
表示多变量多项式环的类。
class sympy.polys.domains.quotientring.QuotientRing(ring, ideal)
表示(交换)商环的类。
通常不应该手动实例化它,而是应该使用基础环的构造函数进行构造。
>>> from sympy.abc import x
>>> from sympy import QQ
>>> I = QQ.old_poly_ring(x).ideal(x**3 + 1)
>>> QQ.old_poly_ring(x).quotient_ring(I)
QQ[x]/<x**3 + 1>
还有更简短的版本:
>>> QQ.old_poly_ring(x)/I
QQ[x]/<x**3 + 1>
>>> QQ.old_poly_ring(x)/[x**3 + 1]
QQ[x]/<x**3 + 1>
属性:
-
环 - 基础环
-
base_ideal - 用于形成商的理想。
模、理想及其基本性质
设(A)是一个环。一个(A)-模是一个集合(M),加上两个二元操作(+: M \times M \to M)和(\times: R \times M \to M),称为加法和标量乘法。这些操作需要满足某些公理,详见例如[Atiyah69]。这样,模是向量空间((A)为域)和阿贝尔群((A = \mathbb{Z}))的直接推广。(A)-模(M)的子模是集合(N \subset M),使得二元操作限制在(N)上,并且(N)成为具有这些操作的(A)-模。
环(A)本身具有一个自然的(A)-模结构,其中模中的加法和乘法与环中的加法和乘法一致。这个(A)-模也被写作(A)。(A)的一个(A)-子模被称为(A)的理想。在代数几何中,理想非常自然地出现。更一般的模可以看作是技术上方便的“活动空间”,超越了仅仅讨论理想。
如果(M),(N)是(A)-模块,则(M \times N)具有自然的(分量方式的)(A)-模块结构。类似地,更多组分的笛卡尔积上有(A)-模块结构。 (对于类别倾向者:带有此(A)-模块结构的有限多个(A)-模块的笛卡尔积,在所有(A)-模块的类别中是有限的双积。 对于无限多个组件,它是直积(但无限直和必须以不同方式构造)。)通常,(A)-模块(M)的重复积记为(M, M², M³ \ldots),或者对于任意指数集(I)为(M^I)。
如果(A)-模块(M)称为自由,则它等价于(A)-模块(A^I),对于某个(不一定有限的)指数集(I)(关于同构定义的定义,请参见下一节)。(I)的基数称为(M)的秩;可以证明这是良定义的。通常情况下,AGCA 模块只与有限秩的自由模块及其他密切相关的模块一起使用。创建模块的最简单方法是使用它们由对象的成员方法构成。例如,让我们在上面创建的(\mathbb{A}²)的坐标环上创建一个秩为 4 的自由模块,以及一个子模块:
>>> F = ar.free_module(4) ; F
4
ℚ[x, y, z]
>>> S = F.submodule([1, x, x**2, x**3], [0, 1, 0, y]) ; S
╱⎡ 2 3⎤ ╲
╲⎣1, x, x , x ⎦, [0, 1, 0, y]╱
注意 Python 列表可以用作模块元素(向量)的快捷表示法。通常,convert方法可用于将 sympy/python 对象转换为内部 AGCA 表示(参见下面的详细参考)。
这里是模块、自由模块和子模块的详细文档:
class sympy.polys.agca.modules.Module(ring)
模块的抽象基类。
不要实例化 - 而是使用显式构造函数:
>>> from sympy import QQ
>>> from sympy.abc import x
>>> QQ.old_poly_ring(x).free_module(2)
QQ[x]**2
属性:
-
dtype - 元素类型
-
ring - 包含环
未实现的方法:
-
子模块
-
商模块
-
is_zero
-
is_submodule
-
multiply_ideal
子类中的convert方法可能需要更改。
contains(elem)
如果elem是该模块的元素,则返回 True。
convert(elem, M=None)
将elem转换为该模块的内部表示。
如果M不是None,则应该是包含它的模块。
identity_hom()
返回self上的单位同态。
is_submodule(other)
如果other是self的子模块,则返回 True。
is_zero()
如果self是零模块,则返回 True。
multiply_ideal(other)
将self乘以理想other。
quotient_module(other)
生成商模块。
submodule(*gens)
生成一个子模块。
subset(other)
如果other是self的子集,则返回 True。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> F.subset([(1, x), (x, 2)])
True
>>> F.subset([(1/x, x), (x, 2)])
False
class sympy.polys.agca.modules.FreeModule(ring, rank)
自由模块的抽象基类。
附加属性:
- rank - 自由模块的秩
未实现的方法:
- 子模块
basis()
返回一组基元素。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).free_module(3).basis()
([1, 0, 0], [0, 1, 0], [0, 0, 1])
convert(elem, M=None)
将elem转换为内部表示。
每当涉及不在内部表示中的元素时,将隐式调用此方法。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> F.convert([1, 0])
[1, 0]
dtype
别名为FreeModuleElement
identity_hom()
返回self上的单位同态。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).free_module(2).identity_hom()
Matrix([
[1, 0], : QQ[x]**2 -> QQ[x]**2
[0, 1]])
is_submodule(other)
如果other是self的子模块,则返回 True。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> M = F.submodule([2, x])
>>> F.is_submodule(F)
True
>>> F.is_submodule(M)
True
>>> M.is_submodule(F)
False
is_zero()
如果self是零模块,则返回 True。
(如果像这个实现假设的那样,系数环不是零环,那么这等价于秩为零。)
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).free_module(0).is_zero()
True
>>> QQ.old_poly_ring(x).free_module(1).is_zero()
False
multiply_ideal(other)
将self乘以理想other。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> I = QQ.old_poly_ring(x).ideal(x)
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> F.multiply_ideal(I)
<[x, 0], [0, x]>
quotient_module(submodule)
返回一个商模块。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ.old_poly_ring(x).free_module(2)
>>> M.quotient_module(M.submodule([1, x], [x, 2]))
QQ[x]**2/<[1, x], [x, 2]>
或者更简洁地,使用重载的除法运算符:
>>> QQ.old_poly_ring(x).free_module(2) / [[1, x], [x, 2]]
QQ[x]**2/<[1, x], [x, 2]>
class sympy.polys.agca.modules.FreeModuleElement(module, data)
自由模块的元素。数据存储为元组。
class sympy.polys.agca.modules.SubModule(gens, container)
子模块的基类。
属性:
-
container - 包含模块
-
gens - generators (subset of containing module)
-
rank - 包含模块的秩
未实现的方法:
-
_contains
-
_syzygies
-
_in_terms_of_generators
-
_intersect
-
_module_quotient
可能需要在子类中更改的方法:
- reduce_element
convert(elem, M=None)
将elem转换为内部表示。
大多数情况下会隐式调用。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ.old_poly_ring(x).free_module(2).submodule([1, x])
>>> M.convert([2, 2*x])
[2, 2*x]
identity_hom()
返回在self上的恒同同态。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).free_module(2).submodule([x, x]).identity_hom()
Matrix([
[1, 0], : <[x, x]> -> <[x, x]>
[0, 1]])
in_terms_of_generators(e)
用生成元表达元素e。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> M = F.submodule([1, 0], [1, 1])
>>> M.in_terms_of_generators([x, x**2])
[DMP_Python([-1, 1, 0], QQ), DMP_Python([1, 0, 0], QQ)]
inclusion_hom()
返回表示self包含映射的同态。
换句话说,从self到self.container的自然映射。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).free_module(2).submodule([x, x]).inclusion_hom()
Matrix([
[1, 0], : <[x, x]> -> QQ[x]**2
[0, 1]])
intersect(other, **options)
返回self与子模块other的交集。
示例
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x, y).free_module(2)
>>> F.submodule([x, x]).intersect(F.submodule([y, y]))
<[x*y, x*y]>
一些实现允许传递更多选项。目前,只有一个实现是relations=True,在这种情况下,函数将返回三元组(res, rela, relb),其中res是交集模块,rela和relb是系数向量列表,表示res的生成元在self的生成元(rela)和other的生成元(relb)中的表达。
>>> F.submodule([x, x]).intersect(F.submodule([y, y]), relations=True)
(<[x*y, x*y]>, [(DMP_Python([[1, 0]], QQ),)], [(DMP_Python([[1], []], QQ),)])
上述结果说明:交集模块由单一元素((-xy, -xy) = -y (x, x) = -x (y, y))生成,其中((x, x))和((y, y))分别是被交集的两个模块的唯一生成元。
is_full_module()
如果self是整个自由模块,则返回 True。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> F.submodule([x, 1]).is_full_module()
False
>>> F.submodule([1, 1], [1, 2]).is_full_module()
True
is_submodule(other)
如果other是self的子模块,则返回 True。
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> M = F.submodule([2, x])
>>> N = M.submodule([2*x, x**2])
>>> M.is_submodule(M)
True
>>> M.is_submodule(N)
True
>>> N.is_submodule(M)
False
is_zero()
如果self是零模块,则返回 True。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> F.submodule([x, 1]).is_zero()
False
>>> F.submodule([0, 0]).is_zero()
True
module_quotient(other, **options)
返回self除以子模块other的模块商。
换句话说,如果self是模块(M),other是(N),那么返回理想({f \in R | fN \subset M})。
示例
>>> from sympy import QQ
>>> from sympy.abc import x, y
>>> F = QQ.old_poly_ring(x, y).free_module(2)
>>> S = F.submodule([x*y, x*y])
>>> T = F.submodule([x, x])
>>> S.module_quotient(T)
<y>
一些实现允许传递更多选项。目前,只有一个实现是relations=True,只有在other是主理想时才能传递。在这种情况下,函数将返回一对(res, rel),其中res是理想,rel是系数向量列表,表示理想的生成元在self的生成元中乘以other的生成元的表达。
>>> S.module_quotient(T, relations=True)
(<y>, [[DMP_Python([[1]], QQ)]])
这意味着商理想由单一元素(y)生成,并且(y (x, x) = 1 (xy, xy)),((x, x))和((xy, xy))分别是(T)和(S)的生成元。
multiply_ideal(I)
将self乘以理想I。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> I = QQ.old_poly_ring(x).ideal(x**2)
>>> M = QQ.old_poly_ring(x).free_module(2).submodule([1, 1])
>>> I*M
<[x**2, x**2]>
quotient_module(other, **opts)
返回一个商模块。
这等同于取包含模块的商模块的子模块。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> S1 = F.submodule([x, 1])
>>> S2 = F.submodule([x**2, x])
>>> S1.quotient_module(S2)
<[x, 1] + <[x**2, x]>>
或者更简洁地,使用重载的除法运算符:
>>> F.submodule([x, 1]) / [(x**2, x)]
<[x, 1] + <[x**2, x]>>
reduce_element(x)
将环中的元素x模除理想self。
这里的“reduce”没有特定的含义,它可以返回唯一的正常形式,稍微简化表达式,或者什么也不做。
submodule(*gens)
生成一个子模块。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ.old_poly_ring(x).free_module(2).submodule([x, 1])
>>> M.submodule([x**2, x])
<[x**2, x]>
syzygy_module(**opts)
计算self生成元的 syzygy 模块。
假设(M)由环(R)上的(f_1, \ldots, f_n)生成。考虑同态(\phi: R^n \to M),定义为将((r_1, \ldots, r_n))映射至(r_1 f_1 + \cdots + r_n f_n)。syzygy 模块定义为(\phi)的核。
示例
syzygy 模块为零当且仅当生成元自由生成自由子模块:
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).free_module(2).submodule([1, 0], [1, 1]).syzygy_module().is_zero()
True
一个稍微有趣的例子:
>>> M = QQ.old_poly_ring(x, y).free_module(2).submodule([x, 2*x], [y, 2*y])
>>> S = QQ.old_poly_ring(x, y).free_module(2).submodule([y, -x])
>>> M.syzygy_module() == S
True
union(other)
返回由self和other的并集生成的模块。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(1)
>>> M = F.submodule([x**2 + x]) # <x(x+1)>
>>> N = F.submodule([x**2 - 1]) # <(x-1)(x+1)>
>>> M.union(N) == F.submodule([x+1])
True
理想的创建与模块非常相似。例如,让我们验证节点三次曲线在原点确实是奇异的:
>>> I = lr.ideal(x, y)
>>> I == lr.ideal(x)
False
>>> I == lr.ideal(y)
False
我们在这里使用了这样一个事实,即一条曲线在某一点非奇异当且仅当局部环的极大理想是主理想,并且在这种情况下至少一个(x)和(y)必须是生成元。
这是理想类的详细文档。请注意,大多数关于理想属性(素性等)的方法尚未实现。
class sympy.polys.agca.ideals.Ideal(ring)
理想的抽象基类。
不要实例化 - 在环类中使用显式构造函数代替:
>>> from sympy import QQ
>>> from sympy.abc import x
>>> QQ.old_poly_ring(x).ideal(x+1)
<x + 1>
属性
- 环 - 此理想所属的环
未实现的方法:
-
_contains_elem
-
_contains_ideal
-
_quotient
-
_intersect
-
_union
-
_product
-
是否整个环
-
是否为零
-
是否为素理想,极大理想,主理想,根理想
-
是否为主理想
-
高度,深度
-
根理想
子类中可能应该重写的方法:
- reduce_element
contains(elem)
如果elem是这个理想的一个元素则返回 True。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).ideal(x+1, x-1).contains(3)
True
>>> QQ.old_poly_ring(x).ideal(x**2, x**3).contains(x)
False
depth()
计算self的深度。
height()
计算self的高度。
intersect(J)
计算self与理想 J 的交集。
示例
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> R = QQ.old_poly_ring(x, y)
>>> R.ideal(x).intersect(R.ideal(y))
<x*y>
is_maximal()
如果self是极大理想则返回 True。
is_primary()
如果self是主理想则返回 True。
is_prime()
如果self是素理想则返回 True。
is_principal()
如果self是主理想则返回 True。
is_radical()
如果self是根理想则返回 True。
is_whole_ring()
如果self是整环则返回 True。
is_zero()
如果self是零理想则返回 True。
product(J)
计算self和J的理想积。
即,计算由(xy)的乘积生成的理想,其中(x)是self的元素,(y \in J)。
示例
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> QQ.old_poly_ring(x, y).ideal(x).product(QQ.old_poly_ring(x, y).ideal(y))
<x*y>
quotient(J, **opts)
计算self除以J的理想商。
即,如果self是理想(I),计算集合(I : J = {x \in R | xJ \subset I })。
示例
>>> from sympy.abc import x, y
>>> from sympy import QQ
>>> R = QQ.old_poly_ring(x, y)
>>> R.ideal(x*y).quotient(R.ideal(x))
<y>
radical()
计算self的根理想。
reduce_element(x)
将我们环的元素x减少至理想self。
这里的“reduce”没有具体的含义:它可以返回一个唯一的标准形式,简化表达式,或者什么也不做。
saturate(J)
计算self通过J的理想饱和。
即,如果self是理想(I),计算集合(I : J^\infty = {x \in R | xJ^n \subset I \text{ for some } n})。
subset(other)
如果other是self的子集则返回 True。
这里的other可能是一个理想。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> I = QQ.old_poly_ring(x).ideal(x+1)
>>> I.subset([x**2 - 1, x**2 + 2*x + 1])
True
>>> I.subset([x**2 + 1, x + 1])
False
>>> I.subset(QQ.old_poly_ring(x).ideal(x**2 - 1))
True
union(J)
计算由self和 J 的并集生成的理想。
示例
>>> from sympy.abc import x
>>> from sympy import QQ
>>> QQ.old_poly_ring(x).ideal(x**2 - 1).union(QQ.old_poly_ring(x).ideal((x+1)**2)) == QQ.old_poly_ring(x).ideal(x+1)
True
如果 (M) 是 (A)-模块且 (N) 是 (A)-子模块,我们可以定义 (M) 的两个元素 (x) 和 (y) 等价,如果 (x - y \in N)。等价类的集合写作 (M/N),并且具有自然的 (A)-模块结构。这被称为 (M) 关于 (N) 的商模。如果 (K) 是包含 (N) 的 (M) 的子模块,则 (K/N) 以一种自然的方式是 (M/N) 的子模块。这样的模块称为子商模。这是商模和子商模的文档:
class sympy.polys.agca.modules.QuotientModule(ring, base, submodule)
商模类。
不要直接实例化这个类。对于子商模,请参见 SubQuotientModule 类。
属性:
-
base - 我们是其商的基模块
-
killed_module - 用于形成商的子模块
-
基模的秩
convert(elem, M=None)
将 elem 转换为内部表示。
每当计算涉及不在内部表示中的元素时,都会隐式调用此方法。
例子
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)]
>>> F.convert([1, 0])
[1, 0] + <[1, 2], [1, x]>
dtype
别名 QuotientModuleElement
identity_hom()
返回在 self 上的恒同同态。
例子
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)]
>>> M.identity_hom()
Matrix([
[1, 0], : QQ[x]**2/<[1, 2], [1, x]> -> QQ[x]**2/<[1, 2], [1, x]>
[0, 1]])
is_submodule(other)
如果 other 是 self 的子模块,则返回 True。
例子
>>> from sympy.abc import x
>>> from sympy import QQ
>>> Q = QQ.old_poly_ring(x).free_module(2) / [(x, x)]
>>> S = Q.submodule([1, 0])
>>> Q.is_submodule(S)
True
>>> S.is_submodule(Q)
False
is_zero()
如果 self 是零模块,则返回 True。
如果基模块与被杀死的子模块相同,则会发生这种情况。
例子
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> (F/[(1, 0)]).is_zero()
False
>>> (F/[(1, 0), (0, 1)]).is_zero()
True
quotient_hom()
返回到 self 的商同态。
即,返回表示从 self.base 到 self 的自然映射的同态。
例子
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = QQ.old_poly_ring(x).free_module(2) / [(1, 2), (1, x)]
>>> M.quotient_hom()
Matrix([
[1, 0], : QQ[x]**2 -> QQ[x]**2/<[1, 2], [1, x]>
[0, 1]])
submodule(*gens, **opts)
生成一个子模块。
这与取基模块的子模块的商是相同的。
例子
>>> from sympy.abc import x
>>> from sympy import QQ
>>> Q = QQ.old_poly_ring(x).free_module(2) / [(x, x)]
>>> Q.submodule([x, 0])
<[x, 0] + <[x, x]>>
class sympy.polys.agca.modules.QuotientModuleElement(module, data)
商模的元素。
eq(d1, d2)
相等比较。
class sympy.polys.agca.modules.SubQuotientModule(gens, container, **opts)
商模的子模块。
等价地,子模的商模。
不要直接实例化它,而是使用子模块或商模块构造方法:
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> S = F.submodule([1, 0], [1, x])
>>> Q = F/[(1, 0)]
>>> S/[(1, 0)] == Q.submodule([5, x])
True
属性:
-
base - 我们是其商的基模块
-
killed_module - 用于形成商的子模块
is_full_module()
如果 self 是整个自由模块,则返回 True。
例子
>>> from sympy.abc import x
>>> from sympy import QQ
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> F.submodule([x, 1]).is_full_module()
False
>>> F.submodule([1, 1], [1, 2]).is_full_module()
True
quotient_hom()
返回到 self 的商同态。
即,返回从 self.base 到 self 的自然映射。
例子
>>> from sympy.abc import x
>>> from sympy import QQ
>>> M = (QQ.old_poly_ring(x).free_module(2) / [(1, x)]).submodule([1, 0])
>>> M.quotient_hom()
Matrix([
[1, 0], : <[1, 0], [1, x]> -> <[1, 0] + <[1, x]>, [1, x] + <[1, x]>>
[0, 1]])
模同态和 Syzygies
设 (M) 和 (N) 是 (A)-模块。满足各种明显性质的映射 (f: M \to N)(见[Atiyah69])被称为 (A)-模同态。在这种情况下,(M) 被称为定义域,N 被称为陪域。集合 ({x \in M | f(x) = 0}) 称为核 (ker(f)),而集合 ({f(x) | x \in M}) 称为像 (im(f))。核是 (M) 的子模,像是 (N) 的子模。同态 (f) 是单射当且仅当 (ker(f) = 0),是满射当且仅当 (im(f) = N)。双射同态称为同构。等价地,(ker(f) = 0) 并且 (im(f) = N)。(AGCA 模块中目前没有特殊名称的相关概念是cokernel,(coker(f) = N/im(f))。)
现在假设( M )是一个( A )-模。如果存在一个满同态( A^n \to M )(对于某些( n )),则( M )被称为有限生成的。如果选择这样的同态( f ),则( A^n )的标准基的像称为( M )的生成元。模( \ker(f) )称为关于生成元的syzygy 模块。如果一个模是有限生成的且有一个有限生成的 syzygy 模块,则称其为有限呈现模。有限呈现模的类别实质上是我们可以有意义地计算的最大类别。
一个重要的定理是,对于我们考虑的所有环,有限生成模的所有子模都是有限生成的,因此有限生成模和有限呈现模是相同的。
虽然最初看起来可能有些抽象,但是 syzygies 的概念实际上非常具有计算性。这是因为存在(相当简单的)算法来计算它们,而更一般的问题(核,交集等)通常可以简化为 syzygy 的计算。
让我们先谈一下 AGCA 模块中同态的定义。首先假设( f: M \to N )是( A )-模的任意同态。如果( K )是( M )的子模,则( f )自然地定义了一个新的同态( g: K \to N )(通过( g(x) = f(x) )),称为( f )在( K )上的限制。如果现在( K )包含在( f )的核中,则此外( f )还自然地定义了一个同态( g: M/K \to N )(同上公式!),我们称( f ) 降至 ( M/K )。类似地,如果( L )是( N )的子模,则存在一个自然的同态( g: M \to N/L ),我们称( g ) 通过 ( f )。最后,如果现在( L )包含( f )的像,则有一个自然的同态( g: M \to L )(同上定义),我们称( g )是通过限制像域从( f )获得的。还要注意这四个操作中的每一个都是可逆的,也就是说,给定( g ),可以总是(非唯一地)找到( f ),使得( g )是通过上述方式从( f )获得的。
注意,所有在 AGCA 中实现的模块都是通过连续取子模和商模从自由模块获得的。因此,为了解释如何在上述情况下定义任意模块之间的同态,我们只需解释如何定义自由模块的同态。但是,基本上通过自由模块的定义,从自由模块( A^n )到任何模块( M )的同态恰好等同于给出( M )的( n )个元素(标准基的像),而从自由模块( A^m )给出元素恰好等同于给出( A )的( m )个元素。因此,自由模块( A^n \to A^m )的同态可以通过矩阵指定,与向量空间的情况完全类似。
类Homomorphism的函数restrict_domain等可以用于执行上述操作,并且自由模的同态映射原则上可以手动实例化。由于这些操作如此常见,因此有一个方便的函数homomorphism来通过上述方法定义任意模块之间的同态映射。这基本上是用户创建同态映射的唯一方法。
sympy.polys.agca.homomorphisms.homomorphism(domain, codomain, matrix)
创建一个同态映射对象。
此函数尝试通过矩阵matrix从domain到codomain构建同态映射。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> R = QQ.old_poly_ring(x)
>>> T = R.free_module(2)
如果domain是由(e_1, \ldots, e_n)生成的自由模,则matrix应该是一个 n 元可迭代对象((b_1, \ldots, b_n)),其中(b_i)是codomain的元素。构造的同态映射是将(e_i)发送到(b_i)的唯一同态映射。
>>> F = R.free_module(2)
>>> h = homomorphism(F, T, [[1, x], [x**2, 0]])
>>> h
Matrix([
[1, x**2], : QQ[x]**2 -> QQ[x]**2
[x, 0]])
>>> h([1, 0])
[1, x]
>>> h([0, 1])
[x**2, 0]
>>> h([1, 1])
[x**2 + 1, x]
如果domain是自由模的子模,则matrix确定从包含自由模到codomain的同态映射,并且通过限制到domain获得返回的同态映射。
>>> S = F.submodule([1, 0], [0, x])
>>> homomorphism(S, T, [[1, x], [x**2, 0]])
Matrix([
[1, x**2], : <[1, 0], [0, x]> -> QQ[x]**2
[x, 0]])
如果domain是一个(子)商模 (N/K),那么matrix确定了从(N)到codomain的同态映射。如果核包含(K),则此同态映射下降到domain并返回;否则会引发异常。
>>> homomorphism(S/[(1, 0)], T, [0, [x**2, 0]])
Matrix([
[0, x**2], : <[1, 0] + <[1, 0]>, [0, x] + <[1, 0]>, [1, 0] + <[1, 0]>> -> QQ[x]**2
[0, 0]])
>>> homomorphism(S/[(0, x)], T, [0, [x**2, 0]])
Traceback (most recent call last):
...
ValueError: kernel <[1, 0], [0, 0]> must contain sm, got <[0,x]>
最后,这里是实际同态映射类的详细参考:
class sympy.polys.agca.homomorphisms.ModuleHomomorphism(domain, codomain)
模块同态映射的抽象基类。不要实例化。
而不是,使用homomorphism函数:
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> homomorphism(F, F, [[1, 0], [0, 1]])
Matrix([
[1, 0], : QQ[x]**2 -> QQ[x]**2
[0, 1]])
属性:
-
环 - 我们考虑的模块的环
-
域 - 域模
-
codomain - 积模
-
_ker - 缓存的核
-
_img - 缓存的图像
未实现的方法:
-
_ 核
-
_ 图像
-
_restrict_domain
-
_restrict_codomain
-
_quotient_domain
-
_quotient_codomain
-
_apply
-
_mul_scalar
-
_compose
-
_add
image()
计算self的图像。
也就是说,如果self是同态映射(\phi: M \to N),那么计算(im(\phi) = {\phi(x) | x \in M })。这是(N)的子模块。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> homomorphism(F, F, [[1, 0], [x, 0]]).image() == F.submodule([1, 0])
True
is_injective()
如果self是单射,则返回 True。
也就是说,检查域的元素是否映射到相同的积模元素。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h.is_injective()
False
>>> h.quotient_domain(h.kernel()).is_injective()
True
is_isomorphism()
如果self是同构,则返回 True。
也就是说,检查每个域的元素是否具有精确一个原像。等效地,self既是单射又是满射。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h = h.restrict_codomain(h.image())
>>> h.is_isomorphism()
False
>>> h.quotient_domain(h.kernel()).is_isomorphism()
True
is_surjective()
如果self是满射,则返回 True。
也就是说,检查每个域的元素是否至少有一个原像。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h.is_surjective()
False
>>> h.restrict_codomain(h.image()).is_surjective()
True
is_zero()
如果self是零态射,则返回 True。
也就是说,检查域的每个元素是否在自身下映射为零。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h.is_zero()
False
>>> h.restrict_domain(F.submodule()).is_zero()
True
>>> h.quotient_codomain(h.image()).is_zero()
True
kernel()
计算self的核。
也就是说,如果self是同态映射(\phi: M \to N),那么计算(ker(\phi) = {x \in M | \phi(x) = 0})。这是(M)的子模块。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> homomorphism(F, F, [[1, 0], [x, 0]]).kernel()
<[x, -1]>
quotient_codomain(sm)
用codomain/sm替换self返回。
这里sm必须是self.codomain的子模块。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2
[0, 0]])
>>> h.quotient_codomain(F.submodule([1, 1]))
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2/<[1, 1]>
[0, 0]])
这与在左侧使用商映射进行组合相同:
>>> (F/[(1, 1)]).quotient_hom() * h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2/<[1, 1]>
[0, 0]])
quotient_domain(sm)
用domain/sm替换self返回。
这里sm必须是self.kernel()的子模块。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2
[0, 0]])
>>> h.quotient_domain(F.submodule([-x, 1]))
Matrix([
[1, x], : QQ[x]**2/<[-x, 1]> -> QQ[x]**2
[0, 0]])
restrict_codomain(sm)
返回self,其余值被限制为sm。
这里sm必须是包含图像的self.codomain的子模。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2
[0, 0]])
>>> h.restrict_codomain(F.submodule([1, 0]))
Matrix([
[1, x], : QQ[x]**2 -> <[1, 0]>
[0, 0]])
restrict_domain(sm)
返回self,其定义被限制为sm。
这里sm必须是self.domain的子模。
示例
>>> from sympy import QQ
>>> from sympy.abc import x
>>> from sympy.polys.agca import homomorphism
>>> F = QQ.old_poly_ring(x).free_module(2)
>>> h = homomorphism(F, F, [[1, 0], [x, 0]])
>>> h
Matrix([
[1, x], : QQ[x]**2 -> QQ[x]**2
[0, 0]])
>>> h.restrict_domain(F.submodule([1, 0]))
Matrix([
[1, x], : <[1, 0]> -> QQ[x]**2
[0, 0]])
这与仅在右侧与子模包含组合相同:
>>> h * F.submodule([1, 0]).inclusion_hom()
Matrix([
[1, x], : <[1, 0]> -> QQ[x]**2
[0, 0]])
有限扩展
设(A)为(交换)环,(B)为(A)的扩展环。若(B)中的元素(t)是(A)上(B)的生成器,则(B)中的所有元素都可以用(t)的系数为(A)的多项式表示。如果且仅如果(t)满足没有非平凡多项式关系,则表示是唯一的,此时(B)可以被视为(A)上的(单变量)多项式环。
一般情况下,以(t)为根的多项式形成一个非零理想。在实践中最重要的情况是由单一单项式生成的理想。如果(t)满足这样的多项式关系,则其最高幂(t^n)可以写成较低幂的线性组合。归纳地,所有更高的幂(t)也有这样的表示。因此,较低的幂(t^i)((i = 0, \dots, n-1))形成(B)的一组基础,然后称为(A)的有限扩展,或更准确地说是由单个元素(t)生成的单基有限扩展。
class sympy.polys.agca.extensions.MonogenicFiniteExtension(mod)
由整数元素生成的有限扩展。
生成器由从参数mod导出的单变量单项式定义。
更短的别名是FiniteExtension。
示例
二次整数环(\mathbb{Z}[\sqrt2]):
>>> from sympy import Symbol, Poly
>>> from sympy.polys.agca.extensions import FiniteExtension
>>> x = Symbol('x')
>>> R = FiniteExtension(Poly(x**2 - 2)); R
ZZ[x]/(x**2 - 2)
>>> R.rank
2
>>> R(1 + x)*(3 - 2*x)
x - 1
有限域(GF(5³)),由原始多项式(x³ + x² + 2)(在(\mathbb{Z}_5)上)定义。
>>> F = FiniteExtension(Poly(x**3 + x**2 + 2, modulus=5)); F
GF(5)[x]/(x**3 + x**2 + 2)
>>> F.basis
(1, x, x**2)
>>> F(x + 3)/(x**2 + 2)
-2*x**2 + x + 2
椭圆曲线的函数域:
>>> t = Symbol('t')
>>> FiniteExtension(Poly(t**2 - x**3 - x + 1, t, field=True))
ZZ(x)[t]/(t**2 - x**3 - x + 1)
dtype
别名为ExtensionElement
class sympy.polys.agca.extensions.ExtensionElement(rep, ext)
有限扩展的元素。
一个在扩展ext的模modulus的DMP类的唯一多项式rep表示。它由mod的表示和modulus的表示构成。
inverse()
乘法逆元。
引发:
NotInvertible
如果元素是零除数。
介绍 poly 模块的域
此页面介绍了 SymPy 的 sympy.polys 模块中使用的“域”的概念。重点是直接介绍如何使用这些域以及理解它们如何作为 Poly 类的内部部分使用。这是一个相对高级的主题,因此建议阅读 模块的基本功能 以获取更简介的对 Poly 类和 sympy.polys 模块的理解。有关域类的参考文档在 Poly Domains 的参考文档 中。利用这些域的内部功能在 多项式操作模块的内部 中有所记录。
域是什么?
对于大多数用户来说,域只在打印输出Poly时才真正显著:
>>> from sympy import Symbol, Poly
>>> x = Symbol('x')
>>> Poly(x**2 + x)
Poly(x**2 + x, x, domain='ZZ')
>>> Poly(x**2 + x/2)
Poly(x**2 + 1/2*x, x, domain='QQ')
我们在这里看到一个 Poly 具有域 ZZ,代表整数,另一个具有域 QQ,代表有理数。这些指示了多项式系数的“域”来源。
从高层次来看,域表示形式概念,如整数集(\mathbb{Z})或有理数(\mathbb{Q})。这里的“域”一词是对数学概念“整环”的引用。
在内部,域对应于多项式所对应的不同计算实现和表达方式。Poly 对象本身有一个内部表示,作为系数的list和表示这些系数实现的 domain 属性:
>>> p = Poly(x**2 + x/2)
>>> p
Poly(x**2 + 1/2*x, x, domain='QQ')
>>> p.domain
QQ
>>> p.rep
DMP_Python([1, 1/2, 0], QQ)
>>> p.rep.rep
[1, 1/2, 0]
>>> type(p.rep.rep[0])
<class 'sympy.external.pythonmpq.PythonMPQ'>
这里的域是 QQ,它表示域系统中有理数的实现。Poly 实例本身具有 Poly.domain 属性 QQ,然后是一个 PythonMPQ 系数列表,其中 PythonMPQ 是实现 QQ 域元素的类。系数列表 [1, 1/2, 0] 给出了多项式表达式 (1)*x**2 + (1/2)*x + (0) 的标准化低级表示。
本页面介绍了在 SymPy 中定义的不同域,它们的实现方式以及如何使用它们。它介绍了如何直接使用域和域元素,并解释了它们作为 Poly 对象的内部使用方式。这些信息对于 SymPy 的开发比对 sympy.polys 模块的用户更相关。
以符号表示表达式
数学表达式可以以多种不同的方式符号化表示。多项式域的目的是为不同类别的表达式提供合适的实现。本节考虑了数学表达式符号化表示的基本方法:“树形结构”、“密集多项式”和“稀疏多项式”。
树形结构表示
符号表达式的最一般表示形式是作为 树形结构,这是大多数普通 SymPy 表达式的表示形式,它们是 Expr 的实例(Basic 的子类)。我们可以使用 srepr() 函数来查看这种表示:
>>> from sympy import Symbol, srepr
>>> x = Symbol('x')
>>> e = 1 + 1/(2 + x**2)
>>> e
1 + 1/(x**2 + 2)
>>> print(srepr(e))
Add(Integer(1), Pow(Add(Pow(Symbol('x'), Integer(2)), Integer(2)), Integer(-1)))
在这里,表达式 e 被表示为一个 Add 节点,它有两个子节点 1 和 1/(x**2 + 2)。子节点 1 被表示为一个 Integer,而另一个子节点被表示为一个 Pow,其基数为 x**2 + 2,指数为 1。然后 x**2 + 2 被表示为一个 Add,其子节点为 x**2 和 2,依此类推。通过这种方式,表达式被表示为一个树,其中内部节点是操作,如 Add、Mul、Pow 等,而叶节点是原子表达式类型,如 Integer 和 Symbol。更多关于这种表示方法的信息,请参见高级表达式操作。
树形表示是 SymPy 中 Expr 架构的核心。它是一种高度灵活的表示方法,可以表示非常广泛的表达式。它还可以以不同方式表示等价表达式,例如:
>>> e = x*(x + 1)
>>> e
x*(x + 1)
>>> e.expand()
x**2 + x
这两个表达式虽然等价,但具有不同的树形表示:
>>> print(srepr(e))
Mul(Symbol('x'), Add(Symbol('x'), Integer(1)))
>>> print(srepr(e.expand()))
Add(Pow(Symbol('x'), Integer(2)), Symbol('x'))
能够以不同方式表示相同的表达式既是一种优势也是一种弱点。能够将表达式转换为不同形式以应对不同的任务是很有用的,但是具有非唯一表示使得很难判断两个表达式是否等价,而这对于许多计算算法来说是非常重要的。最重要的任务是能够判断一个表达式是否等于零,在一般情况下这是不可判定的(参见理查森定理),但在许多重要的特殊情况下是可判定的。
DUP 表示
限制允许表达式的特殊情况可以实现更高效的符号表示。正如我们之前看到的Poly可以将多项式表示为系数列表。这意味着像x**4 + x + 1这样的表达式可以简单地表示为[1, 0, 0, 1, 1]。多项式表达式的这种系数列表表示称为“密集单变量多项式”(DUP)表示。在该表示内部,乘法、加法和关键的零测试算法比对应的树形表示要高效得多。我们可以通过查看Poly实例的rep.rep属性来看到这种表示:
>>> p = Poly(x**4 + x + 1)
>>> p.rep.rep
[1, 0, 0, 1, 1]
在 DUP 表示中,不可能用不同的方式表示相同的表达式。x*(x + 1)和x**2 + x之间没有区别,因为它们都是[1, 1, 0]。这意味着比较两个表达式很容易:它们只有在所有系数都相等时才相等。零测试特别简单:多项式仅在所有系数都为零时为零(当然,我们需要对系数本身进行简单的零测试)。
我们可以比树形表示法更有效地创建在 DUP 表示法上操作的函数。实际上,许多标准 sympy 表达式的操作实际上是通过转换为多项式表示,然后执行计算来完成的。一个例子是factor()函数:
>>> from sympy import factor
>>> e = 2*x**3 + 10*x**2 + 16*x + 8
>>> e
2*x**3 + 10*x**2 + 16*x + 8
>>> factor(e)
2*(x + 1)*(x + 2)**2
在内部,factor()将把表达式从树形表示转换为 DUP 表示,然后使用函数dup_factor_list:
>>> from sympy import ZZ
>>> from sympy.polys.factortools import dup_factor_list
>>> p = [ZZ(2), ZZ(10), ZZ(16), ZZ(8)]
>>> p
[2, 10, 16, 8]
>>> dup_factor_list(p, ZZ)
(2, [([1, 1], 1), ([1, 2], 2)])
有许多以dup_*命名的函数用于操作 DUP 表示,这些函数在多项式操作模块内部有详细文档。还有以dmp_*前缀命名的函数用于操作多元多项式。### DMP 表示
多元多项式(多个变量的多项式)可以表示为系数本身是多项式的多项式。例如,x**2*y + x**2 + x*y + y + 1可以表示为以x为多项式的多项式,其中系数本身是y的多项式,即:(y + 1)*x**2 + (y)*x + (y+1)。由于我们可以用系数列表表示一个多项式,多元多项式可以用系数列表的列表表示:
>>> from sympy import symbols
>>> x, y = symbols('x, y')
>>> p = Poly(x**2*y + x**2 + x*y + y + 1)
>>> p
Poly(x**2*y + x**2 + x*y + y + 1, x, y, domain='ZZ')
>>> p.rep.rep
[[1, 1], [1, 0], [1, 1]]
这种列表的(列表的…)系数表示称为“密集多元多项式”(DMP)表示。### 稀疏多项式表示
我们可以使用字典来代替列表,将非零单项式项映射到它们的系数。这被称为“稀疏多项式”表示。我们可以通过as_dict()方法看到它的实现:
>>> Poly(7*x**20 + 8*x + 9).rep.rep
[7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 9]
>>> Poly(7*x**20 + 8*x + 9).as_dict()
{(0,): 9, (1,): 8, (20,): 7}
此字典的键是x的幂的指数,值是系数,因此例如7*x**20在字典中变为(20,): 7。键是一个元组,因此在多变量情况下,如4*x**2*y**3可以表示为(2, 3): 4。稀疏表示可以更高效,因为它避免了存储和操作零系数的需求。在具有大量生成器(变量)的情况下,稠密表示尤其低效,最好使用稀疏表示:
>>> from sympy import prod
>>> gens = symbols('x:10')
>>> gens
(x0, x1, x2, x3, x4, x5, x6, x7, x8, x9)
>>> p = Poly(prod(gens))
>>> p
Poly(x0*x1*x2*x3*x4*x5*x6*x7*x8*x9, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, domain='ZZ')
>>> p.rep.rep
[[[[[[[[[[1, 0], []], [[]]], [[[]]]], [[[[]]]]], [[[[[]]]]]], [[[[[[]]]]]]], [[[[[[[]]]]]]]], [[[[[[[[]]]]]]]]], [[[[[[[[[]]]]]]]]]]
>>> p.as_dict()
{(1, 1, 1, 1, 1, 1, 1, 1, 1, 1): 1}
在上一个输出中显示的字典表示将单项式(表示为幂的元组,如(1, 1, 1, ...)即x0**1 * x1**1, ...)映射到系数1。与 DMP 表示相比,我们拥有一个更为扁平化的数据结构:它是一个只有一个键和值的dict。对于处理稀疏表示的算法,可能比这个特定例子中的密集算法更为高效。
SymPy 的多项式模块实现了基于密集和稀疏表示的多项式表达式。还有其他实现不同特殊类别表达式的实现,可以作为这些多项式的系数。本页的其余部分将讨论这些表示形式及其使用方法。
域的基本用法
几个域已经预定义并可以直接使用,例如 ZZ 和 QQ,它们分别代表整数环(\mathbb{Z})和有理数域(\mathbb{Q})。Domain对象用于构造元素,这些元素可以用于普通的算术运算。
>>> from sympy import ZZ
>>> z1 = ZZ(2)
>>> z1
2
>>> z1 + z1
4
>>> type(z1)
<class 'int'>
>>> z1 in ZZ
True
对于任何域的元素,基本运算+、-和*(加法、减法和乘法)都能工作并生成新的域元素。使用/(Python 的“真除法”运算符)进行除法不一定适用于所有域元素,除非已知该域是一个域。例如,两个 ZZ 中的元素相除可能会得到一个不是 ZZ 元素的float:
>>> z1 / z1
1.0
>>> type(z1 / z1)
<class 'float'>
>>> ZZ.is_Field
False
对于非域的情况,/的行为在不同的域的基础类型的不同实现中也可能不同。例如,使用SYMPY_GROUND_TYPES=flint,在 ZZ 中除法运算会引发错误,而不是返回浮点数:
>>> z1 / z1
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for /: 'flint._flint.fmpz' and 'flint._flint.fmpz'
大多数表示非域环的域允许使用 Python 的地板除法//和模除运算%。例如,使用 ZZ:
>>> z1 // z1
1
>>> z1 % z1
0
QQ 域表示有理数的域并允许除法:
>>> from sympy import QQ
>>> q1 = QQ(1, 2)
>>> q1
1/2
>>> q2 = QQ(2, 3)
>>> q2
2/3
>>> q1 / q2
3/4
>>> type(q1)
<class 'sympy.external.pythonmpq.PythonMPQ'>
通常,预计将与任意域中的元素一起使用的代码不应使用除法运算符/,//和%。只有运算符+,-,*和**(带非负整数指数)应假定可用于任意域元素。所有其他操作应作为Domain对象的函数访问:
>>> ZZ.quo(ZZ(5), ZZ(3)) # 5 // 3
1
>>> ZZ.rem(ZZ(5), ZZ(3)) # 5 % 3
2
>>> ZZ.div(ZZ(5), ZZ(3)) # divmod(5, 3)
(1, 2)
>>> QQ.div(QQ(5), QQ(3))
(5/3, 0)
exquo()函数用于计算精确商。这类似于a / b,但预计除法是精确的(没有余数),否则将引发错误:
>>> QQ.exquo(QQ(5), QQ(3))
5/3
>>> ZZ.exquo(ZZ(4), ZZ(2))
2
>>> ZZ.exquo(ZZ(5), ZZ(3))
Traceback (most recent call last):
...
ExactQuotientFailed: 3 does not divide 5 in ZZ
一般而言,不能保证域元素的确切方法和属性超出基本算术运算。不应假定例如 ZZ 总是类型为int。如果安装了gmpy或gmpy2,则使用mpz或mpq类型而不是 ZZ 和 QQ 的纯 Python 实现:
>>> from sympy import ZZ, QQ
>>> ZZ(2)
mpz(2)
>>> QQ(2, 3)
mpq(2, 3)
mpz类型在大整数操作中比 Python 标准int类型更快,尽管对于较小整数,差异不那么显著。代表有理数的mpq类型在 C 中实现,而不是 Python,并且比未安装 gmpy 时使用的 QQ 的纯 Python 实现快几倍。
通常可以从域的dtype属性检查元素的 Python 类型。安装 gmpy 时,ZZ 的 dtype 为(mpz),这不是实际类型,不能与(isinstance)一起使用。因此,可以使用of_type()方法检查对象是否为dtype的元素:
>>> z = ZZ(2)
>>> type(z)
<class 'int'>
>>> ZZ.dtype
<class 'int'>
>>> ZZ.of_type(z)
True
域元素与 sympy 表达式
请注意,域元素与普通 sympy 表达式不是相同类型,后者是Expr的子类,如Integer。普通 sympy 表达式通过sympify()函数创建:
>>> from sympy import sympify
>>> z1_sympy = sympify(2) # Normal sympy object
>>> z1_sympy
2
>>> type(z1_sympy)
<class 'sympy.core.numbers.Integer'>
>>> from sympy import Expr
>>> isinstance(z1_sympy, Expr)
True
在使用域时,重要的是不要将 sympy 表达式与域元素混合使用,即使在简单情况下有时可能会有效。每个域对象都有方法 to_sympy() 和 from_sympy() 用于在 sympy 表达式和域元素之间进行转换:
>>> z_sympy = sympify(2)
>>> z_zz = ZZ.from_sympy(z_sympy)
>>> z_zz
2
>>> type(z_sympy)
<class 'sympy.core.numbers.Integer'>
>>> type(z_zz)
<class 'int'>
>>> ZZ.to_sympy(z_zz)
2
>>> type(ZZ.to_sympy(z_zz))
<class 'sympy.core.numbers.Integer'>
任何特定的域只能表示一些 sympy 表达式,因此如果表达式不能在域中表示,则转换将失败:
>>> from sympy import sqrt
>>> e = sqrt(2)
>>> e
sqrt(2)
>>> ZZ.from_sympy(e)
Traceback (most recent call last):
...
CoercionFailed: expected an integer, got sqrt(2)
我们已经看到,在某些情况下,我们可以使用域对象本身作为构造函数,例如 QQ(2)。只要给定的参数对于域的 dtype 是有效的,通常这样做是有效的。尽管在交互式会话和演示中使用这种方法很方便,但通常最好使用 from_sympy() 方法从 sympy 表达式(或可以 sympify 为 sympy 表达式的对象)构造域元素。
在域中工作时,重要的是不要将域元素与其他 Python 类型(如 int、float)以及标准的 sympy Expr 表达式混合使用。需要注意的是,某些 Python 操作将会在不经意间执行此操作。例如,sum 函数将使用常规的 int 值零,因此 sum([a, b]) 实际上会被计算为 (0 + a) + b,其中 0 的类型是 int。
每个域至少是一个环,如果不是一个域,那么它保证具有两个特定元素,分别对应于 (1) 和 (0)。域对象提供这些作为属性的域元素 one 和 zero。这些对于像 Python 的 sum 函数非常有用,该函数允许提供一个替代对象作为“零”:
>>> ZZ.one
1
>>> ZZ.zero
0
>>> sum([ZZ(1), ZZ(2)]) # don't do this (even it sometimes works)
3
>>> sum([ZZ(1), ZZ(2)], ZZ.zero) # provide the zero from the domain
3
在域中执行计算的标准模式如下:
-
从 sympy
Expr实例开始,表示表达式。 -
选择一个能够表示表达式的适当域。
-
使用
from_sympy()将所有表达式转换为域元素。 -
使用域元素执行计算。
-
使用
to_sympy()将转换回Expr。
这里是sum函数的实现示例,说明了这些步骤并对一些整数求和,但是使用了域元素而不是标准的 sympy 表达式进行计算:
def sum_domain(expressions_sympy):
"""Sum sympy expressions but performing calculations in domain ZZ"""
# Convert to domain
expressions_dom = [ZZ.from_sympy(e) for e in expressions_sympy]
# Perform calculations in the domain
result_dom = ZZ.zero
for e_dom in expressions_dom:
result_dom += e_dom
# Convert the result back to Expr
result_sympy = ZZ.to_sympy(result_dom)
return result_sympy
高斯整数和高斯有理数
到目前为止我们见过的两个示例域是 ZZ 和 QQ,分别表示整数和有理数。还有其他简单的域,如 ZZ_I 和 QQ_I,分别表示高斯整数和高斯有理数。高斯整数是形如 (a\sqrt{-1} + b) 的数,其中 (a) 和 (b) 是整数。高斯有理数类似地定义,不同之处在于 (a) 和 (b) 可以是有理数。我们可以像这样使用高斯域:
>>> from sympy import ZZ_I, QQ_I, I
>>> z = ZZ_I.from_sympy(1 + 2*I)
>>> z
(1 + 2*I)
>>> z**2
(-3 + 4*I)
注意与树形表示中计算方式的对比,需要使用 expand() 来获得简化形式:
>>> from sympy import expand, I
>>> z = 1 + 2*I
>>> z**2
(1 + 2*I)**2
>>> expand(z**2)
-3 + 4*I
ZZ_I 和 QQ_I 域由类 GaussianIntegerRing 和 GaussianRationalField 实现,它们的元素分别由 GaussianInteger 和 GaussianRational 表示。在 ZZ_I 或 QQ_I 的内部表示中,一个元素简单地是 (a, b),其中 a 和 b 分别是 ZZ 或 QQ 的元素。域 ZZ_I 是一个与 ZZ 类似属性的环,而 QQ_I 则像 QQ 一样是一个域:
>>> ZZ.is_Field
False
>>> QQ.is_Field
True
>>> ZZ_I.is_Field
False
>>> QQ_I.is_Field
True
由于 QQ_I 是一个域,非零元素总是可以进行除法,而在 ZZ_I 中我们有最大公约数(GCD)的重要概念:
>>> e1 = QQ_I.from_sympy(1+I)
>>> e2 = QQ_I.from_sympy(2-I/2)
>>> e1/e2
(6/17 + 10/17*I)
>>> ZZ_I.gcd(ZZ_I(5), ZZ_I.from_sympy(1+2*I))
(1 + 2*I)
有限域
到目前为止,我们看到了域 ZZ, QQ, ZZ_I 和 QQ_I。还有代表 有限域 的领域,尽管这些的实现尚不完整。可以用 FF 或 GF 构建一个素数阶有限域 GF(p)。可以用 GF(p) 构建一个素数阶有限域 (p) 的领域:
>>> from sympy import GF
>>> K = GF(5)
>>> two = K(2)
>>> two
2 mod 5
>>> two ** 2A
4 mod 5
>>> two ** 3
3 mod 5
FF 也是 GF 的别名(分别代表“有限域”和“伽罗瓦域”)。它们是等效的,FF(n) 和 GF(n) 都将创建一个领域,该领域是 FiniteField 的实例。关联的领域元素将是 PythonFiniteField 或 GMPYFiniteField 的实例,具体取决于是否安装了 gmpy。
有限阶域 (p^n) (其中 (n \ne 1))尚未实现。可以使用例如 GF(6) 或 GF(9),但得到的领域不是一个域。它只是模 6 或 9 的整数集,因此具有零除数和非可逆元素:
>>> K = GF(6)
>>> K(3) * K(2)
0 mod 6
很高兴看到对素数幂次有限域的适当实现,但目前在 SymPy 中尚不可用(欢迎贡献!)。
实数域和复数域
域 RR 和 CC 在数学上分别对应于 实数 和 复数,(\mathbb{R}) 和 (\mathbb{C})。这些的实现使用浮点数运算。在实践中,这意味着这些领域用于表示包含浮点数的表达式。域 RR 的元素是 RealElement 类的实例,并具有用于表示 mpmath 中浮点数的 mpf 元组。域 CC 的元素是 ComplexElement 类的实例,并具有表示实部和虚部的 mpf 元组对。有关浮点数的表示方式,请参阅 mpmath 文档:
>>> from sympy import RR, CC
>>> xr = RR(3)
>>> xr
3.0
>>> xr._mpf_
(0, 3, 0, 2)
>>> zc = CC(3+1j)
>>> zc
(3.0 + 1.0j)
>>> zc._mpc_
((0, 3, 0, 2), (0, 1, 0, 1))
在这些域中使用近似浮点算术会带来所有通常的问题。sympy.polys模块中的许多算法基本上是为精确算术而设计的,因此使用这些域可能会有问题:
>>> RR('0.1') + RR('0.2') == RR('0.3')
False
由于这些是使用mpmath(一个多精度库)实现的,因此可以使用不同的工作精度创建不同的域。默认域 RR 和 CC 使用 53 位二进制精度,类似于标准的双精度浮点数,对应于大约 15 位十进制数字:
>>> from sympy.polys.domains.realfield import RealField
>>> RR.precision
53
>>> RR.dps
15
>>> RR(1) / RR(3)
0.333333333333333
>>> RR100 = RealField(100)
>>> RR100.precision
100
>>> RR100.dps
29
>>> RR100(1) / RR100(3)
0.33333333333333333333333333333
然而,这里的实现存在一个 bug,实际上所有RealElement都使用了全局精度设置。这意味着刚刚创建的RR100已经改变了全局精度,我们需要在这里的 doctest 中恢复它:
>>> RR(1) / RR(3) # wrong result!
0.33333333333333333333333333333
>>> dummy = RealField(53) # hack to restore precision
>>> RR(1) / RR(3) # restored
0.333333333333333
(显然,这应该被修复!)
代数数域
有理数的代数扩张称为代数数域,在 sympy 中实现为 QQ。这些的自然语法应该类似于QQ(sqrt(2)),但是QQ()已经被重载为 QQ 元素的构造函数。这些域可以使用algebraic_field()方法创建,例如QQ.algebraic_field(sqrt(2))。所得到的域将是AlgebraicField的一个实例,其元素为ANP的实例。
对于这些的打印支持较少,但我们可以使用to_sympy()来利用对应的Expr打印支持:
>>> K = QQ.algebraic_field(sqrt(2))
>>> K
QQ<sqrt(2)>
>>> b = K.one + K.from_sympy(sqrt(2))
>>> b
ANP([1, 1], [1, 0, -2], QQ)
>>> K.to_sympy(b)
1 + sqrt(2)
>>> b ** 2
ANP([2, 3], [1, 0, -2], QQ)
>>> K.to_sympy(b**2)
2*sqrt(2) + 3
原始打印显示立即显示了元素的内部表示,作为ANP实例。域(\mathbb{Q}(\sqrt{2}))由形如(a\sqrt{2}+b)的数构成,其中(a)和(b)是有理数。因此,这个域中的每个数可以表示为一对(a, b),它们是 QQ 元素的元素。域元素将这两个元素存储在列表中,并且还存储扩展元素(\sqrt{2})的最小多项式的列表表示。有一个 sympy 函数minpoly()可以计算有理数上任意代数表达式的最小多项式:
>>> from sympy import minpoly, Symbol
>>> x = Symbol('x')
>>> minpoly(sqrt(2), x)
x**2 - 2
在作为系数列表的稠密多项式表示中,该多项式如上所示为[1, 0, -2],如ANP中QQ<sqrt(2)>元素的显示。
也可以创建具有多个生成元的代数数域,例如(\mathbb{Q}(\sqrt{2},\sqrt{3})):
>>> K = QQ.algebraic_field(sqrt(2), sqrt(3))
>>> K
QQ<sqrt(2) + sqrt(3)>
>>> sqrt2 = K.from_sympy(sqrt(2))
>>> sqrt3 = K.from_sympy(sqrt(3))
>>> p = (K.one + sqrt2) * (K.one + sqrt3)
>>> p
ANP([1/2, 1, -3/2], [1, 0, -10, 0, 1], QQ)
>>> K.to_sympy(p)
1 + sqrt(2) + sqrt(3) + sqrt(6)
>>> K.to_sympy(p**2)
4*sqrt(6) + 6*sqrt(3) + 8*sqrt(2) + 12
这里的代数扩展(\mathbb{Q}(\sqrt{2},\sqrt{3}))被转换为(同构的)(\mathbb{Q}(\sqrt{2}+\sqrt{3})),具有单个生成元(\sqrt{2}+\sqrt{3})。由于原始元定理,总是可以找到类似这样的单一生成元。有一个 sympy 函数primitive_element(),可以计算扩展的原始元的最小多项式:
>>> from sympy import primitive_element, minpoly
>>> e = primitive_element([sqrt(2), sqrt(3)], x)
>>> e[0]
x**4 - 10*x**2 + 1
>>> e[0].subs(x, sqrt(2) + sqrt(3)).expand()
0
最小多项式x**4 - 10*x**2 + 1具有密集列表表示[1, 0, -10, 0, 1],如上述ANP输出。原始元定理的含义是,所有代数数域都可以表示为有某个最小多项式的有理数扩展。在代数数域上的计算只需利用最小多项式,这使得可以计算所有算术操作,还可以进行如多项式因式分解等更高级别的操作。
多项式环域
多项式环也实现了表示像K[x]这样的环,其中K域中的系数是生成元x的多项式:
>>> from sympy import ZZ, symbols
>>> x = symbols('x')
>>> K = ZZ[x]
>>> K
ZZ[x]
>>> x_dom = K(x)
>>> x_dom + K.one
x + 1
所有前述讨论的操作都适用于多项式环的元素:
>>> p = x_dom + K.one
>>> p
x + 1
>>> p + p
2*x + 2
>>> p - p
0
>>> p * p
x**2 + 2*x + 1
>>> p ** 3
x**3 + 3*x**2 + 3*x + 1
>>> K.exquo(x_dom**2 - K.one, x_dom - K.one)
x + 1
K[x]的元素的内部表示不同于普通 sympy(Expr)表达式的表示方式。任何表达式的Expr表示都是作为一个树,例如:
>>> from sympy import srepr
>>> K = ZZ[x]
>>> p_expr = x**2 + 2*x + 1
>>> p_expr
x**2 + 2*x + 1
>>> srepr(p_expr)
"Add(Pow(Symbol('x'), Integer(2)), Mul(Integer(2), Symbol('x')), Integer(1))"
这里表达式是一个树,顶部节点是Add,其子节点是Pow等。这种树形表示使得可以以不同的方式表示等价表达式,例如:
>>> x = symbols('x')
>>> p_expr = x*(x + 1) + x
>>> p_expr
x*(x + 1) + x
>>> p_expr.expand()
x**2 + 2*x
对比之下,域ZZ[x]仅表示多项式,并通过简单地存储展开多项式的非零系数来实现(“稀疏”多项式表示)。特别地,ZZ[x]的元素被表示为 Python dict。它们的类型是PolyElement,它是dict的一个子类。将其转换为普通的dict显示内部表示:
>>> x = symbols('x')
>>> K = ZZ[x]
>>> x_dom = K(x)
>>> p_dom = K(3)*x_dom**2 + K(2)*x_dom + K(7)
>>> p_dom
3*x**2 + 2*x + 7
>>> dict(p_dom)
{(0,): 7, (1,): 2, (2,): 3}
这种内部形式使得无法表示未展开的乘法,因此ZZ[x]元素的任何乘法都将被展开:
>>> x = symbols('x')
>>> K = ZZ[x]
>>> x_dom = K(x)
>>> p_expr = x * (x + 1) + x
>>> p_expr
x*(x + 1) + x
>>> p_dom = x_dom * (x_dom + K.one) + x_dom
>>> p_dom
x**2 + 2*x
这些相同的考虑也适用于幂次:
>>> (x + 1) ** 2
(x + 1)**2
>>> (x_dom + K.one) ** 2
x**2 + 2*x + 1
我们还可以构造多变量多项式环:
>>> x, y = symbols('x, y')
>>> K = ZZ[x,y]
>>> xk = K(x)
>>> yk = K(y)
>>> xk**2*yk + xk + yk
x**2*y + x + y
也可以构造嵌套的多项式环(尽管效率较低)。环K[x][y]在形式上等价于K[x,y],尽管它们在 sympy 中的实现不同:
>>> K = ZZ[x][y]
>>> p = K(x**2 + x*y + y**2)
>>> p
y**2 + x*y + x**2
>>> dict(p)
{(0,): x**2, (1,): x, (2,): 1}
这里像x**2这样的系数也是PolyElement的实例,因此这是一个dict,其中值也是dict。完整的表示更像是:
>>> {k: dict(v) for k, v in p.items()}
{(0,): {(2,): 1}, (1,): {(1,): 1}, (2,): {(0,): 1}}
多变量环域ZZ[x,y]作为一个单一扁平化的dict有更高效的表示:
>>> K = ZZ[x,y]
>>> p = K(x**2 + x*y + y**2)
>>> p
x**2 + x*y + y**2
>>> dict(p)
{(0, 2): 1, (1, 1): 1, (2, 0): 1}
这些表示方式之间的效率差异随生成器数量的增加而增大,即ZZ[x,y,z,t,...]与ZZ[x][y][z][t]...。
旧(密集)多项式环
在上一节中,我们看到多项式环如K[x]的域表示使用了将单项式指数映射到系数的稀疏多项式表示。还有一个使用密集 DMP 表示的K[x]的旧版本。我们可以使用poly_ring()和old_poly_ring()创建这两个版本的K[x],其中语法K[x]等同于K.poly_ring(x):
>>> K1 = ZZ.poly_ring(x)
>>> K2 = ZZ.old_poly_ring(x)
>>> K1
ZZ[x]
>>> K2
ZZ[x]
>>> K1 == ZZ[x]
True
>>> K2 == ZZ[x]
False
>>> p1 = K1.from_sympy(x**2 + 1)
>>> p2 = K2.from_sympy(x**2 + 1)
>>> p1
x**2 + 1
>>> p2
DMP_Python([1, 0, 1], ZZ)
>>> type(K1)
<class 'sympy.polys.domains.polynomialring.PolynomialRing'>
>>> type(p1)
<class 'sympy.polys.rings.PolyElement'>
>>> type(K2)
<class 'sympy.polys.domains.old_polynomialring.GlobalPolynomialRing'>
>>> type(p2)
<class 'sympy.polys.polyclasses.DMP_Python'>
旧多项式环域的内部表示是DMP表示,作为(列表的)系数:
>>> repr(p2)
'DMP_Python([1, 0, 1], ZZ, ZZ[x])'
多项式的DMP表示法最显著的用途是作为Poly内部表示(本文档后面会进一步讨论)。
PolyRing vs PolynomialRing
你可能只是想在某个特定多项式环中执行计算,而不用担心为任意域实现某些功能。在这种情况下,你可以直接使用ring()函数构造环:
>>> from sympy import ring
>>> K, xr, yr = ring([x, y], ZZ)
>>> K
Polynomial ring in x, y over ZZ with lex order
>>> xr**2 - yr**2
x**2 - y**2
>>> (xr**2 - yr**2) // (xr - yr)
x + y
这里的对象K表示环,并且是PolyRing的一个实例,但不是多项式域(不是Domain子类的实例,因此不能与Poly一起使用)。这样,多项式环的实现可以独立于域系统使用。
域系统的目的是提供一个统一的接口,用于处理和转换表达式的不同表示形式。为了使PolyRing实现在这一背景下可用,PolynomialRing类是对PolyRing类的包装,提供了域系统期望的接口。这使得多项式环的此实现可用作设计用于处理来自不同域的表达式的更广泛代码库的一部分。多项式环的域是与由ring()返回的环不同的对象,尽管它们都具有相同的元素:
>>> K, xr, yr = ring([x, y], ZZ)
>>> K
Polynomial ring in x, y over ZZ with lex order
>>> K2 = ZZ[x,y]
>>> K2
ZZ[x,y]
>>> K2.ring
Polynomial ring in x, y over ZZ with lex order
>>> K2.ring == K
True
>>> K(x+y)
x + y
>>> K2(x+y)
x + y
>>> type(K(x+y))
<class 'sympy.polys.rings.PolyElement'>
>>> type(K2(x+y))
<class 'sympy.polys.rings.PolyElement'>
>>> K(x+y) == K2(x+y)
True
有理函数域
有些域被分类为域,而其他则不是。域与非域域之间的主要区别在于,在域中,总是可以用任何非零元素除以任何元素。通常可以通过get_field()方法将任何域转换为包含该域的域:
>>> from sympy import ZZ, QQ, symbols
>>> x, y = symbols('x, y')
>>> ZZ.is_Field
False
>>> QQ.is_Field
True
>>> QQ[x]
QQ[x]
>>> QQ[x].is_Field
False
>>> QQ[x].get_field()
QQ(x)
>>> QQ[x].get_field().is_Field
True
>>> QQ.frac_field(x)
QQ(x)
这引入了一个新的域类型 K(x),表示在另一个域 K 上生成器 x 的有理函数域。不可能使用 () 语法构造域 QQ(x),因此创建它的最简单方法是使用域方法 frac_field() (QQ.frac_field(x)) 或 get_field() (QQ[x].get_field())。frac_field() 方法是更直接的方法。
有理函数域 K(x) 是 RationalField 的一个实例。该域表示形式为 (p(x) / q(x)) 的函数,其中 (p) 和 (q) 是多项式。域元素表示为 K[x] 中的一对多项式:
>>> K = QQ.frac_field(x)
>>> xk = K(x)
>>> f = xk / (K.one + xk**2)
>>> f
x/(x**2 + 1)
>>> f.numer
x
>>> f.denom
x**2 + 1
>>> QQ[x].of_type(f.numer)
True
>>> QQ[x].of_type(f.denom)
True
在这个域中分子和分母之间的约分是自动进行的:
>>> p1 = xk**2 - 1
>>> p2 = xk - 1
>>> p1
x**2 - 1
>>> p2
x - 1
>>> p1 / p2
x + 1
计算这种约分可能会很慢,这使得有理函数域可能比多项式环或代数域慢。
就像多项式环的情况一样,有分数域既有新(稀疏)版本也有旧(稠密)版本。
>>> K1 = QQ.frac_field(x)
>>> K2 = QQ.old_frac_field(x)
>>> K1
QQ(x)
>>> K2
QQ(x)
>>> type(K1)
<class 'sympy.polys.domains.fractionfield.FractionField'>
>>> type(K2)
<class 'sympy.polys.domains.old_fractionfield.FractionField'>
就像多项式环的情况一样,有理函数域的实现可以独立于域系统使用:
>>> from sympy import field
>>> K, xf, yf = field([x, y], ZZ)
>>> xf / (1 - yf)
-x/(y - 1)
这里 K 是 FracField 的一个实例,而不是 RationalField,后者适用于域 ZZ(x,y)。
表达式域
最后要考虑的域是“表达式域”,也称为 EX。使用其他域无法表示的表达式始终可以使用表达式域表示。EX 的元素实际上只是 Expr 实例的包装器:
>>> from sympy import EX
>>> p = EX.from_sympy(1 + x)
>>> p
EX(x + 1)
>>> type(p)
<class 'sympy.polys.domains.expressiondomain.ExpressionDomain.Expression'>
>>> p.ex
x + 1
>>> type(p.ex)
<class 'sympy.core.add.Add'>
对于其他域,表达式的域表示通常比由Expr使用的树表示更有效。在 EX 中,内部表示是Expr,因此显然不是更有效。EX 域的目的是能够用与其他域一致的接口包装任意表达式。当找不到合适的域表示时,EX 域作为备用使用。尽管这并未提供特定的效率,但允许实现的算法在处理没有适当域表示的表达式时仍可用。
选择一个域
在上述描述的工作流程中,思路是从一些 sympy 表达式开始,选择一个域,并将所有表达式转换为该域,以便执行一些计算。显然产生的问题是如何选择适当的域来表示一些 sympy 表达式。为此,有一个函数construct_domain(),它接受一个表达式列表,并将选择一个域并将所有表达式转换为该域:
>>> from sympy import construct_domain, Integer
>>> elements_sympy = [Integer(3), Integer(2)] # elements as Expr instances
>>> elements_sympy
[3, 2]
>>> K, elements_K = construct_domain(elements_sympy)
>>> K
ZZ
>>> elements_K
[3, 2]
>>> type(elements_sympy[0])
<class 'sympy.core.numbers.Integer'>
>>> type(elements_K[0])
<class 'int'>
在这个例子中,我们看到两个整数3和2可以在域 ZZ 中表示。这些表达式已被转换为该域的元素,在这种情况下意味着int类型而不是Expr的实例。当输入可以被 sympify 时,不需要显式创建Expr实例,因此例如construct_domain([3, 2])将给出与上述相同的输出。
给定更复杂的输入,construct_domain()将选择更复杂的域:
>>> from sympy import Rational, symbols
>>> x, y = symbols('x, y')
>>> construct_domain([Rational(1, 2), Integer(3)])[0]
QQ
>>> construct_domain([2*x, 3])[0]
ZZ[x]
>>> construct_domain([x/2, 3])[0]
QQ[x]
>>> construct_domain([2/x, 3])[0]
ZZ(x)
>>> construct_domain([x, y])[0]
ZZ[x,y]
如果在输入中发现任何非整数有理数,则基础域将是 QQ 而不是 ZZ。如果输入中发现任何符号,则将创建一个PolynomialRing。如果输入中有多个符号,则还可以创建一个多变量多项式环如QQ[x,y]。如果分母中出现任何符号,则将创建一个RationalField,例如QQ(x)。
上述一些领域是域,其他则是(非域)环。在某些情境下,需要一个域领域,以便进行除法操作,为此construct_domain() 提供了一个field=True选项,即使表达式可以在非域环中表示,也将强制构造一个域领域:
>>> construct_domain([1, 2], field=True)[0]
QQ
>>> construct_domain([2*x, 3], field=True)[0]
ZZ(x)
>>> construct_domain([x/2, 3], field=True)[0]
ZZ(x)
>>> construct_domain([2/x, 3], field=True)[0]
ZZ(x)
>>> construct_domain([x, y], field=True)[0]
ZZ(x,y)
默认情况下,construct_domain() 不会构造代数扩展域,而是使用 EX 域(ExpressionDomain)。关键字参数 extension=True 可用于构造一个AlgebraicField,如果输入是无理但代数的。
>>> from sympy import sqrt
>>> construct_domain([sqrt(2)])[0]
EX
>>> construct_domain([sqrt(2)], extension=True)[0]
QQ<sqrt(2)>
>>> construct_domain([sqrt(2), sqrt(3)], extension=True)[0]
QQ<sqrt(2) + sqrt(3)>
当输入中存在代数独立的超越元素时,将构造一个PolynomialRing或RationalField,将这些超越元素视为生成器:
>>> from sympy import sin, cos
>>> construct_domain([sin(x), y])[0]
ZZ[y,sin(x)]
但是,如果存在输入不是代数独立的可能性,则该领域将是 EX:
>>> construct_domain([sin(x), cos(x)])[0]
EX
这里 sin(x) 和 cos(x) 并不是代数独立的,因为 sin(x)**2 + cos(x)**2 = 1。
在不同领域之间转换元素
在不同领域中进行的计算通常是有用的。然而,重要的是避免将领域元素与普通的 sympy 表达式和其他 Python 类型混合,同样重要的是避免将来自不同领域的元素混合。convert_from() 方法用于将一个领域的元素转换为另一个领域的元素:
>>> num_zz = ZZ(3)
>>> ZZ.of_type(num_zz)
True
>>> num_qq = QQ.convert_from(num_zz, ZZ)
>>> ZZ.of_type(num_qq)
False
>>> QQ.of_type(num_qq)
True
可以调用convert() 方法,而不需要指定源领域作为第二个参数,例如:
>>> QQ.convert(ZZ(2))
2
这是因为 convert() 可以检查 ZZ(2) 的类型,并尝试确定它是哪个域 (ZZ) 的元素。像 ZZ 和 QQ 这样的某些域被视为特殊情况,以使其工作。更复杂域的元素是 DomainElement 的子类实例,该类具有 parent() 方法,可以识别元素所属的域。例如在多项式环 ZZ[x] 中我们有:
>>> from sympy import ZZ, Symbol
>>> x = Symbol('x')
>>> K = ZZ[x]
>>> K
ZZ[x]
>>> p = K(x) + K.one
>>> p
x + 1
>>> type(p)
<class 'sympy.polys.rings.PolyElement'>
>>> p.parent()
ZZ[x]
>>> p.parent() == K
True
不过,通过将源域指定为第二个参数调用 convert_from() 更为高效:
>>> QQ.convert_from(ZZ(2), ZZ)
2
统一域
当我们想要结合来自两个不同域的元素,并对它们进行混合计算时,我们需要
-
选择一个能表示两者所有元素的新域。
-
将所有元素转换到新域。
-
在新域中执行计算。
从第 1 点中产生的关键问题是如何选择一个能够表示两个域元素的域。为此,有 unify() 方法:
>>> x1, K1 = ZZ(2), ZZ
>>> y2, K2 = QQ(3, 2), QQ
>>> K1
ZZ
>>> K2
QQ
>>> K3 = K1.unify(K2)
>>> K3
QQ
>>> x3 = K3.convert_from(x1, K1)
>>> y3 = K3.convert_from(y2, K2)
>>> x3 + y3
7/2
unify() 方法将找到一个包含两个域的公共域,在这个例子中 ZZ.unify(QQ) 得到的是 QQ,因为 ZZ 的每个元素都可以表示为 QQ 的元素。这意味着所有的输入 (x1 和 y2) 都可以转换为公共域 K3 中的元素(作为 x3 和 y3)。一旦在公共域中,我们可以安全地使用像 + 这样的算术操作。在这个例子中,一个域是另一个域的超集,我们看到 K1.unify(K2) == K2,所以实际上不需要转换 y2。总的来说,然而 K1.unify(K2) 可以给出一个既不等于 K1 也不等于 K2 的新域。
unify() 方法理解如何组合不同的多项式环域以及如何统一基础域:
>>> ZZ[x].unify(ZZ[y])
ZZ[x,y]
>>> ZZ[x,y].unify(ZZ[y])
ZZ[x,y]
>>> ZZ[x].unify(QQ)
QQ[x]
同样可以统一代数域和有理函数域:
>>> K1 = QQ.algebraic_field(sqrt(2))[x]
>>> K2 = QQ.algebraic_field(sqrt(3))[y]
>>> K1
QQ<sqrt(2)>[x]
>>> K2
QQ<sqrt(3)>[y]
>>> K1.unify(K2)
QQ<sqrt(2) + sqrt(3)>[x,y]
>>> QQ.frac_field(x).unify(ZZ[y])
ZZ(x,y)
多项式内部
现在我们可以理解 Poly 类在内部如何工作。这是 Poly 的公共接口:
>>> from sympy import Poly, symbols, ZZ
>>> x, y, z, t = symbols('x, y, z, t')
>>> p = Poly(x**2 + 1, x, domain=ZZ)
>>> p
Poly(x**2 + 1, x, domain='ZZ')
>>> p.gens
(x,)
>>> p.domain
ZZ
>>> p.all_coeffs()
[1, 0, 1]
>>> p.as_expr()
x**2 + 1
这是Poly的内部实现:
>>> d = p.rep # internal representation of Poly
>>> d
DMP_Python([1, 0, 1], ZZ)
>>> d.rep # internal representation of DMP
[1, 0, 1]
>>> type(d.rep)
<class 'list'>
>>> type(d.rep[0])
<class 'int'>
>>> d.dom
ZZ
Poly实例的内部表示是一个DMP的实例,它是旧多项式环域old_poly_ring()中用于域元素的类。它将多项式表示为系数列表,这些系数本身是域的元素,并保持对它们域的引用(在本例中是 ZZ)。
选择一个 Poly 的域。
如果没有为Poly构造器指定域,则会使用construct_domain()推断。像field=True这样的参数会传递给construct_domain():
>>> from sympy import sqrt
>>> Poly(x**2 + 1, x)
Poly(x**2 + 1, x, domain='ZZ')
>>> Poly(x**2 + 1, x, field=True)
Poly(x**2 + 1, x, domain='QQ')
>>> Poly(x**2/2 + 1, x)
Poly(1/2*x**2 + 1, x, domain='QQ')
>>> Poly(x**2 + sqrt(2), x)
Poly(x**2 + sqrt(2), x, domain='EX')
>>> Poly(x**2 + sqrt(2), x, extension=True)
Poly(x**2 + sqrt(2), x, domain='QQ<sqrt(2)>')
也可以使用扩展参数来指定扩展的生成器,即使不需要扩展来表示系数,尽管直接使用construct_domain()时无法使用。扩展元素列表将传递给primitive_element()以创建适当的AlgebraicField域:
>>> from sympy import construct_domain
>>> Poly(x**2 + 1, x)
Poly(x**2 + 1, x, domain='ZZ')
>>> Poly(x**2 + 1, x, extension=sqrt(2))
Poly(x**2 + 1, x, domain='QQ<sqrt(2)>')
>>> Poly(x**2 + 1, x, extension=[sqrt(2), sqrt(3)])
Poly(x**2 + 1, x, domain='QQ<sqrt(2) + sqrt(3)>')
>>> construct_domain([1, 0, 1], extension=sqrt(2))[0]
ZZ
(或许construct_domain()在这里应该像Poly一样做…)
选择生成器。
如果除了生成器之外还有其他符号,则会创建多项式环或有理函数域域。在这种情况下,系数使用的域是稀疏(“new”)多项式环:
>>> p = Poly(x**2*y + z, x)
>>> p
Poly(y*x**2 + z, x, domain='ZZ[y,z]')
>>> p.gens
(x,)
>>> p.domain
ZZ[y,z]
>>> p.domain == ZZ[y,z]
True
>>> p.domain == ZZ.poly_ring(y, z)
True
>>> p.domain == ZZ.old_poly_ring(y, z)
False
>>> p.rep.rep
[y, 0, z]
>>> p.rep.rep[0]
y
>>> type(p.rep.rep[0])
<class 'sympy.polys.rings.PolyElement'>
>>> dict(p.rep.rep[0])
{(1, 0): 1}
这里我们有一种稠密和稀疏实现的奇怪混合体。Poly实例将自己视为生成器x的单变量多项式,但系数来自域ZZ[y,z]。Poly的内部表示是“稠密单变量多项式”(DUP)格式的系数列表。然而,每个系数都是在y和z中稀疏多项式的实现。
如果我们将x、y和z都作为Poly的生成器,则得到完全密集的 DMP 列表表示:
>>> p = Poly(x**2*y + z, x, y, z)
>>> p
Poly(x**2*y + z, x, y, z, domain='ZZ')
>>> p.rep
DMP_Python([[[1], []], [[]], [[1, 0]]], ZZ)
>>> p.rep.rep
[[[1], []], [[]], [[1, 0]]]
>>> p.rep.rep[0][0][0]
1
>>> type(p.rep.rep[0][0][0])
<class 'int'>
另一方面,我们可以通过选择一个表达式中根本不存在的生成器,为Poly创建完全稀疏的表示:
>>> p = Poly(x**2*y + z, t)
>>> p
Poly(x**2*y + z, t, domain='ZZ[x,y,z]')
>>> p.rep
DMP_Python([x**2*y + z], ZZ[x,y,z])
>>> p.rep.rep[0]
x**2*y + z
>>> type(p.rep.rep[0])
<class 'sympy.polys.rings.PolyElement'>
>>> dict(p.rep.rep[0])
{(0, 0, 1): 1, (2, 1, 0): 1}
如果未向Poly构造函数提供生成器,则将尝试选择生成器,使表达式在这些生成器上是多项式的。在表达式是一些符号的多项式表达式的常见情况下,这些符号将被视为生成器。然而,其他非符号表达式也可以被视为生成器:
>>> Poly(x**2*y + z)
Poly(x**2*y + z, x, y, z, domain='ZZ')
>>> from sympy import pi, exp
>>> Poly(exp(x) + exp(2*x) + 1)
Poly((exp(x))**2 + (exp(x)) + 1, exp(x), domain='ZZ')
>>> Poly(pi*x)
Poly(x*pi, x, pi, domain='ZZ')
>>> Poly(pi*x, x)
Poly(pi*x, x, domain='ZZ[pi]')
代数相关的生成器。
将exp(x)或pi作为Poly或其多项式环域的生成器是数学上有效的,因为这些对象是超越的,所以包含它们的环扩展同构于多项式环。由于x和exp(x)是代数无关的,因此也可以同时将它们作为同一个Poly的生成器使用。然而,一些其他的生成器组合是无效的,比如x和sqrt(x)或sin(x)和cos(x)。这些例子是无效的,因为生成器不是代数无关的(例如sqrt(x)**2 = x和sin(x)**2 + cos(x)**2 = 1)。尽管如此,该实现无法检测这些代数关系:
>>> from sympy import sin, cos, sqrt
>>> Poly(x*exp(x)) # fine
Poly(x*(exp(x)), x, exp(x), domain='ZZ')
>>> Poly(sin(x)+cos(x)) # not fine
Poly((cos(x)) + (sin(x)), cos(x), sin(x), domain='ZZ')
>>> Poly(x + sqrt(x)) # not fine
Poly(x + (sqrt(x)), x, sqrt(x), domain='ZZ')
对于像这样的Poly进行计算是不可靠的,因为在这种实现中零测试将无法正常工作:
>>> p1 = Poly(x, x, sqrt(x))
>>> p2 = Poly(sqrt(x), x, sqrt(x))
>>> p1
Poly(x, x, sqrt(x), domain='ZZ')
>>> p2
Poly((sqrt(x)), x, sqrt(x), domain='ZZ')
>>> p3 = p1 - p2**2
>>> p3 # should be zero...
Poly(x - (sqrt(x))**2, x, sqrt(x), domain='ZZ')
>>> p3.as_expr()
0
可以通过以下方式改进Poly的这一方面:
-
通过引入能够表示更多代数扩展类的新域来扩展域系统。
-
在
construct_domain()中改进代数依赖的检测。 -
改进生成器的自动选择。
上述示例表明,拥有一个能够表示更一般的代数扩展的域将非常有用(代数域 仅适用于 QQ 的扩展)。改进代数依赖的检测更加困难,但至少可以处理像sin(x)和cos(x)这样的常见情况。在选择生成器时,应能够认识到sqrt(x)可以是x + sqrt(x)的唯一生成器:
>>> Poly(x + sqrt(x)) # this could be improved!
Poly(x + (sqrt(x)), x, sqrt(x), domain='ZZ')
>>> Poly(x + sqrt(x), sqrt(x)) # this could be improved!
Poly((sqrt(x)) + x, sqrt(x), domain='ZZ[x]')