如何在SAGE中实现新的代数结构

Sage的范畴和胁迫框架

本教程的目的是解释在实现新的代数结构时如何从Sage的范畴框架和强制模型中受益。它是基于2011年创建的一份工作表。

我们通过一个具体的例子,即分数域的玩具实现,来说明Sage的范畴框架和胁迫模型的概念。代码是逐步开发的,因此读者可以专注于本教程每一部分的一个细节。完整的代码可以在附录中找到。

提纲

  • 使用现有基类

    要使用Sage的强制系统,必须使用以下子类 sage.structure.parent.Parentsage.structure.element.Element ,分别为。它们提供了许多“神奇的”双下划线Python方法的默认实现,这些方法不能被重写。相反,实际的实现应该在 single underscore 方法,如 _add__mul_

  • 将父结构转换为某个类别的对象

    在初始化期间声明类别-您的父结构将继承更多有用的方法和一致性测试。

  • 为您的父结构提供一个元素类

    为其分配一个名为 Element -元素将从该类别继承更多有用的方法。此外,一些基本的转换将立即生效。

  • 实施进一步的转换

    永远不要重写父母的 __call__ 方法!提供方法 _element_constructor_ 取而代之的是。

  • 宣布胁迫

    如果转换恰好是一种态射,你可以考虑把它变成一种胁迫。到时候它会的 implicitly 用于算术运算。

  • 高级强制:为父结构定义构造函数

    Sage会在需要的时候自动为你创建新的父母,通过所谓的 sage.categories.pushout.pushout() 建筑。

  • 运行自动测试套件

    每种方法都应该被记录下来,并提供文档测试(我们在这里不给出例子)。此外,为类别的对象或元素定义的任何方法都应该由测试方法支持,该测试方法在运行测试套件时执行。

基类

在Sage中,“父”是一个类别的对象,包含元素。父母应该继承 sage.structure.parent.Parent 以及它们的元素来自 sage.structure.element.Element

SAGE提供了适当的子类 ParentElement 各种更具体的代数结构,如群、环或域及其元素。但塞奇的一些旧东西并不使用它。 Volunteers for refactoring are welcome!

父辈

由于我们希望实现一种特殊类型的字段,即分数字段,因此在基类之上进行构建是有意义的 sage.rings.ring.Field 由Sage提供。**

sage: from sage.rings.ring import Field

此基类提供的方法比一般父类多得多::

sage: [p for p in dir(Field) if p not in dir(Parent)]
['_CommutativeRing__fraction_field',
 '__iter__',
 '__len__',
 '__rxor__',
 '__xor__',
 '_an_element_impl',
 '_coerce_',
 '_coerce_c',
 '_coerce_impl',
 '_default_category',
 '_gens',
 '_ideal_class_',
 '_ideal_monoid',
 '_latex_names',
 '_list',
 '_one_element',
 '_pseudo_fraction_field',
 '_unit_ideal',
 '_zero_element',
 '_zero_ideal',
 'algebraic_closure',
 'base_extend',
 'class_group',
 'content',
 'derivation',
 'derivation_module',
 'divides',
 'epsilon',
 'extension',
 'fraction_field',
 'frobenius_endomorphism',
 'gcd',
 'gen',
 'gens',
 'ideal',
 'ideal_monoid',
 'integral_closure',
 'is_commutative',
 'is_field',
 'is_integral_domain',
 'is_integrally_closed',
 'is_noetherian',
 'is_prime_field',
 'is_subring',
 'krull_dimension',
 'localization',
 'ngens',
 'one',
 'order',
 'prime_subfield',
 'principal_ideal',
 'random_element',
 'unit_ideal',
 'zero',
 'zero_ideal',
 'zeta',
 'zeta_order']

下面是分数字段的一个非常基本的实现,稍后需要补充。**

sage: from sage.structure.unique_representation import UniqueRepresentation
sage: class MyFrac(UniqueRepresentation, Field):
....:     def __init__(self, base):
....:         if base not in IntegralDomains():
....:             raise ValueError("%s is no integral domain" % base)
....:         Field.__init__(self, base)
....:     def _repr_(self):
....:         return "NewFrac(%s)"%repr(self.base())
....:     def base_ring(self):
....:         return self.base().base_ring()
....:     def characteristic(self):
....:         return self.base().characteristic()

此基本实现由以下步骤构成:

  • Sage中的任何戒指都有一个 base 以及一个 base ring 。环的“通常”分数域 R 有底子 R 和底环 R.base_ring() **

    sage: Frac(QQ['x']).base(), Frac(QQ['x']).base_ring()
    (Univariate Polynomial Ring in x over Rational Field, Rational Field)
    

    声明基数很容易:我们只需将其作为参数传递给字段构造函数。**

    sage: Field(ZZ['x']).base()
    Univariate Polynomial Ring in x over Integer Ring
    

    我们正在实现一个返回基环的单独方法。

  • 对于算术方法和字符串表示,Python使用双--下划线方法。Sage的基类通常有一个默认实现,它被要求 implement SINGLE underscore methods _repr_, and similarly _add_, _mul_ etc.

  • 我们鼓励您 make your parent "unique" 。这就是说,父母只有在他们完全相同的情况下才应该评价平等。Sage提供了创建唯一父级的框架。我们在这里使用了最简单的方法:从类继承 sage.structure.unique_representation.UniqueRepresentation 就足够了。使父代独一无二对于有效的实现非常重要,因为重复创建“相同的”父代将花费大量时间。

  • 分数域仅为整数域定义。因此,如果给定的环不属于整环范畴,则会产生错误。这是我们的第一个类别用例。

  • 最后,我们添加了一个返回字段特征的方法。我们不会深入讨论细节,但我们在下面研究的一些自动化测试隐含地依赖于这种方法。

我们看到我们的基本实现正确地拒绝了不是整域的环:

sage: MyFrac(ZZ['x'])
NewFrac(Univariate Polynomial Ring in x over Integer Ring)
sage: MyFrac(Integers(15))
Traceback (most recent call last):
...
ValueError: Ring of integers modulo 15 is no integral domain

备注

继承自 UniqueRepresentation 自动为我们的类提供pickling,保留唯一的父条件。如果我们在某个外部模块或交互会话中定义了类,则pickling将立即生效。

但是,为了使下面的示例在Sage的文档测试框架中工作,我们需要将我们的类指定为 __main__ 模块,以便在取消pickling期间可以查找类。

sage: import __main__
sage: __main__.MyFrac = MyFrac
sage: loads(dumps(MyFrac(ZZ))) is MyFrac(ZZ)
True

备注

在接下来的部分中,我们将陆续添加或更改 MyFrac 。我们没有在每个步骤中给出完整的类定义,而是定义了 MyFrac 通过继承先前定义的版本 MyFrac 。我们相信这将帮助读者专注于每一节中相关的单个细节。

完整的代码可以在附录中找到。

元素

我们使用基类 sage.structure.element.FieldElement 。请注意,在创建字段元素时,不会测试给定父元素是否为字段::

sage: from sage.structure.element import FieldElement
sage: FieldElement(ZZ)
Generic element of a structure

我们的分数字段元素的玩具实现基于以下考虑:

  • 分数域元素由分子和分母定义,这两个元素都需要是基的元素。应该有返回分子的方法。分母。

  • 分母不能为零,并且(如果基是有序环)我们可以使它非负,而不损失一般性。默认情况下,分母为1。

  • 字符串表示由单-下划线方法返回 _repr_ 。为了使我们的分数字段元素与Sage中已有的元素区分开来,我们使用了不同的字符串表示。

  • 算法以单下划线方式实现 _add__mul_We do not override the default double underscore __add__, __mul__ ,否则,我们就不能使用塞奇的强制模型。

  • 可以使用以下命令实现比较 _richcmp_ 。这会自动使关系运算符类似于 ==< 工作。在此方法中,您可以使用 richcmp SAGE提供的功能和相关工具。

    请注意 _richcmp_ 应提供,否则无法进行比较:

    sage: class Foo(sage.structure.element.Element):
    ....:  def __init__(self, parent, x):
    ....:      self.x = x
    ....:  def _repr_(self):
    ....:      return "<%s>" % self.x
    sage: a = Foo(ZZ, 1)
    sage: b = Foo(ZZ, 2)
    sage: a <= b
    Traceback (most recent call last):
    ...
    TypeError: '<=' not supported between instances of 'Foo' and 'Foo'
    
  • 在单下划线方法中,我们可以假设 both arguments belong to the same parent 。这是强制模式的一个好处。

  • 在构造作为算术运算结果的新元素时,我们不会直接命名类,而是使用 self.__class__ 。稍后,这将派上用场。

这会产生以下代码:

sage: class MyElement(FieldElement):
....:     def __init__(self, parent,n,d=None):
....:         B = parent.base()
....:         if d is None:
....:             d = B.one()
....:         if n not in B or d not in B:
....:             raise ValueError("Numerator and denominator must be elements of %s"%B)
....:         # Numerator and denominator should not just be "in" B,
....:         # but should be defined as elements of B
....:         d = B(d)
....:         n = B(n)
....:         if d==0:
....:             raise ZeroDivisionError("The denominator must not be zero")
....:         if d<0:
....:             self.n = -n
....:             self.d = -d
....:         else:
....:             self.n = n
....:             self.d = d
....:         FieldElement.__init__(self,parent)
....:     def numerator(self):
....:         return self.n
....:     def denominator(self):
....:         return self.d
....:     def _repr_(self):
....:         return "(%s):(%s)"%(self.n,self.d)
....:     def _richcmp_(self, other, op):
....:         from sage.structure.richcmp import richcmp
....:         return richcmp(self.n*other.denominator(), other.numerator()*self.d, op)
....:     def _add_(self, other):
....:         C = self.__class__
....:         D = self.d*other.denominator()
....:         return C(self.parent(), self.n*other.denominator()+self.d*other.numerator(), D)
....:     def _sub_(self, other):
....:         C = self.__class__
....:         D = self.d*other.denominator()
....:         return C(self.parent(), self.n*other.denominator()-self.d*other.numerator(),D)
....:     def _mul_(self, other):
....:         C = self.__class__
....:         return C(self.parent(), self.n*other.numerator(), self.d*other.denominator())
....:     def _div_(self, other):
....:         C = self.__class__
....:         return C(self.parent(), self.n*other.denominator(), self.d*other.numerator())
基本实施的特点和限制

多亏了单下划线方法,一些基本的算术运算才行得通, if 我们停留在单一的父结构内::

sage: P = MyFrac(ZZ)
sage: a = MyElement(P, 3, 4)
sage: b = MyElement(P, 1, 2)
sage: a+b, a-b, a*b, a/b
((10):(8), (2):(8), (3):(8), (6):(4))
sage: a-b == MyElement(P, 1, 4)
True

存在元素测试的默认实现。我们已经可以做到:

sage: a in P
True

自那以后 a 被定义为 P 。然而,我们还不能验证整数是否包含在整数环的分数域中。它甚至不会给出错误的答案,但会导致错误::

sage: 1 in P
Traceback (most recent call last):
...
NotImplementedError: cannot construct elements of NewFrac(Integer Ring)

我们稍后会处理这件事的。

《Sage》中的范畴

有时基类并不反映数学: mtimes n 域上的矩阵一般不超过一个向量空间。因此,这个集合(称为 MatrixSpace )不是在 sage.rings.ring.Ring 。但是,如果 m=n ,则矩阵空间是代数,因此是环。

从Python基类的角度来看,这两种情况是相同的:

sage: MS1 = MatrixSpace(QQ,2,3)
sage: isinstance(MS1, Ring)
False
sage: MS2 = MatrixSpace(QQ,2)
sage: isinstance(MS2, Ring)
False

SAGE的分类框架可以区分这两种情况:

sage: Rings()
Category of rings
sage: MS1 in Rings()
False
sage: MS2 in Rings()
True

事实上, MS2more 方法比 MS1 **

sage: import inspect
sage: L1 = len([s for s in dir(MS1) if inspect.ismethod(getattr(MS1,s,None))])
sage: L2 = len([s for s in dir(MS2) if inspect.ismethod(getattr(MS2,s,None))])
sage: L1 < L2
True

这是因为班级 MS2 还继承自代数的父类::

sage: MS1.__class__.__bases__
(<class 'sage.matrix.matrix_space.MatrixSpace'>,
<class 'sage.categories.category.JoinCategory.parent_class'>)
sage: MS2.__class__.__bases__
(<class 'sage.matrix.matrix_space.MatrixSpace'>,
<class 'sage.categories.category.JoinCategory.parent_class'>)

下面,我们将解释如何利用这一点。

毫不奇怪,我们的父母 P 上面定义的知道它属于字段类别,因为它派生自字段的基类。

sage: P.category()
Category of fields

然而,我们可以选择一个较小的类别,即商域类别。

为什么要选择一个类别?

One can provide default methods for all objects of a category, and for all elements of such objects. Hence, the category framework is a way to inherit useful stuff that is not present in the base classes. These default methods do not rely on implementation details, but on mathematical concepts.

此外,类别还定义了 test suites 有关它们的对象和元素,请参见最后一节。因此,人们还可以免费接受基本的理智测试。

这是怎么回事 category framework 工作?

对象的抽象基类(“PARENT_CLASS”)和对象的元素(“ELEMENT_CLASS”)由类别属性提供。在父级的初始化期间,父级的类是 dynamically changed 添加到类别父类的子类中。同样,类别的元素类的子类可用于创建父元素,如下所述。

类的动态更改在Cython中不起作用。尽管如此,方法继承仍然有效,因为 __getattr__ 方法。

备注

强烈建议在Python和Cython中使用CATEGORY框架。

让我们看看选择商字段类别而不是字段类别是否有任何好处:

sage: QuotientFields().parent_class, QuotientFields().element_class
(<class 'sage.categories.quotient_fields.QuotientFields.parent_class'>,
 <class 'sage.categories.quotient_fields.QuotientFields.element_class'>)
sage: [p for p in dir(QuotientFields().parent_class) if p not in dir(Fields().parent_class)]
[]
sage: [p for p in dir(QuotientFields().element_class) if p not in dir(Fields().element_class)]
['_derivative',
 'denominator',
 'derivative',
 'numerator',
 'partial_fraction_decomposition']

因此,我们的分数域没有直接的收益,但是我们的分数域元素可以使用其他方法。请注意,其中一些方法是占位符:没有缺省实现,但它是 required (分别为 optional )要实施这些方法:

sage: QuotientFields().element_class.denominator
<abstract method denominator at ...>
sage: from sage.misc.abstract_method import abstract_methods_of_class
sage: abstract_methods_of_class(QuotientFields().element_class)['optional']
['_add_', '_mul_']
sage: abstract_methods_of_class(QuotientFields().element_class)['required']
['__bool__', 'denominator', 'numerator']

因此,在实现商域的元素时,它是 required 来实现返回分母和分子的方法,以及一个告诉元素是否非零的方法,此外,它 optional (但当然推荐)提供一些算术方法。如果忘记实现所需的方法,CATEGORY框架的测试套件将会抱怨-见下文。

实现父级的类别框架

我们只需要通过字段构造函数的一个可选参数声明正确的类别,其中我们提供了覆盖默认类别的可能性:

sage: from sage.categories.quotient_fields import QuotientFields
sage: class MyFrac(MyFrac):
....:     def __init__(self, base, category=None):
....:         if base not in IntegralDomains():
....:             raise ValueError("%s is no integral domain" % base)
....:         Field.__init__(self, base, category=category or QuotientFields())

在构造 MyFrac ,它们的类被动态地更改为一个名为 MyFrac_with_category 。它是一个常见的子类 MyFrac 和类别的父类::

sage: P = MyFrac(ZZ)
sage: type(P)
<class '__main__.MyFrac_with_category'>
sage: isinstance(P,MyFrac)
True
sage: isinstance(P,QuotientFields().parent_class)
True

分数域 P 继承其他方法。例如,基类 Field 没有一种方法 sum 。但 P 继承了交换可加么半群范畴的这种方法-请参阅 sum() **

sage: P.sum.__module__
'sage.categories.additive_monoids'

我们在上面已经看到,我们可以添加元素。尽管如此, sum 方法不起作用,但::

sage: a = MyElement(P, 3, 4)
sage: b = MyElement(P, 1, 2)
sage: c = MyElement(P, -1, 2)
sage: P.sum([a, b, c])
Traceback (most recent call last):
...
NotImplementedError: cannot construct elements of NewFrac(Integer Ring)

原因是 sum 方法以返回值 P.zero() ,它缺省为 P(0) -但将整数转换为 P 还没有实施。

实现元素的类别框架

与我们看到的父类类似,动态创建了一个新类,它将父类的类别的Element类与我们上面实现的类组合在一起。但是,类别框架针对元素的实现方式与针对父级的不同:

  • 我们为家长提供 P (或其类)具有名为“Element”的属性,其值为一个类。

  • 父辈 automatically 获取属性 P.element_class ,这两个子类都是 P.ElementP.category().element_class

因此,为了给我们的分数字段提供它们自己的元素类, we just need to add a single line to our class **

sage: class MyFrac(MyFrac):
....:     Element = MyElement

这一小小的改变提供了几个好处:

  • 现在,我们只需调用父元素::

    sage: P = MyFrac(ZZ)
    sage: P(1), P(2,3)
    ((1):(1), (2):(3))
    
  • 有一种方法 zero 返回预期结果::

    sage: P.zero()
    (0):(1)
    
  • 这个 sum 上述方法突然奏效::

    sage: a = MyElement(P, 9, 4)
    sage: b = MyElement(P, 1, 2)
    sage: c = MyElement(P, -1, 2)
    sage: P.sum([a,b,c])
    (36):(16)
    
  • 现在,使用我们定义的乘法进行开箱即用的乘法:

    sage: a^3
    (729):(64)
    
幕后发生了什么,让这件事奏效了?

我们提供了 P.Element ,从而获得 P.element_class ,这是一种 lazy attribute 。它提供了一个 dynamic 类,它是两者的子类 MyElement 上面定义的和 P.category().element_class **

sage: P.__class__.element_class
<sage.misc.lazy_attribute.lazy_attribute object at ...>
sage: P.element_class
<class '__main__.MyFrac_with_category.element_class'>
sage: type(P.element_class)
<class 'sage.structure.dynamic_class.DynamicInheritComparisonMetaclass'>
sage: issubclass(P.element_class, MyElement)
True
sage: issubclass(P.element_class,P.category().element_class)
True

这个 default __call__ 方法论 P 将给定参数传递给 P.element_class ,添加参数 parent=P 。这就是我们现在能够通过调用父元素来创建元素的原因。

具体地说,这些元素是新动态类的实例:

sage: type(P(2,3))
<class '__main__.MyFrac_with_category.element_class'>

备注

All 元素: P 应使用Element类。为了确保算术运算的结果也是如此,我们将它们创建为 self.__class__ 在算术方法中 MyElement

P.zero() 默认为返回 P(0) ,并因此返回 P.element_class 。自.以来 P.sum([...]) 总和以 P.zero() 而SUM的类别只取决于第一个被加数,通过我们的实现,我们有:

sage: type(a)
<class '__main__.MyElement'>
sage: isinstance(a,P.element_class)
False
sage: type(P.sum([a,b,c]))
<class '__main__.MyFrac_with_category.element_class'>

方法 factor 由以下人员提供 P.category().element_class (见上)简单有效:

sage: a; a.factor(); P(6,4).factor()
(9):(4)
2^-2 * 3^2
2^-1 * 3

但这令人惊讶:元素 a is just an instance of MyElement, but not of P.element_class, and its class does not know about the factor method. In fact, this is due to a ``_ _getattr__``方法定义为 sage.structure.element.Element 。**

sage: hasattr(type(a), 'factor')
False
sage: hasattr(P.element_class, 'factor')
True
sage: hasattr(a, 'factor')
True

关于表演的第一个注解

类别框架有时被指责为速度倒退,如在 :issue:`9138`:issue:`11900` 。但如果类别框架是 used properly ,那么它就是快的。为了进行说明,我们确定了访问从Element类继承的属性所需的时间。首先,我们考虑一个元素,该元素使用了我们上面实现的类,但没有正确使用类别框架:

sage: type(a)
<class '__main__.MyElement'>
sage: timeit('a.factor',number=1000)     # random
1000 loops, best of 3: 2 us per loop

现在,我们考虑一个元素,它等于 a ,但正确使用类别框架:

sage: a2 = P(9,4)
sage: a2 == a
True
sage: type(a2)
<class '__main__.MyFrac_with_category.element_class'>
sage: timeit('a2.factor',number=1000)    # random
1000 loops, best of 3: 365 ns per loop

所以, don't be afraid of using categories!

胁迫-基础知识

理论背景

胁迫不仅仅是 type conversion

在C编程语言中,“强制”意味着“自动类型转换”。然而,在Sage中,如果一个人想要在不同父母的元素之间进行算术、比较等,就需要强制。因此, coercion is not about a change of types, but about a change of parents.

作为示例,我们展示了相同类型的元素可能完全属于完全不同的父级:

sage: P1 = QQ['v,w']; P2 = ZZ['w,v']
sage: type(P1.gen()) == type(P2.gen())
True
sage: P1 == P2
False

P_2 自然而然地是一个亚环 P_1 。因此,能够将两个环的元素相加是有意义的-结果应该位于 P_1 ,它确实做到了:

sage: (P1.gen()+P2.gen()).parent() is P1
True

如果需要的话,这会很不方便。 manually 将元素转换为 P_2 vt.进入,进入 P_1 在添加之前。强制系统自动完成这一转换。

并非每一次皈依都是一种胁迫

强制在没有用户显式请求的情况下隐式发生。因此,胁迫必须基于数学上的严谨性。在我们的示例中,任何元素 P_2 可以自然地解释为 P_1 。因此,我们有:

sage: P1.has_coerce_map_from(P2)
True
sage: P1.coerce_map_from(P2)
Coercion map:
  From: Multivariate Polynomial Ring in w, v over Integer Ring
  To:   Multivariate Polynomial Ring in v, w over Rational Field

虽然存在从 P_1P_2 (即仅限于具有整系数的多项式),此转换不是胁迫:

sage: P2.convert_map_from(P1)
Conversion map:
  From: Multivariate Polynomial Ring in v, w over Rational Field
  To:   Multivariate Polynomial Ring in w, v over Integer Ring
sage: P2.has_coerce_map_from(P1)
False
sage: P2.coerce_map_from(P1) is None
True
被要求强制的四条公理
  1. 胁迫是适当范畴中的态射。

    第一个公理有两个含义:

    1. 强制在父级的所有元素上定义。

      整数上的零次多项式可以解释为整数-但尝试转换非零次多项式将导致错误:

      sage: ZZ(P2.one())
      1
      sage: ZZ(P2.gen(1))
      Traceback (most recent call last):
      ...
      TypeError: not a constant polynomial
      

      因此,我们只有一个 partial 地图。这是一个很好的 conversion ,但部分地图不符合条件 coercion

    2. 胁迫是结构保持的。

      任何实数都可以通过四舍五入转换为整数。但是,这样的转换在算术运算中没有用处,因为基本的代数结构不会保留:

      sage: int(1.6)+int(2.7) == int(1.6+2.7)
      False
      

      要保留的结构取决于涉及的父母的类别。例如,从整数到有理域的强制是欧几里德域的同态:

      sage: QQ.coerce_map_from(ZZ).category_for()
      Join of Category of euclidean domains and Category of infinite sets
      and Category of metric spaces
      
  2. 从一个父母到另一个父母至多有一种胁迫

    另外,如果有一个 coercion 从… P_2P_1 ,然后是一个 conversion 从… P_2P_1 是为所有元素定义的 P_2 并与这种胁迫相吻合。尽管如此,用户公开的地图是内部使用的地图的副本,因此不同的实例化之间缺乏身份:

    sage: P1.coerce_map_from(P2) is P1.convert_map_from(P2)
    False
    

    对于内部使用的地图,地图是相同的:

    sage: P1._internal_coerce_map_from(P2) is P1._internal_convert_map_from(P2)
    True
    
  3. 胁迫可以被合成

    If there is a coercion varphi: P_1 to P_2 and another coercion psi: P_2 to P_3, then the composition of varphi followed by psi must yield the unique coercion from P_1 to P_3.

  4. 身份是一种胁迫

    与前面的两个公理一起,它是这样的:如果有来自 P_1P_2 以及来自 P_2P_1 ,那么它们是相互相反的。

实施转换

我们在上面已经看到,在提供属性之后,可以使用到分数字段的一些转换 Element 。但是,我们不能将一个分数字段的元素转换为另一个分数字段的元素,但:

sage: P(2/3)
Traceback (most recent call last):
...
ValueError: Numerator and denominator must be elements of Integer Ring

为了实现转换, the default __call__ method should (almost) never be overridden. 相反, we implement the method _element_constructor_ ,它应该返回父级的Element类的实例。一些旧的父类违反了该规则-请帮助重构它们!::

sage: class MyFrac(MyFrac):
....:     def _element_constructor_(self, *args, **kwds):
....:         if len(args)!=1:
....:             return self.element_class(self, *args, **kwds)
....:         x = args[0]
....:         try:
....:             P = x.parent()
....:         except AttributeError:
....:             return self.element_class(self, x, **kwds)
....:         if P in QuotientFields() and P != self.base():
....:             return self.element_class(self, x.numerator(), x.denominator(), **kwds)
....:         return self.element_class(self, x, **kwds)

除了从基环和基环元素对的转换之外,我们现在还有一个从有理数到我们的分数域的转换 ZZ

sage: P = MyFrac(ZZ)
sage: P(2); P(2,3); P(3/4)
(2):(1)
(2):(3)
(3):(4)

回想一下,上面的测试 1 in P 失败,并出现错误。我们再次尝试,发现错误已消失。这是因为我们现在能够将整数 1 vt.进入,进入 P 。但遏制试验仍然产生了错误的答案::

sage: 1 in P
False

技术原因:我们有一个转换 P(1)1 vt.进入,进入 P ,但这不是一种胁迫-至今!::

sage: P.has_coerce_map_from(ZZ), P.has_coerce_map_from(QQ)
(False, False)

建立一种胁迫

有两种主要方法可以让Sage使用特定的转换作为胁迫:

  • 一个人可以使用 sage.structure.parent.Parent.register_coercion() ,通常在父进程的初始化期间(参见该方法的文档)。

  • 更灵活的方法是提供一种方法 _coerce_map_from_ 为家长准备的。

让我们 PR 为人父母。如果 P._coerce_map_from_(R) 退货 FalseNone ,那么就不会有来自 RP 。如果它返回包含属性域的地图 R 和共域 P ,然后这张地图被用于胁迫。如果它回来了 True ,然后从 RP 被用作胁迫手段。

请注意,在下面的实现中,我们需要Rational字段的特殊情况,因为 QQ.base() 不是整数环。**

sage: class MyFrac(MyFrac):
....:     def _coerce_map_from_(self, S):
....:         if self.base().has_coerce_map_from(S):
....:             return True
....:         if S in QuotientFields():
....:             if self.base().has_coerce_map_from(S.base()):
....:                 return True
....:             if hasattr(S,'ring_of_integers') and self.base().has_coerce_map_from(S.ring_of_integers()):
....:                 return True

通过上述方法,强迫进入基环的母体也将强制进入分数场,并且如果相应的基环存在胁迫,则分数场强制进入另一个分数场。现在,我们有:

sage: P = MyFrac(QQ['x'])
sage: P.has_coerce_map_from(ZZ['x']), P.has_coerce_map_from(Frac(ZZ['x'])), P.has_coerce_map_from(QQ)
(True, True, True)

我们现在可以使用来自 ZZ[x] 以及来自 QQ vt.进入,进入 P 对于两个环之间的算术运算:

sage: 3/4+P(2)+ZZ['x'].gen(), (P(2)+ZZ['x'].gen()).parent() is P
((4*x + 11):(4), True)
相等性与元素包容

回想一下,上面的测试 1 in P 给出了错误的答案。现在让我们重复测试::

sage: 1 in P
True

为什么会这样呢?

默认的元素包容测试 x in P 是基于三个构建块的相互作用:转换、强制和相等测试。

  1. 显然,如果转换 P(x) 引发错误,然后 x 不能被视为 P 。另一方面,一种转换 P(x) 一般都能做非常可恶的事情。所以,事实是 P(x) 没有错误的工作是必要的,但不足以 x in P

  2. 如果 P 是的父项 x ,然后转换 P(x) 不会改变 x (至少,这是默认设置)。因此,我们将拥有 x=P(x)

  3. Sage不仅在算术运算中使用强制,还在比较中使用强制: If 有来自父母的胁迫 xP ,然后进行相等性检验 x==P(x) 减少到 P(x)==P(x) 。否则, x==P(x) 将评估为FALSE。

这将导致元素包容测试的以下默认实现:

备注

x in P 保持当且仅当测试 x==P(x) 不引发错误并计算为True。

如果用户对这种行为不满意,那么可以使用“神奇的”Python方法 __contains__ 可以被覆盖。

胁迫-高级部分

到目前为止,我们可以将整数和有理数添加到分数字段的新实现的元素中 ZZ

sage: P = MyFrac(ZZ)
sage: 1/2+P(2,3)+1
(13):(6)

令人惊讶的是,我们甚至可以将整数上的多项式添加到 P ,即使是 result lives in a new parent ,即在环上多项式环中 P **

sage: P(1/2) + ZZ['x'].gen(), (P(1/2) + ZZ['x'].gen()).parent() is P['x']
((1):(1)*x + (1):(2), True)

在下一个看起来更简单的例子中,“显然”有一个来自分数域的强制 ZZ 的分数域 ZZ[x] 。然而,Sage对我们新实现的分数域还不够了解。因此,它不承认胁迫:

sage: Frac(ZZ['x']).has_coerce_map_from(P)
False

两个显而易见的问题出现了:

  1. 在上面的示例中,新环是如何/为什么构建的?

  2. 我们如何才能从 Pmathrm{Frac}(ZZ[x])

The key to answering both question is the construction of parents from simpler pieces, that we are studying now. Note that we will answer the second question not by providing a coercion from P to mathrm{Frac}(ZZ[x]), but by teaching Sage to automatically construct mathrm{MyFrac}(ZZ[x]) and coerce both P and mathrm{Frac}(ZZ[x]) into it.

如果我们幸运的话,父母可以告诉我们它是如何构建的:

sage: Poly,R = QQ['x'].construction()
sage: Poly,R
(Poly[x], Rational Field)
sage: Fract,R = QQ.construction()
sage: Fract,R
(FractionField, Integer Ring)

在这两种情况下,由 construction() 是一种数学结构,称为 construction functor -参见 ConstructionFunctor 。第二个返回值是应用构造函数的更简单的父函数。

作为函数式,相同的结构可以应用于同一类别的不同对象::

sage: Poly(QQ) is QQ['x']
True
sage: Poly(ZZ) is ZZ['x']
True
sage: Poly(P) is P['x']
True
sage: Fract(QQ['x'])
Fraction Field of Univariate Polynomial Ring in x over Rational Field

让我们看看这些构造函数是在哪些类别上定义的:

sage: Poly.domain()
Category of rings
sage: Poly.codomain()
Category of rings
sage: Fract.domain()
Category of integral domains
sage: Fract.codomain()
Category of fields

具体地说,施工人员可以组成::

sage: Poly*Fract
Poly[x](FractionField(...))
sage: (Poly*Fract)(ZZ) is QQ['x']
True

此外,通常假设构造函数器的输入到输出具有强制性:

sage: ((Poly*Fract)(ZZ)).coerce_map_from(ZZ)
Composite map:
  From: Integer Ring
  To:   Univariate Polynomial Ring in x over Rational Field
  Defn:   Natural morphism:
          From: Integer Ring
          To:   Rational Field
        then
          Polynomial base injection morphism:
          From: Rational Field
          To:   Univariate Polynomial Ring in x over Rational Field

施工人员不一定要通勤::

sage: (Fract*Poly)(ZZ)
Fraction Field of Univariate Polynomial Ring in x over Integer Ring

施工者的推出

我们现在可以阐明我们的问题了。我们有父母 P_1P_2R ,和施工人员 F_1F_2 ,以便 P_1 = F_1(R)P_2 = F_2(R) 。我们想找一个新的施工人员 F_3 ,使得两者都 P_1P_2 强行进入 P_3 = F_3(R)

类比范畴理论的概念, P_3 被称为 pushout()P_1P_2 ;以及类似的 F_3 被称为是对 F_1F_2 。**

sage: from sage.categories.pushout import pushout
sage: pushout(Fract(ZZ),Poly(ZZ))
Univariate Polynomial Ring in x over Rational Field

F_1circ F_2F_2circ F_1 理所当然地成为推动 F_1F_2 。然而,函数者的顺序必须依赖于规范的选择。“不可分解”构造函数器有一个 rank ,这允许以规范的方式对它们进行排序:

备注

如果 F1.rank 小于 F2.rank ,那么推出的是 F_2circ F_1 (因此, F_1 首先应用)。

我们有:

sage: Fract.rank, Poly.rank
(5, 9)

因此,推送是::

sage: Fract.pushout(Poly), Poly.pushout(Fract)
(Poly[x](FractionField(...)), Poly[x](FractionField(...)))

这就是上面的例子起作用的原因。

然而,只有“初级”施工人员才有一个级别::

sage: (Fract*Poly).rank
Traceback (most recent call last):
...
AttributeError: 'CompositeConstructionFunctor' object has no attribute 'rank'...
洗牌复合结构功能元件

If composed construction fuctors ...circ F_2circ F_1 and ...circ G_2circ G_1 are given, then Sage determines their pushout by shuffling the constituents:

  • If F1.rank < G1.rank then we apply F_1 first, and continue with ...circ F_3circ F_2 and ...circ G_2circ G_1.

  • If F1.rank > G1.rank then we apply G_1 first, and continue with ...circ F_2circ F_1 and ...circ G_3circ G_2.

如果 F1.rank == G1.rank ,然后需要用其他技术打破平局(见下文)。

作为一个例子,我们首先得到一些函数链,然后看看函数链是如何被打乱的。**

sage: AlgClos, R = CC.construction(); AlgClos
AlgebraicClosureFunctor
sage: Compl, R = RR.construction(); Compl
Completion[+Infinity, prec=53]
sage: Matr, R = (MatrixSpace(ZZ,3)).construction(); Matr
MatrixFunctor
sage: AlgClos.rank, Compl.rank, Fract.rank, Poly.rank, Matr.rank
(3, 4, 5, 9, 10)

当我们申请的时候 FractAlgClosPolyFract 对于整数环,我们得到::

sage: (Fract*Poly*AlgClos*Fract)(ZZ)
Fraction Field of Univariate Polynomial Ring in x over Algebraic Field

当我们申请的时候 ComplMatrPoly 对于整数环,我们得到::

sage: (Poly*Matr*Compl)(ZZ)
Univariate Polynomial Ring in x over Full MatrixSpace of 3 by 3 dense matrices over Integer Ring

应用洗牌程序产生:

sage: (Poly*Matr*Fract*Poly*AlgClos*Fract*Compl)(ZZ)
Univariate Polynomial Ring in x over Full MatrixSpace of 3 by 3 dense matrices over Fraction Field of Univariate Polynomial Ring in x over Algebraic Field

而这确实等同于Sage发现的推出::

sage: pushout((Fract*Poly*AlgClos*Fract)(ZZ), (Poly*Matr*Compl)(ZZ))
Univariate Polynomial Ring in x over Full MatrixSpace of 3 by 3 dense matrices over Fraction Field of Univariate Polynomial Ring in x over Algebraic Field
打破平局

如果 F1.rank==G1.rank 然后,Sage的Push-out构造提供了两种继续进行的方法:

  1. Construction functors have a method merge() that either returns None or returns a construction functor---see below. If either F1.merge(G1) or G1.merge(F1) returns a construction functor H_1, then we apply H_1 and continue with ...circ F_3circ F_2 and ...circ G_3circ G_2.

  2. Construction functors have a method commutes(). If either F1.commutes(G1) or G1.commutes(F1) returns True, then we apply both F_1 and G_1 in any order, and continue with ...circ F_3circ F_2 and ...circ G_3circ G_2.

默认情况下, F1.merge(G1) 退货 F1 如果 F1==G1 ,并返回 None 否则的话。这个 commutes() 方法,但是到目前为止,似乎还没有人实现过两个相同级别的函数器。

建立默认实现

的典型应用 merge() 就是提供一种胁迫 different implementationssame algebraic structure

备注

如果 F1(P)F2(P) 是同一事物的不同实现,那么 F1.merge(F2)(P) 应返回默认实现。

我们希望大胆地将分数字段的玩具实现转换为新的默认实现。因此:

  • 接下来,我们实现“通常的”分数字段函数器的一个新版本,具有相同的等级,但返回我们的新实现。

  • 我们通过Merge方法将我们的新实现设为默认实现。

  • 因为我们分数字段接受可选参数 category ,我们将可选参数传递给构造函数器,构造函数器将使用它来创建分数字段。

警告

  • 请勿覆盖默认设置 __call__ 方法论 ConstructionFunctor -实施 _apply_functor 取而代之的是。

  • 在初始化过程中声明函数器的域和共域。

sage: from sage.categories.pushout import ConstructionFunctor
sage: class MyFracFunctor(ConstructionFunctor):
....:     rank = 5
....:     def __init__(self, args=None, kwds=None):
....:         self.args = args or ()
....:         self.kwds = kwds or {}
....:         ConstructionFunctor.__init__(self, IntegralDomains(), Fields())
....:     def _apply_functor(self, R):
....:         return MyFrac(R,*self.args,**self.kwds)
....:     def merge(self, other):
....:         if isinstance(other, (type(self), sage.categories.pushout.FractionField)):
....:             return self
sage: MyFracFunctor()
MyFracFunctor

我们验证了我们的函数器确实可以用来构造分数域的实现,并且它可以与其自身或通常的分数域构造器合并:

sage: MyFracFunctor()(ZZ)
NewFrac(Integer Ring)
sage: MyFracFunctor().merge(MyFracFunctor())
MyFracFunctor
sage: MyFracFunctor().merge(Fract)
MyFracFunctor

还需要让我们的新分数领域了解新的构造函数器。创建分数字段时使用的参数将存储为属性-这是由提供的功能 CachedRepresentation 。我们将这些参数中除第一个以外的所有参数传递给构造函数器,以便构造函数器能够重建分数域。

sage: class MyFrac(MyFrac):
....:     def construction(self):
....:         return MyFracFunctor(self._reduction[1][1:], self._reduction[2]), self.base()
sage: MyFrac(ZZ['x']).construction()
(MyFracFunctor, Univariate Polynomial Ring in x over Integer Ring)

由于合并,我们拥有:

sage: pushout(MyFrac(ZZ['x']), Frac(QQ['x']))
NewFrac(Univariate Polynomial Ring in x over Rational Field)

关于性能的第二个注解

能够进行涉及不同父代元素的算术运算,并自动创建推送来包含结果,这当然很方便-但如果速度很重要,就不应该依赖它。仅仅是将元素转换为不同的父元素就需要时间。此外,通过 :issue:`14058` ,推送可能会受到Python的循环垃圾回收的影响。因此,如果不保持对它的强引用,可能会重复创建相同的父对象,这是浪费时间。在下面的示例中,我们说明了由于盲目依赖强制而导致的速度减慢:

sage: ZZxy = ZZ['x','y']
sage: a = ZZxy('x')
sage: b = 1/2
sage: timeit("c = a+b")    # random
10000 loops, best of 3: 172 us per loop
sage: QQxy = QQ['x','y']
sage: timeit("c2 = QQxy(a)+QQxy(b)") # random
10000 loops, best of 3: 168 us per loop
sage: a2 = QQxy(a)
sage: b2 = QQxy(b)
sage: timeit("c2 = a2+b2") # random
100000 loops, best of 3: 10.5 us per loop

因此,如果避免显式或隐式转换为推出,而是立即在推出中工作,则可以获得10倍以上的速度提升。

类别框架的测试套件

类别框架不仅提供功能,还提供测试框架。这一节在逻辑上属于类别一节,但如果没有我们在强制一节中实现的BITS,我们的分数字段的实现还不会通过测试。

“抽象”方法

我们在上面已经看到,类别可能需要/建议用户必须/应该实现的某些父方法或元素方法。这是为了与Sage中已经存在的方法顺利地融合在一起。

应该提供的方法被调用 abstract_method() 。让我们看看商字段及其元素需要哪些方法:

sage: from sage.misc.abstract_method import abstract_methods_of_class
sage: abstract_methods_of_class(QuotientFields().parent_class)['optional']
[]
sage: abstract_methods_of_class(QuotientFields().parent_class)['required']
['__contains__']

因此,唯一需要的方法(实际上是属于集合类别的所有父级都需要的)是元素包容测试。这很好,因为基类 Parent 提供默认的包容测试。

元素必须提供更多::

sage: abstract_methods_of_class(QuotientFields().element_class)['optional']
['_add_', '_mul_']
sage: abstract_methods_of_class(QuotientFields().element_class)['required']
['__bool__', 'denominator', 'numerator']

因此,元素必须提供 denominator()numerator() 方法,并且必须能够区分它们是否为零。基类 Element 提供默认设置 __bool__() 方法。此外,元素可以提供Sage的单下划线算术方法(实际上是任何环元素 should 提供它们)。

这个 _test_... 方法

如果父方法或元素方法的名称以“_test_”开头,则会在自动测试套件中生成一个测试。例如,它被测试

  • 无论是父母 P 实际上是类别的父类的实例 P

  • 用户是否已经实现了所需的抽象方法,

  • 某些定义的结构属性(例如,交换性)是否成立。

例如,如果忘记实现所需的方法,则会出现以下错误:

sage: class Foo(Parent):
....:  Element = sage.structure.element.Element
....:  def __init__(self):
....:      Parent.__init__(self, category=QuotientFields())
sage: Bar = Foo()
sage: bar = Bar.element_class(Bar)
sage: bar._test_not_implemented_methods()
Traceback (most recent call last):
...
AssertionError: Not implemented method: denominator

以下是构成商字段测试套件的测试:

sage: [t for t in dir(QuotientFields().parent_class) if t.startswith('_test_')]
['_test_additive_associativity',
 '_test_an_element',
 '_test_associativity',
 '_test_cardinality',
 '_test_characteristic',
 '_test_characteristic_fields',
 '_test_construction',
 '_test_distributivity',
 '_test_divides',
 '_test_elements',
 '_test_elements_eq_reflexive',
 '_test_elements_eq_symmetric',
 '_test_elements_eq_transitive',
 '_test_elements_neq',
 '_test_euclidean_degree',
 '_test_fraction_field',
 '_test_gcd_vs_xgcd',
 '_test_one',
 '_test_prod',
 '_test_quo_rem',
 '_test_some_elements',
 '_test_zero',
 '_test_zero_divisors']

我们实现了所有抽象方法(或从基类继承它们),我们使用了类别框架,并且我们实现了强制。因此,我们确信测试套件运行时没有错误。事实上,的确如此!

备注

下面的技巧使用 __main__ 模块只在文档测试中需要,在交互会话中或在外部定义类时不需要。

sage: __main__.MyFrac = MyFrac
sage: __main__.MyElement = MyElement
sage: P = MyFrac(ZZ['x'])
sage: TestSuite(P).run()

让我们来看看实际执行了哪些测试::

sage: TestSuite(P).run(verbose=True)
running ._test_additive_associativity() . . . pass
running ._test_an_element() . . . pass
running ._test_associativity() . . . pass
running ._test_cardinality() . . . pass
running ._test_category() . . . pass
running ._test_characteristic() . . . pass
running ._test_characteristic_fields() . . . pass
running ._test_construction() . . . pass
running ._test_distributivity() . . . pass
running ._test_divides() . . . pass
running ._test_elements() . . .
  Running the test suite of self.an_element()
  running ._test_category() . . . pass
  running ._test_eq() . . . pass
  running ._test_new() . . . pass
  running ._test_nonzero_equal() . . . pass
  running ._test_not_implemented_methods() . . . pass
  running ._test_pickling() . . . pass
  pass
running ._test_elements_eq_reflexive() . . . pass
running ._test_elements_eq_symmetric() . . . pass
running ._test_elements_eq_transitive() . . . pass
running ._test_elements_neq() . . . pass
running ._test_eq() . . . pass
running ._test_euclidean_degree() . . . pass
running ._test_fraction_field() . . . pass
running ._test_gcd_vs_xgcd() . . . pass
running ._test_new() . . . pass
running ._test_not_implemented_methods() . . . pass
running ._test_one() . . . pass
running ._test_pickling() . . . pass
running ._test_prod() . . . pass
running ._test_quo_rem() . . . pass
running ._test_some_elements() . . . pass
running ._test_zero() . . . pass
running ._test_zero_divisors() . . . pass

使用附加测试实现新类别

正如人们所看到的,测试也是在元素上执行的。有一些方法返回一个元素或一些元素的列表,依赖于在大多数代数结构中可以找到的“典型”元素。**

sage: P.an_element(); P.some_elements()
(2):(1)
[(2):(1)]

遗憾的是,默认方法返回的元素列表的长度为1,而单个元素也可能更有趣一些。方法an_Element依赖于一个方法 _an_element_() ,因此,我们实现了这一点。我们还覆盖了Some_Elements方法。**

sage: class MyFrac(MyFrac):
....:     def _an_element_(self):
....:         a = self.base().an_element()
....:         b = self.base_ring().an_element()
....:         if (a+b)!=0:
....:             return self(a)**2/(self(a+b)**3)
....:         if b != 0:
....:             return self(a)/self(b)**2
....:         return self(a)**2*self(b)**3
....:     def some_elements(self):
....:         return [self.an_element(),self(self.base().an_element()),self(self.base_ring().an_element())]
sage: P = MyFrac(ZZ['x'])
sage: P.an_element(); P.some_elements()
(x^2):(x^3 + 3*x^2 + 3*x + 1)
[(x^2):(x^3 + 3*x^2 + 3*x + 1), (x):(1), (1):(1)]

现在,随着我们有了更多有趣的元素,我们还可以为“因子”方法添加一个测试。回想一下,该方法是从类别继承而来的,但它似乎没有经过测试。

通常,对由类别定义的方法的测试应该由同一类别提供。因此,既然 factor 在商字段类别中定义,则应在那里添加一个测试。但是我们不会在这里更改源代码,而是创建子--类别。

显然,如果 e 是商域的元素,是由返回的因数的乘积 e.factor() 应等于 e 。为了形成产品,我们使用 prod 方法,毫不奇怪,它继承自另一个类别::

sage: P.prod.__module__
'sage.categories.monoids'

当我们想要创建子类别时,我们需要提供一种方法 super_categories() ,它返回所有直接超级类别的列表(此处:商字段类别)。

警告

A sub--category S of a category C is not implemented as a sub--class of C.__class__! S becomes a sub--category of C only if S.super_categories() returns (a sub--category of) C!

类别的父方法和元素方法作为作为属性的类的方法提供 ParentMethodsElement Methods 这一类别的名称如下:

sage: from sage.categories.category import Category
sage: class QuotientFieldsWithTest(Category): # do *not* inherit from QuotientFields, but ...
....:     def super_categories(self):
....:         return [QuotientFields()]       # ... declare QuotientFields as a super category!
....:     class ParentMethods:
....:         pass
....:     class ElementMethods:
....:         def _test_factorisation(self, **options):
....:             P = self.parent()
....:             assert self == P.prod([P(b)**e for b,e in self.factor()])

我们提供了一个具有该新类别的商字段实现的实例。请注意,类别具有缺省值 _repr_ 方法,该方法从类名中猜测出一个好的字符串表示形式: QuotientFieldsWithTest 变成了“有测试的商域”。

备注

下面的技巧使用 __main__ 模块只在文档测试中需要,在交互会话中或在外部定义类时不需要。

sage: __main__.MyFrac = MyFrac
sage: __main__.MyElement = MyElement
sage: __main__.QuotientFieldsWithTest = QuotientFieldsWithTest
sage: P = MyFrac(ZZ['x'], category=QuotientFieldsWithTest())
sage: P.category()
Category of quotient fields with test

新的测试是从类别继承的。自.以来 an_element() 返回一个复杂的元素, _test_factorisation 是一个严峻的考验::

sage: P.an_element()._test_factorisation
<bound method QuotientFieldsWithTest.ElementMethods._test_factorisation of (x^2):(x^3 + 3*x^2 + 3*x + 1)>
sage: P.an_element().factor()
(x + 1)^-3 * x^2

最后,我们观察到新测试已自动成为测试套件的一部分。我们注意到,现有的测试也变得更加严重,因为我们做出了 sage.structure.parent.Parent.an_element() 返回一些更有趣的东西。**

sage: TestSuite(P).run(verbose=True)
running ._test_additive_associativity() . . . pass
running ._test_an_element() . . . pass
running ._test_associativity() . . . pass
running ._test_cardinality() . . . pass
running ._test_category() . . . pass
running ._test_characteristic() . . . pass
running ._test_characteristic_fields() . . . pass
running ._test_construction() . . . pass
running ._test_distributivity() . . . pass
running ._test_divides() . . . pass
running ._test_elements() . . .
  Running the test suite of self.an_element()
  running ._test_category() . . . pass
  running ._test_eq() . . . pass
  running ._test_factorisation() . . . pass
  running ._test_new() . . . pass
  running ._test_nonzero_equal() . . . pass
  running ._test_not_implemented_methods() . . . pass
  running ._test_pickling() . . . pass
  pass
running ._test_elements_eq_reflexive() . . . pass
running ._test_elements_eq_symmetric() . . . pass
running ._test_elements_eq_transitive() . . . pass
running ._test_elements_neq() . . . pass
running ._test_eq() . . . pass
running ._test_euclidean_degree() . . . pass
running ._test_fraction_field() . . . pass
running ._test_gcd_vs_xgcd() . . . pass
running ._test_new() . . . pass
running ._test_not_implemented_methods() . . . pass
running ._test_one() . . . pass
running ._test_pickling() . . . pass
running ._test_prod() . . . pass
running ._test_quo_rem() . . . pass
running ._test_some_elements() . . . pass
running ._test_zero() . . . pass
running ._test_zero_divisors() . . . pass

附录:完整代码

  1# Importing base classes, ...
  2import sage
  3from sage.rings.ring import Field
  4from sage.structure.element import FieldElement
  5from sage.categories.category import Category
  6# ... the UniqueRepresentation tool,
  7from sage.structure.unique_representation import UniqueRepresentation
  8# ... some categories, and ...
  9from sage.categories.fields import Fields
 10from sage.categories.quotient_fields import QuotientFields
 11from sage.categories.integral_domains import IntegralDomains
 12# construction functors
 13from sage.categories.pushout import ConstructionFunctor
 14
 15# Fraction field elements
 16class MyElement(FieldElement):
 17    def __init__(self, parent, n, d=None):
 18        if parent is None:
 19            raise ValueError("The parent must be provided")
 20        B = parent.base()
 21        if d is None:
 22            # The default denominator is one
 23            d = B.one()
 24        # verify that both numerator and denominator belong to the base
 25        if n not in B or d not in B:
 26            raise ValueError("Numerator and denominator must be elements of %s"%B)
 27        # Numerator and denominator should not just be "in" B,
 28        # but should be defined as elements of B
 29        d = B(d)
 30        n = B(n)
 31        # the denominator must not be zero
 32        if d==0:
 33            raise ZeroDivisionError("The denominator must not be zero")
 34        # normalize the denominator: WLOG, it shall be non-negative.
 35        if d<0:
 36            self.n = -n
 37            self.d = -d
 38        else:
 39            self.n = n
 40            self.d = d
 41        FieldElement.__init__(self,parent)
 42
 43    # Methods required by the category of fraction fields:
 44    def numerator(self):
 45        return self.n
 46    def denominator(self):
 47        return self.d
 48
 49    # String representation (single underscore!)
 50    def _repr_(self):
 51        return "(%s):(%s)"%(self.n,self.d)
 52
 53    # Comparison: We can assume that both arguments are coerced
 54    # into the same parent, which is a fraction field. Hence, we
 55    # are allowed to use the denominator() and numerator() methods
 56    # on the second argument.
 57    def _richcmp_(self, other, op):
 58        from sage.structure.richcmp import richcmp
 59        return richcmp(self.n*other.denominator(), other.numerator()*self.d, op)
 60
 61    # Arithmetic methods, single underscore. We can assume that both
 62    # arguments are coerced into the same parent.
 63    # We return instances of self.__class__, because self.__class__ will
 64    # eventually be a sub-class of MyElement.
 65    def _add_(self, other):
 66        C = self.__class__
 67        D = self.d*other.denominator()
 68        return C(self.parent(), self.n*other.denominator()+self.d*other.numerator(),D)
 69    def _sub_(self, other):
 70        C = self.__class__
 71        D = self.d*other.denominator()
 72        return C(self.parent(), self.n*other.denominator()-self.d*other.numerator(),D)
 73    def _mul_(self, other):
 74        C = self.__class__
 75        return C(self.parent(), self.n*other.numerator(), self.d*other.denominator())
 76    def _div_(self, other):
 77        C = self.__class__
 78        return C(self.parent(), self.n*other.denominator(), self.d*other.numerator())
 79
 80# Inheritance from UniqueRepresentation implements the unique parent
 81# behaviour. Moreover, it implements pickling (provided that Python
 82# succeeds to look up the class definition).
 83class MyFrac(UniqueRepresentation, Field):
 84    # Implement the category framework for elements, which also
 85    # makes some basic conversions work.
 86    Element = MyElement
 87
 88    # Allow to pass to a different category, by an optional argument
 89    def __init__(self, base, category=None):
 90        # Fraction fields only exist for integral domains
 91        if base not in IntegralDomains():
 92            raise ValueError("%s is no integral domain" % base)
 93        # Implement the category framework for the parent
 94        Field.__init__(self, base, category=category or QuotientFields())
 95
 96    # Single-underscore method for string representation
 97    def _repr_(self):
 98        return "NewFrac(%s)"%repr(self.base())
 99
100    # Two methods that are implicitly used in some tests
101    def base_ring(self):
102        return self.base().base_ring()
103    def characteristic(self):
104        return self.base().characteristic()
105
106    # Implement conversions. Do not override __call__!
107    def _element_constructor_(self, *args, **kwds):
108        if len(args)!=1:
109           return self.element_class(self, *args, **kwds)
110        x = args[0]
111        try:
112            P = x.parent()
113        except AttributeError:
114            return self.element_class(self, x, **kwds)
115        if P in QuotientFields() and P != self.base():
116            return self.element_class(self, x.numerator(), x.denominator(), **kwds)
117        return self.element_class(self, x, **kwds)
118
119    # Implement coercion from the base and from fraction fields
120    # over a ring that coerces into the base
121    def _coerce_map_from_(self, S):
122        if self.base().has_coerce_map_from(S):
123            return True
124        if S in QuotientFields():
125            if self.base().has_coerce_map_from(S.base()):
126                return True
127            if hasattr(S,'ring_of_integers') and self.base().has_coerce_map_from(S.ring_of_integers()):
128                return True
129    # Tell how this parent was constructed, in order to enable pushout constructions
130    def construction(self):
131        return MyFracFunctor(), self.base()
132
133    # return some elements of this parent
134    def _an_element_(self):
135        a = self.base().an_element()
136        b = self.base_ring().an_element()
137        if (a+b)!=0:
138            return self(a)**2/(self(a+b)**3)
139        if b != 0:
140            return self(a)/self(b)**2
141        return self(a)**2*self(b)**3
142    def some_elements(self):
143        return [self.an_element(),self(self.base().an_element()),self(self.base_ring().an_element())]
144
145
146# A construction functor for our implementation of fraction fields
147class MyFracFunctor(ConstructionFunctor):
148    # The rank is the same for Sage's original fraction field functor
149    rank = 5
150    def __init__(self):
151        # The fraction field construction is a functor
152        # from the category of integral domains into the category of
153        # fields
154        # NOTE: We could actually narrow the codomain and use the
155        # category QuotientFields()
156        ConstructionFunctor.__init__(self, IntegralDomains(), Fields())
157    # Applying the functor to an object. Do not override __call__!
158    def _apply_functor(self, R):
159        return MyFrac(R)
160    # Note: To apply the functor to morphisms, implement
161    #       _apply_functor_to_morphism
162
163    # Make sure that arithmetic involving elements of Frac(R) and
164    # MyFrac(R) works and yields elements of MyFrac(R)
165    def merge(self, other):
166        if isinstance(other, (type(self), sage.categories.pushout.FractionField)):
167            return self
168
169# A quotient field category with additional tests.
170# Notes:
171# - Category inherits from UniqueRepresentation. Hence, there
172#   is only one category for given arguments.
173# - Since QuotientFieldsWithTest is a singleton (there is only
174#   one instance of this class), we could inherit from
175#   sage.categories.category_singleton.Category_singleton
176#   rather than from sage.categories.category.Category
177class QuotientFieldsWithTest(Category):
178    # Our category is a sub-category of the category of quotient fields,
179    # by means of the following method.
180    def super_categories(self):
181        return [QuotientFields()]
182
183    # Here, we could implement methods that are available for
184    # all objects in this category.
185    class ParentMethods:
186        pass
187
188    # Here, we add a new test that is available for all elements
189    # of any object in this category.
190    class ElementMethods:
191        def _test_factorisation(self, **options):
192            P = self.parent()
193            # The methods prod() and factor() are inherited from
194            # some other categories.
195            assert self == P.prod([P(b)**e for b,e in self.factor()])