父结构、转换与强制转换

这一节可能比前一节更技术化,但为了有效且高效地使用 Sage 中的环和其他代数结构, 理解父结构和强制转换的意义非常重要。

请注意,我们在这里只解释概念,不展示具体实现。 面向实现的教程可以参见 Sage thematic tutorial

元素

如果想在 Python 中实现一个环,第一步是创建一个类来表示该环的元素 X, 并为其提供必要的双下划线方法,例如 __add__, __sub__, __mul__, 同时确保环公理成立。

由于 Python 是一种强类型(但动态类型)语言,可能会想到为每个环实现一个 Python 类。 毕竟,Python 有整数类型 <int> 和实数类型 <float> 等等。 但这种方法很快就会失败:环的数量是无限的,无法实现无限多个类。

相反,可以创建一个类层次结构来实现常见的代数结构元素,例如群、环、斜域、交换环、域、代数等等。

但这意味着不同环的元素可以具有相同的类型。

sage: P.<x,y> = GF(3)[]
sage: Q.<a,b> = GF(4,'z')[]
sage: type(x)==type(a)
True
>>> from sage.all import *
>>> P = GF(Integer(3))['x, y']; (x, y,) = P._first_ngens(2)
>>> Q = GF(Integer(4),'z')['a, b']; (a, b,) = Q._first_ngens(2)
>>> type(x)==type(a)
True

另一方面,也可以有不同的 Python 类来实现相同的数学结构(例如稠密矩阵与稀疏矩阵)

sage: P.<a> = PolynomialRing(ZZ)
sage: Q.<b> = PolynomialRing(ZZ, sparse=True)
sage: R.<c> = PolynomialRing(ZZ, implementation='NTL')
sage: type(a); type(b); type(c)
<class 'sage.rings.polynomial.polynomial_integer_dense_flint.Polynomial_integer_dense_flint'>
<class 'sage.rings.polynomial.polynomial_ring.PolynomialRing_integral_domain_with_category.element_class'>
<class 'sage.rings.polynomial.polynomial_integer_dense_ntl.Polynomial_integer_dense_ntl'>
>>> from sage.all import *
>>> P = PolynomialRing(ZZ, names=('a',)); (a,) = P._first_ngens(1)
>>> Q = PolynomialRing(ZZ, sparse=True, names=('b',)); (b,) = Q._first_ngens(1)
>>> R = PolynomialRing(ZZ, implementation='NTL', names=('c',)); (c,) = R._first_ngens(1)
>>> type(a); type(b); type(c)
<class 'sage.rings.polynomial.polynomial_integer_dense_flint.Polynomial_integer_dense_flint'>
<class 'sage.rings.polynomial.polynomial_ring.PolynomialRing_integral_domain_with_category.element_class'>
<class 'sage.rings.polynomial.polynomial_integer_dense_ntl.Polynomial_integer_dense_ntl'>

这带来了两个问题:一方面,如果两个元素是相同类的实例,可以预期它们的 __add__ 方法能够相加; 但如果这些元素属于非常不同的环,则不希望如此。另一方面,如果两个元素属于同一环的不同实现,想要相加, 但如果它们属于不同的 Python 类,这并不容易实现。

解决这些问题的方法称为“强制转换”,将在下面解释。

然而,每个元素都必须知道它属于哪个父结构。这可以通过 parent() 方法获得:

sage: a.parent(); b.parent(); c.parent()
Univariate Polynomial Ring in a over Integer Ring
Sparse Univariate Polynomial Ring in b over Integer Ring
Univariate Polynomial Ring in c over Integer Ring (using NTL)
>>> from sage.all import *
>>> a.parent(); b.parent(); c.parent()
Univariate Polynomial Ring in a over Integer Ring
Sparse Univariate Polynomial Ring in b over Integer Ring
Univariate Polynomial Ring in c over Integer Ring (using NTL)

父结构与范畴

与代数结构元素的 Python 类层次结构类似,Sage 也提供包含这些元素的代数结构的类。 在 Sage 中包含元素的结构称为“父结构”,并且有一个基类。 大致上与数学概念的层次结构一致,有一系列类,例如集合、环、域等等:

sage: isinstance(QQ,Field)
True
sage: isinstance(QQ, Ring)
True
sage: isinstance(ZZ,Field)
False
sage: isinstance(ZZ, Ring)
True
>>> from sage.all import *
>>> isinstance(QQ,Field)
True
>>> isinstance(QQ, Ring)
True
>>> isinstance(ZZ,Field)
False
>>> isinstance(ZZ, Ring)
True

在代数中,共享相同代数结构的对象被归类到所谓的“范畴”中。 因此,Sage 中类层次结构与范畴层次结构之间有一个粗略的类比。 然而,不应过分强调 Python 类与范畴的类比。毕竟,数学范畴也在 Sage 中实现:

sage: Rings()
Category of rings
sage: ZZ.category()
Join of Category of Dedekind domains
    and Category of euclidean domains
    and Category of noetherian rings
    and Category of infinite enumerated sets
    and Category of metric spaces
sage: ZZ.category().is_subcategory(Rings())
True
sage: ZZ in Rings()
True
sage: ZZ in Fields()
False
sage: QQ in Fields()
True
>>> from sage.all import *
>>> Rings()
Category of rings
>>> ZZ.category()
Join of Category of Dedekind domains
    and Category of euclidean domains
    and Category of noetherian rings
    and Category of infinite enumerated sets
    and Category of metric spaces
>>> ZZ.category().is_subcategory(Rings())
True
>>> ZZ in Rings()
True
>>> ZZ in Fields()
False
>>> QQ in Fields()
True

虽然 Sage 的类层次结构集中在实现细节上,但 Sage 的范畴框架更集中在数学结构上。 可以在范畴中实现不依赖具体实现的通用方法和测试。

Sage 中的父结构应该是唯一的 Python 对象。 例如,一旦创建了一个具有特定基环和特定生成器列表的多项式环,结果将被缓存:

sage: RR['x','y'] is RR['x','y']
True
>>> from sage.all import *
>>> RR['x','y'] is RR['x','y']
True

类型与父结构

类型 RingElement 并不完全对应于数学概念中的环元素。 例如,虽然方阵属于一个环,但它们不是 RingElement 的实例:

sage: M = Matrix(ZZ,2,2); M
[0 0]
[0 0]
sage: isinstance(M, RingElement)
False
>>> from sage.all import *
>>> M = Matrix(ZZ,Integer(2),Integer(2)); M
[0 0]
[0 0]
>>> isinstance(M, RingElement)
False

虽然在 Sage 中 父结构 是唯一的,但在一个父结构中的相等元素不一定是相同的。 这与 Python 对某些(虽然不是全部)整数的行为形成对比:

sage: int(1) is int(1) # Python int
True
sage: int(-15) is int(-15)
False
sage: 1 is 1           # Sage Integer
False
>>> from sage.all import *
>>> int(Integer(1)) is int(Integer(1)) # Python int
True
>>> int(-Integer(15)) is int(-Integer(15))
False
>>> Integer(1) is Integer(1)           # Sage Integer
False

不同环的元素通常不是通过它们的类型区分,而是通过它们的父结构区分:

sage: a = GF(2)(1)
sage: b = GF(5)(1)
sage: type(a) is type(b)
True
sage: parent(a)
Finite Field of size 2
sage: parent(b)
Finite Field of size 5
>>> from sage.all import *
>>> a = GF(Integer(2))(Integer(1))
>>> b = GF(Integer(5))(Integer(1))
>>> type(a) is type(b)
True
>>> parent(a)
Finite Field of size 2
>>> parent(b)
Finite Field of size 5

因此,从代数的角度来看,元素的父结构比它的类型更重要。

转换与强制转换

在某些情况下,可以将一个父结构的元素转换为另一个父结构的元素。 这样的转换可以是显式的也可以是隐式的(被称为 强制转换)。

读者可能知道例如 C 语言中的 类型转换类型强制转换 的概念。 Sage 中也有转换和强制转换的概念。但 Sage 中的概念集中在 父结构 上,而不是类型上。 所以请不要将 C 语言中的类型转换与 Sage 中的转换混淆!

我们在这里给出一个相当简短的说明。 详细描述和实现信息,请参阅参考手册中的强制转换章节以及 thematic tutorial.

关于在 不同 环的元素上进行算术运算的可能性,有两种极端观点:

  • 不同的环是不同的世界,对不同环的元素进行加法或乘法没有任何意义; 即使 1 + 1/2 也没有意义,因为第一个加数是整数,第二个是有理数。

或者

  • 如果一个环 R1 的元素 r1 可以以某种方式在另一个环 R2 中解释, 那么所有涉及 r1 和任意 R2 元素的算术运算都是允许的。 乘法单位存在于所有域和许多环,它们应该都是相等的。

Sage 选择了一种折衷方案。如果 P1P2 是父结构,p1P1 的元素, 那么用户可以显式请求将 p1P2 中解释。这在所有情况下可能没有意义, 或者对于 P1 的所有元素都没有定义,用户需要确保其合理性。我们称之为 转换

sage: a = GF(2)(1)
sage: b = GF(5)(1)
sage: GF(5)(a) == b
True
sage: GF(2)(b) == a
True
>>> from sage.all import *
>>> a = GF(Integer(2))(Integer(1))
>>> b = GF(Integer(5))(Integer(1))
>>> GF(Integer(5))(a) == b
True
>>> GF(Integer(2))(b) == a
True

然而,只有当这种转换可以彻底和一致地完成时,才会发生 隐式 (或自动)转换。 数学的严谨性在这一点上至关重要。

这种隐式转换称为 强制转换。如果定义了强制转换,那么它必须与转换一致。 定义强制转换需要满足两个条件:

  1. P1P2 的强制转换必须由结构保持映射给出(例如环同态)。 仅仅一些 P1 的元素可以映射到 P2 是不够的,映射必须尊重 P1 的代数结构。

  2. 这些强制转换映射的选择必须一致:如果 P3 是第三个父结构, 那么从 P1P2 的选定强制转换与从 P2P3 的强制转换的组合 必须与从 P1P3 的选定强制转换一致。特别是, 如果存在从 P1P2 和从 P2P1 的强制转换,则组合必须是 P1 的恒等映射。

因此,尽管可以将 GF(2) 的每个元素转换为 GF(5),但不能强制转换, 因为 GF(2)GF(5) 之间没有环同态。

一致性方面更难解释。我们用多元多项式环来说明。在应用中,保留名称的强制转换最有意义。因此,我们有:

sage: R1.<x,y> = ZZ[]
sage: R2 = ZZ['y','x']
sage: R2.has_coerce_map_from(R1)
True
sage: R2(x)
x
sage: R2(y)
y
sage: R2.coerce(y)
y
>>> from sage.all import *
>>> R1 = ZZ['x, y']; (x, y,) = R1._first_ngens(2)
>>> R2 = ZZ['y','x']
>>> R2.has_coerce_map_from(R1)
True
>>> R2(x)
x
>>> R2(y)
y
>>> R2.coerce(y)
y

如果没有保留名称的环同态,则不定义强制转换。然而,转换可能仍然是可能的,即通过根据生成器列表中的位置映射环生成器:

sage: R3 = ZZ['z','x']
sage: R3.has_coerce_map_from(R1)
False
sage: R3(x)
z
sage: R3(y)
x
sage: R3.coerce(y)
Traceback (most recent call last):
...
TypeError: no canonical coercion
from Multivariate Polynomial Ring in x, y over Integer Ring
to Multivariate Polynomial Ring in z, x over Integer Ring
>>> from sage.all import *
>>> R3 = ZZ['z','x']
>>> R3.has_coerce_map_from(R1)
False
>>> R3(x)
z
>>> R3(y)
x
>>> R3.coerce(y)
Traceback (most recent call last):
...
TypeError: no canonical coercion
from Multivariate Polynomial Ring in x, y over Integer Ring
to Multivariate Polynomial Ring in z, x over Integer Ring

但这种保留位置的转换不符合强制转换:通过组合从 ZZ['x','y']ZZ['y','x'] 的保留名称映射 与从 ZZ['y','x']ZZ['a','b'] 的保留位置映射,将得到一个既不保留名称也不保留位置的映射,违反了一致性。

如果存在强制转换,它将用于比较不同环的元素或进行算术运算。这通常很方便, 但用户应该意识将 == 关系扩展到不同父结构的边界可能很容易导致过度使用。 例如,虽然 == 应该是 同一 环元素上的等价关系,但如果涉及 不同 环,则不一定如此。 例如,ZZ 和有限域中的 1 被认为是相等的,因为从整数到任何有限域都有一个规范的强制转换。 然而,通常两个不同的有限域之间没有强制转换。因此我们有:

sage: GF(5)(1) == 1
True
sage: 1 == GF(2)(1)
True
sage: GF(5)(1) == GF(2)(1)
False
sage: GF(5)(1) != GF(2)(1)
True
>>> from sage.all import *
>>> GF(Integer(5))(Integer(1)) == Integer(1)
True
>>> Integer(1) == GF(Integer(2))(Integer(1))
True
>>> GF(Integer(5))(Integer(1)) == GF(Integer(2))(Integer(1))
False
>>> GF(Integer(5))(Integer(1)) != GF(Integer(2))(Integer(1))
True

同理,我们有:

sage: R3(R1.1) == R3.1
True
sage: R1.1 == R3.1
False
sage: R1.1 != R3.1
True
>>> from sage.all import *
>>> R3(R1.gen(1)) == R3.gen(1)
True
>>> R1.gen(1) == R3.gen(1)
False
>>> R1.gen(1) != R3.gen(1)
True

一致性条件的另一个结果是强制转换只能从精确环(例如有理数 QQ)到不精确环(例如具有固定精度的实数 RR),而不能反过来。 原因是从 QQRR 的强制转换与从 RRQQ 的转换的组合应该是 QQ 上的恒等映射。 但这是不可能的,因为在 RR 中一些不同的有理数可能被视为相等,如下例所示:

sage: RR(1/10^200+1/10^100) == RR(1/10^100)
True
sage: 1/10^200+1/10^100 == 1/10^100
False
>>> from sage.all import *
>>> RR(Integer(1)/Integer(10)**Integer(200)+Integer(1)/Integer(10)**Integer(100)) == RR(Integer(1)/Integer(10)**Integer(100))
True
>>> Integer(1)/Integer(10)**Integer(200)+Integer(1)/Integer(10)**Integer(100) == Integer(1)/Integer(10)**Integer(100)
False

当比较两个父结构 P1P2 的元素时,可能没有两个环之间的强制转换, 但有一个规范的父结构 P3 可选,使得 P1P2 都强制转换到 P3。 在这种情况下,也会发生强制转换。一个典型用例是有理数和具有整数系数的多项式之和,产生具有有理系数的多项式:

sage: P1.<x> = ZZ[]
sage: p = 2*x+3
sage: q = 1/2
sage: parent(p)
Univariate Polynomial Ring in x over Integer Ring
sage: parent(p+q)
Univariate Polynomial Ring in x over Rational Field
>>> from sage.all import *
>>> P1 = ZZ['x']; (x,) = P1._first_ngens(1)
>>> p = Integer(2)*x+Integer(3)
>>> q = Integer(1)/Integer(2)
>>> parent(p)
Univariate Polynomial Ring in x over Integer Ring
>>> parent(p+q)
Univariate Polynomial Ring in x over Rational Field

注意,原则上结果在 ZZ['x'] 的分数域中也有意义。 然而,Sage 会尝试选择一个 规范的 共同父结构,使得看起来最自然(在我们的例子中是 QQ['x'])。 如果几个潜在的共同父结构看起来同样自然,为了获得可靠的结果,Sage 不会 随机选择其中一个。 该选择所基于的机制在 thematic tutorial 中进行了解释。

以下示例不会发生强制转换到共同父结构:

sage: R.<x> = QQ[]
sage: S.<y> = QQ[]
sage: x+y
Traceback (most recent call last):
...
TypeError: unsupported operand parent(s) for +: 'Univariate Polynomial Ring in x over Rational Field' and 'Univariate Polynomial Ring in y over Rational Field'
>>> from sage.all import *
>>> R = QQ['x']; (x,) = R._first_ngens(1)
>>> S = QQ['y']; (y,) = S._first_ngens(1)
>>> x+y
Traceback (most recent call last):
...
TypeError: unsupported operand parent(s) for +: 'Univariate Polynomial Ring in x over Rational Field' and 'Univariate Polynomial Ring in y over Rational Field'

原因是 Sage 不会选择潜在候选结构 QQ['x']['y'], QQ['y']['x'], QQ['x','y']QQ['y','x'] 之一, 因为所有这四个成对不同的结构看起来都是自然的共同父结构,并且没有明显的规范选择。