编程

加载和附加Sage文件

接下来我们将演示如何将单独文件中编写的程序加载到Sage中。创建一个名为 example.sage with the following content:

print("Hello World")
print(2^3)

你可以读入并执行 example.sage 使用文件 load 命令。

sage: load("example.sage")
Hello World
8

也可以使用将Sage文件附加到正在运行的会话 attach 命令:

sage: attach("example.sage")
Hello World
8

如果你改变了 example.sage 在Sage中输入一个空白行(即 return ),则 example.sage 将自动重新加载到Sage中。

特别地, attach 每当文件发生更改时自动重新加载文件,这在调试代码时很方便,而 load 只加载一次文件。

当Sage装载 example.sage 它将其转换为Python,然后由Python解释器执行。这种转换是最小的;它主要涉及在 Integer() 浮点字面值 RealNumber() ,替换 ^ 是的 ** 和替换,例如。, R.2 通过 R.gen(2) . 的转换版本 example.sage 与包含在同一目录中 example.sage 被称为 example.sage.py . 此文件包含以下代码:

print("Hello World")
print(Integer(2)**Integer(3))

整型文字被包装,并且 ^ 替换为 ** . (在Python中 ^ 意思是“排他或”和 ** 意思是“指数化”。)

(此准备在 sage/misc/interpreter.py

您可以将多行缩进的代码粘贴到Sage中,只要有新行来生成新的块(在文件中这是不必要的)。然而,将此类代码输入Sage的最佳方法是将其保存到一个文件中并使用 attach ,如上所述。

创建编译代码

速度在数学计算中是至关重要的。虽然Python是一种非常方便的高级语言,但是如果在编译语言中使用静态类型实现某些计算,那么它们可以比Python快几个数量级。如果Sage是完全用Python编写的,那么它的某些方面就太慢了。为了解决这个问题,Sage支持Python的编译“版本” ([Cyt][Pyr]) . Cython同时类似于Python和C。大多数Python结构,包括列表理解、条件表达式、类似代码的 += 允许;您还可以导入在其他Python模块中编写的代码。此外,您可以声明任意C变量,并且可以直接进行任意C库调用。生成的代码被转换成C语言并使用C编译器进行编译。

为了使您自己编译的Sage代码,给文件一个 .spyx 扩展(而不是 .sage ). 如果您使用的是命令行界面,那么您可以像使用解释代码一样附加和加载编译的代码(目前,notebook接口不支持附加和加载Cython代码)。实际的编译是在“幕后”完成的,而不必做任何明确的操作。编译的共享对象库存储在 $HOME/.sage/temp/hostname/pid/spyx . 当您退出Sage时,这些文件将被删除。

没有Sage preparsing应用于spyx文件,例如。, 1/3 将在spyx文件中生成0而不是有理数 \(1/3\) .如果 foo 是Sage库中的函数,用于从spyx文件导入使用它 sage.all 使用 sage.all.foo .

import sage.all
def foo(n):
    return sage.all.factorial(n)

访问单独文件中的C函数

访问单独定义的C函数也很容易 * .c文件。这里有一个例子。创建文件 test.ctest.spyx 在包含以下内容的同一目录中:

纯C代码: test.c

int add_one(int n) {
  return n + 1;
}

赛顿密码: test.spyx

cdef extern from "test.c":
    int add_one(int n)

def test(n):
    return add_one(n)

那么下面的工作就可以了:

sage: attach("test.spyx")
Compiling (...)/test.spyx...
sage: test(10)
11

如果有额外的库 foo 需要编译从Cython文件生成的C代码时,添加行 clib foo 到Cython的源头。类似地,一个附加的C文件 bar 可以在编译中加入声明 cfile bar .

独立的Python/Sage脚本

以下独立的Sage脚本因子为整数、多项式等:

#!/usr/bin/env sage

import sys
from sage.all import *

if len(sys.argv) != 2:
    print("Usage: %s <n>" % sys.argv[0])
    print("Outputs the prime factorization of n.")
    sys.exit(1)

print(factor(sage_eval(sys.argv[1])))

为了使用这个脚本 SAGE_ROOT 一定在你的路上。如果调用上述脚本 factor ,以下是用法示例:

$ ./factor 2006
2 * 17 * 59

数据类型

Sage中的每个对象都有一个明确的类型。Python有许多基本的内置类型,Sage库还添加了更多的类型。一些内置的Python类型包括字符串、列表、元组、int和float,如图所示:

sage: s = "sage"; type(s)
<... 'str'>
sage: s = 'sage'; type(s)      # you can use either single or double quotes
<... 'str'>
sage: s = [1,2,3,4]; type(s)
<... 'list'>
sage: s = (1,2,3,4); type(s)
<... 'tuple'>
sage: s = int(2006); type(s)
<... 'int'>
sage: s = float(2006); type(s)
<... 'float'>

除此之外,Sage还添加了许多其他类型。E、 g.,向量空间:

sage: V = VectorSpace(QQ, 1000000); V
Vector space of dimension 1000000 over Rational Field
sage: type(V)
<class 'sage.modules.free_module.FreeModule_ambient_field_with_category'>

只能调用某些函数 V . 在其他的数学软件系统中,这些将被称为使用“函数”符号 foo(V,...) . 在Sage中,某些函数附加在 V ,并且使用像java或C++那样的面向对象语法,例如 V.foo(...) . 这有助于防止全局命名空间被成千上万个函数污染,并意味着许多具有不同行为的不同函数可以命名为foo,而不必使用参数(或case语句)的类型检查来决定调用哪个函数。另外,如果重用函数的名称,该函数仍然可用(例如,如果调用某个函数 zeta ,然后要计算Riemann-Zeta函数的值为0.5,仍然可以输入 s=.5; s.zeta()

sage: zeta = -1
sage: s=.5; s.zeta()
-1.46035450880959

在一些非常常见的情况下,为了方便起见,还支持通常的函数表示法,因为使用面向对象的表示法时,数学表达式可能看起来很混乱。这里有一些例子。

sage: n = 2; n.sqrt()
sqrt(2)
sage: sqrt(2)
sqrt(2)
sage: V = VectorSpace(QQ,2)
sage: V.basis()
    [
    (1, 0),
    (0, 1)
    ]
sage: basis(V)
    [
    (1, 0),
    (0, 1)
    ]
sage: M = MatrixSpace(GF(7), 2); M
Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 7
sage: A = M([1,2,3,4]); A
[1 2]
[3 4]
sage: A.charpoly('x')
x^2 + 2*x + 5
sage: charpoly(A, 'x')
x^2 + 2*x + 5

列出的所有成员函数 \(A\) ,使用制表符完成。只是打字 A. ,然后键入 [tab] 键盘上的键,如中所述 反向搜索和制表符完成 .

列表、元组和序列

列表数据类型存储任意类型的元素。与C、C++等类似(但与大多数标准的计算机代数系统不同),列表的元素从 \(0\)

sage: v = [2, 3, 5, 'x', SymmetricGroup(3)]; v
[2, 3, 5, 'x', Symmetric group of order 3! as a permutation group]
sage: type(v)
<... 'list'>
sage: v[0]
2
sage: v[2]
5

(当索引到列表中时,如果索引不是Python int就可以了!)一个聪明的整数(或有理数,或任何带有 __index__ 方法)会很好地工作。

sage: v = [1,2,3]
sage: v[2]
3
sage: n = 2      # SAGE Integer
sage: v[n]       # Perfectly OK!
3
sage: v[int(n)]  # Also OK.
3

这个 range 函数创建Python int的列表(不是Sage整数):

sage: range(1, 15)  # py2
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

这在使用列表理解构造列表时非常有用:

sage: L = [factor(n) for n in range(1, 15)]
sage: L
[1, 2, 3, 2^2, 5, 2 * 3, 7, 2^3, 3^2, 2 * 5, 11, 2^2 * 3, 13, 2 * 7]
sage: L[12]
13
sage: type(L[12])
<class 'sage.structure.factorization_integer.IntegerFactorization'>
sage: [factor(n) for n in range(1, 15) if is_odd(n)]
[1, 3, 5, 7, 3^2, 11, 13]

有关如何使用列表理解创建列表的更多信息,请参见 [PyT].

列表切片是一个很好的特性。如果 L 是一张单子 L[m:n] 返回的子列表 L\(m^{{th}}\) 元素并在 \((n-1)^{{st}}\) 元素,如下所示。

sage: L = [factor(n) for n in range(1, 20)]
sage: L[4:9]
[5, 2 * 3, 7, 2^3, 3^2]
sage: L[:4]
[1, 2, 3, 2^2]
sage: L[14:4]
[]
sage: L[14:]
[3 * 5, 2^4, 17, 2 * 3^2, 19]

元组类似于列表,只是它们是不可变的,这意味着一旦它们被创建,它们就不能被更改。

sage: v = (1,2,3,4); v
(1, 2, 3, 4)
sage: type(v)
<... 'tuple'>
sage: v[1] = 5
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment

序列是第三种面向列表的Sage类型。与列表和元组不同,Sequence不是内置的Python类型。默认情况下,序列是可变的,但是使用 Sequence 类方法 set_immutable ,可以将其设置为不可变的,如下例所示。一个序列的所有元素都有一个共同的父元素,称为序列宇宙。

sage: v = Sequence([1,2,3,4/5])
sage: v
[1, 2, 3, 4/5]
sage: type(v)
<class 'sage.structure.sequence.Sequence_generic'>
sage: type(v[1])
<type 'sage.rings.rational.Rational'>
sage: v.universe()
Rational Field
sage: v.is_immutable()
False
sage: v.set_immutable()
sage: v[0] = 3
Traceback (most recent call last):
...
ValueError: object is immutable; please change a copy instead.

序列源于列表,可用于任何可以使用列表的位置:

sage: v = Sequence([1,2,3,4/5])
sage: isinstance(v, list)
True
sage: list(v)
[1, 2, 3, 4/5]
sage: type(list(v))
<... 'list'>

另一个例子是,向量空间的基础是不可变的序列,因为不改变它们很重要。

sage: V = QQ^3; B = V.basis(); B
[
(1, 0, 0),
(0, 1, 0),
(0, 0, 1)
]
sage: type(B)
<class 'sage.structure.sequence.Sequence_generic'>
sage: B[0] = B[1]
Traceback (most recent call last):
...
ValueError: object is immutable; please change a copy instead.
sage: B.universe()
Vector space of dimension 3 over Rational Field

辞典

字典(有时也称为关联数组)是“散列”对象(例如字符串、数字和元组)的映射;请参阅Python文档http://docs.python.org/tut/node7.html以及http://docs.python.org/lib/typesmapping.html以获取详细信息)。

sage: d = {1:5, 'sage':17, ZZ:GF(7)}
sage: type(d)
<... 'dict'>
sage: list(d.keys())
[1, 'sage', Integer Ring]
sage: d['sage']
17
sage: d[ZZ]
Finite Field of size 7
sage: d[1]
5

第三个键说明字典的索引可以很复杂,例如整数环。

您可以将上述字典转换为具有相同数据的列表:

sage: list(d.items())
[(1, 5), ('sage', 17), (Integer Ring, Finite Field of size 7)]

一个常见的习惯用法是在字典中重复对:

sage: d = {2:4, 3:9, 4:16}
sage: [a*b for a, b in d.items()]
[8, 27, 64]

如最后一个输出所示,字典是无序的。

集合

Python有一个内置的set类型。它提供的主要功能是快速查找元素是否在集合中,以及标准的集合论运算。

sage: X = set([1,19,'a']);   Y = set([1,1,1, 2/3])
sage: X   # random sort order
{1, 19, 'a'}
sage: X == set(['a', 1, 1, 19])
True
sage: Y
{2/3, 1}
sage: 'a' in X
True
sage: 'a' in Y
False
sage: X.intersection(Y)
{1}

Sage也有自己的set类型(在某些情况下)使用内置的Python set类型实现,但是有一些额外的Sage相关功能。使用创建Sage集 Set(...) . 例如,

sage: X = Set([1,19,'a']);   Y = Set([1,1,1, 2/3])
sage: X   # random sort order
{'a', 1, 19}
sage: X == Set(['a', 1, 1, 19])
True
sage: Y
{1, 2/3}
sage: X.intersection(Y)
{1}
sage: print(latex(Y))
\left\{1, \frac{2}{3}\right\}
sage: Set(ZZ)
Set of elements of Integer Ring

遍历器

迭代器是Python的新成员,在数学应用中特别有用。这里有几个示例;请参见 [PyT] 更多细节。我们在非负整数的平方上做一个迭代器 \(10000000\) .

sage: v = (n^2 for n in xrange(10000000))  # py2
sage: v = (n^2 for n in range(10000000))  # py3
sage: next(v)
0
sage: next(v)
1
sage: next(v)
4

我们在形式的素数上创建一个迭代 \(4p+1\) 具有 \(p\) 同样是素数,看看前几个值。

sage: w = (4*p + 1 for p in Primes() if is_prime(4*p+1))
sage: w         # in the next line, 0xb0853d6c is a random 0x number
<generator object at 0xb0853d6c>
sage: next(w)
13
sage: next(w)
29
sage: next(w)
53

某些环,例如有限域和整数都有与其相关联的迭代器:

sage: [x for x in GF(7)]
[0, 1, 2, 3, 4, 5, 6]
sage: W = ((x,y) for x in ZZ for y in ZZ)
sage: next(W)
(0, 0)
sage: next(W)
(0, 1)
sage: next(W)
(0, -1)

循环、函数、控制语句和比较

我们已经看到了一些 for 循环。在Python中,一个 for 循环具有缩进结构,例如

>>> for i in range(5):
...     print(i)
...
0
1
2
3
4

注意for语句末尾的冒号(在GAP或Maple中没有“do”或“od”),在循环的“body”前面有缩进,即 print(i) . 这个缩进很重要。在Sage中,当你点击时,缩进会自动为你输入 enter 在“:”之后,如下图所示。

sage: for i in range(5):
....:     print(i)  # now hit enter twice
....:
0
1
2
3
4

符号 = 用于赋值。符号 == 用于检查相等性:

sage: for i in range(15):
....:     if gcd(i,15) == 1:
....:         print(i)
....:
1
2
4
7
8
11
13
14

请记住缩进是如何确定块结构的 ifforwhile 声明:

sage: def legendre(a,p):
....:     is_sqr_modp=-1
....:     for i in range(p):
....:         if a % p == i^2 % p:
....:             is_sqr_modp=1
....:     return is_sqr_modp

sage: legendre(2,7)
1
sage: legendre(3,7)
-1

当然,这不是一个有效的Legendre符号实现!它旨在说明Python/Sage编程的各个方面。Sage附带的函数{kronecker}通过对PARI的C-library调用有效地计算Legendre符号。

最后,我们注意到比较,例如 ==!=<=>=>< ,如果可能,数字之间会自动将两个数字转换为同一类型:

sage: 2 < 3.1; 3.1 <= 1
True
False
sage: 2/3 < 3/2;   3/2 < 3/1
True
True

使用bool表示符号不等式:

sage: x < x + 1
x < x + 1
sage: bool(x < x + 1)
True

当在Sage中比较不同类型的对象时,在大多数情况下Sage会尝试找到两个对象对共同父对象的规范强制(请参见 父母、皈依和强迫 更多细节)。如果成功,则在强制对象之间执行比较;如果不成功,则认为对象不相等。用于测试两个变量是否引用同一个对象使用 is . 正如我们在本例中看到的,Python int 1 是唯一的,但是Sage整数 1 不是:

sage: 1 is 2/2
False
sage: int(1) is int(2)/int(2)   # py2
True
sage: 1 is 1
False
sage: 1 == 2/2
True

在下面两行中,第一个等式是 False 因为没有标准态射 \(\QQ\to \GF{{5}}\) ,因此没有标准的方法来比较 \(1\) 在里面 \(\GF{{5}}\)\(1 \in \QQ\) . 相反,有一个规范映射 \(\ZZ \to \GF{{5}}\) ,因此第二个比较是 True . 还要注意,顺序并不重要。

sage: GF(5)(1) == QQ(1); QQ(1) == GF(5)(1)
False
False
sage: GF(5)(1) == ZZ(1); ZZ(1) == GF(5)(1)
True
True
sage: ZZ(1) == QQ(1)
True

警告:在Sage中的比较比在Magma中的限制更大,它声明 \(1 \in \GF{{5}}\) 等于 \(1 \in \QQ\) .

sage: magma('GF(5)!1 eq Rationals()!1')            # optional - magma
true

分析

作者:Martin Albrecht(malb@informatik.uni-bremen.de)

“过早的优化是万恶之源。”—唐纳德·克努斯

有时,检查代码中的瓶颈以了解哪些部分占用了最多的计算时间,这可以很好地了解哪些部分需要优化。Python和Sage提供了几个评测选项(这个过程被称为)。

最简单的用法是 prun 命令。它返回一个摘要,描述哪些函数花费了多少计算时间。侧写(当前速度慢!-例如,对于版本1.0)有限域上的矩阵乘法,请执行以下操作:

sage: k,a = GF(2**8, 'a').objgen()
sage: A = Matrix(k,10,10,[k.random_element() for _ in range(10*10)])
sage: %prun B = A*A
       32893 function calls in 1.100 CPU seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function)
 12127  0.160   0.000   0.160  0.000 :0(isinstance)
  2000  0.150   0.000   0.280  0.000 matrix.py:2235(__getitem__)
  1000  0.120   0.000   0.370  0.000 finite_field_element.py:392(__mul__)
  1903  0.120   0.000   0.200  0.000 finite_field_element.py:47(__init__)
  1900  0.090   0.000   0.220  0.000 finite_field_element.py:376(__compat)
   900  0.080   0.000   0.260  0.000 finite_field_element.py:380(__add__)
     1  0.070   0.070   1.100  1.100 matrix.py:864(__mul__)
  2105  0.070   0.000   0.070  0.000 matrix.py:282(ncols)
  ...

在这里 ncalls 是通话次数, tottime 是在给定函数中花费的总时间(不包括调用子函数的时间), percall 是的商 tottime 除以 ncalls . cumtime 是此函数和所有子函数(即从调用到退出)所花费的总时间, percall 是的商 cumtime 除以原始调用,以及 filename:lineno(function) 提供每个函数的相应数据。这里的经验法则是:清单中的函数越高,它的成本就越高。因此,对优化问题更感兴趣。

As usual, prun? provides details on how to use the profiler and understand the output.

也可以将分析数据写入对象,以便进行更仔细的检查:

sage: %prun -r A*A
sage: stats = _
sage: stats?

注:进入 stats = prun -r A\*A 显示语法错误消息,因为prun是IPython shell命令,而不是常规函数。

为了更好地以图形方式表示分析数据,可以使用hotshot profiler,一个名为 hotshot2cachetree 程序呢 kcachegrind (仅限Unix)。与hotshot profiler相同的示例:

sage: k,a = GF(2**8, 'a').objgen()
sage: A = Matrix(k,10,10,[k.random_element() for _ in range(10*10)])
sage: import hotshot
sage: filename = "pythongrind.prof"
sage: prof = hotshot.Profile(filename, lineevents=1)
sage: prof.run("A*A")
<hotshot.Profile instance at 0x414c11ec>
sage: prof.close()

这将生成一个文件 pythongrind.prof 在当前工作目录中。现在可以将其转换为cachegrind格式进行可视化。

在系统外壳上,键入

$ hotshot2calltree -o cachegrind.out.42 pythongrind.prof

输出文件 cachegrind.out.42 现在可以用 kcachegrind . 请注意命名约定 cachegrind.out.XX 需要服从。