GP/PARI

PARI是一个紧凑的,非常成熟的,高度优化主要关注数论的C语言程序,在Sage中有两种不同的接口可以调用:

  • gp- “G o P ARI” 解释器
  • pari- PARI的C语言库。

例如,下面两种方法做同一件事情。它们看上去相同,但是输出完全不同,而且背后所发生的事情也完全不同。

  1. sage: gp('znprimroot(10007)')
  2. Mod(5, 10007)
  3. sage: pari('znprimroot(10007)')
  4. Mod(5, 10007)

前者,一个独立的GP解释器作为服务器运行,字符串'znprimroot(10007)'传给它,由GP执行,并将结果赋给一个GP变量(占用GP子进程的内存空间并且不会被释放)。然后变量的值显示出来。后者,没有运行单独的程序,字符串'znprimroot(10007)'由特定的PARI的C语言库函数执行。结果存储在Python的堆中,当不再使用时会被释放。两个对象的类型不同:

  1. sage: type(gp('znprimroot(10007)'))
  2. <class 'sage.interfaces.gp.GpElement'>
  3. sage: type(pari('znprimroot(10007)'))
  4. <type 'sage.libs.pari.gen.gen'>

那么应该用哪一个?取决于你要做什么。在通常GP/PARI命令行中可以做在GP接口中做的任何事情,因为运行的就是那个程序。特别的,你可以载入复杂的PARI程序并执行。相反,PARI接口(基于C语言库)的限制更多。首先,并没有实现所有的成员函数。第二,很多代码,比如涉及数值积分的,不能直接通过PARI接口执行。也可以说,PARI接口方式比GP接口方式更快,稳定性更好。

(如果GP接口方式执行一个给定的输入行时内存溢出,它会自动将堆栈大小翻番,并重试。这样,如果你没有正确预计所需的内存量,计算也不会崩溃。这是一个不错的技术,但是通常的GP解释器并不提供。对于PARI的C语言库接口,它会立即将已建立的对象复制出堆栈,这样堆栈就不会再增长了。但是每一个对象不能超过100MB,否则建立这个对象的时候,堆栈就会溢出。额外的复制操作会稍微影响性能。)

总之,除了使用不同的高级内存管理策略和Python语言之外,Sage使用PARI的C语言库所提供的功能与GP/PARI解释器所提供的功能接近。

首先我们从Python列表新建一个PARI列表。

  1. sage: v = pari([1,2,3,4,5])
  2. sage: v
  3. [1, 2, 3, 4, 5]
  4. sage: type(v)
  5. <type 'cypari2.gen.Gen'>

每一个PARI对象都是Gen类型的。 基本对象的PARI类型可由成员函数type得到。

  1. sage: v.type()
  2. 't_VEC'

在PARI中,使用ellinit([1,2,3,4,5])新建一个椭圆曲线。Sage中是类似的,只是ellinit是一个方法,可由任何PARI对象调用,如,我们的t_VEC$v$。

  1. sage: e = v.ellinit()
  2. sage: e.type()
  3. 't_VEC'
  4. sage: pari(e)[:13]
  5. [1, 2, 3, 4, 5, 9, 11, 29, 35, -183, -3429, -10351, 6128487/10351]

现在我们有了一个椭圆曲线对象,可以对它进行一些运算。

  1. sage: e.elltors()
  2. [1,[],[]]
  3. sage: e.ellglobalred()
  4. [10351,[1, -1, 0, -1], 1]
  5. sage: f = e.ellchangecurve([1,-1,0,-1])
  6. sage: f[:5]
  7. [1, -1, 0, 4, 3]