Programmierung

Sage-Dateien Laden und Anhängen

Als nächstes zeigen wir wie man Programme, die einer separaten Datei geschrieben wurden in Sage lädt. Erstellen Sie eine Datei, welche Sie beispiel.sage nennen mit folgendem Inhalt:

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

Sie können beispiel.sage einlesen und ausführen, indem Sie den load-Befehl verwenden.

sage: load("beispiel.sage")
Hello World
8
>>> from sage.all import *
>>> load("beispiel.sage")
Hello World
8
load("beispiel.sage")

Sie können auch eine Sage-Datei an eine laufende Sitzung anhängen, indem Sie den attach-Befehl verwenden:

sage: attach("beispiel.sage")
Hello World
8
>>> from sage.all import *
>>> attach("beispiel.sage")
Hello World
8
attach("beispiel.sage")

Wenn Sie nun beispiel.sage verändern und eine Leerzeile in Sage eingeben (d.h. return drücken) wird der Inhalt von beispiel.sage automatisch in Sage neu geladen.

Insbesondere lädt der attach-Befehl eine Datei jedesmal, wenn diese verändert wird automatisch neu, was beim Debuggen von Code nützlich sein kann, wobei der load-Befehl eine Datei nur einmal lädt.

Wenn Sage die Datei beispiel.sage lädt, wird sie zu Python-Code konvertiert, welcher dann vom Python-Interpreter ausgeführt wird. Diese Konvertierung ist geringfügig; sie besteht hautsächlich daraus Integer-Literale mit Integer() und Fließkomma-Literale mit RealNumber() zu versehen, ^ durch ** zu ersetzen und z.B. R.2 durch R.gen(2) auszutauschen. Die konvertierte Version von beispiel.sage befindet sich im gleichen Verzeichnis wie beispiel.sage und ist beispiel.sage.py genannt. Diese Datei enthält den folgenden Code:

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

Integer-Literale wurden mit Integer() versehen und das ^ wurde durch ein ** ersetzt. (In Python bedeutet ^ „exklusives oder“ und ** bedeutet „Exponentiation“.)

Dieses „preparsing“ ist in sage/misc/interpreter.py implementiert.)

Sie können mehrzeiligen eingerückten Code in Sage einfügen, solange Zeilenenden neue Blöcke kenntlich machen (dies ist in Dateien nicht notwendig). Jedoch beseht die beste Möglichkeit solchen Code in Sage einzufügen darin, diesen in einer Datei zu speichern und attach wie oben beschrieben zu verwenden.

Kompilierten Code erzeugen

Geschwindigkeit ist bei mathematischen Berechnungen äußerst wichtig. Python ist zwar eine komfortable Programmiersprache mit sehr hohen Abstraktionsniveau, jedoch können bestimmte Berechnungen mehrere Größenordnungen schneller als in Python sein, wenn sie in einer kompilierten Sprache mit statischen Datentypen implementiert wurden. Manche Teile von Sage würden zu langsam sein, wenn sie komplett in Python geschrieben wären. Um dies zu berücksichtigen unterstützt Sage eine kompilierte „Version“ von Python, welche Cython ([Cyt] und [Pyr]) genannt wird. Cython ist gleichzeitig sowohl zu Python, als auch zu C ähnlich. Die meisten von Pythons Konstruktionen, einschließlich „list comprehensions“, bedingte Ausdrücke und Code wie += sind erlaubt; Sie können auch Code importieren, den Sie in anderen Python-Modulen geschrieben haben. Darüberhinaus können Sie beliebige C Variablen definieren und beliebe C-Bibliothekaufrufe direkt ausführen. Der daraus entstehende Code wird nach C konvertiert und mithilfe eines C-Compilers kompiliert.

Um eigenen kompilierten Sagecode zu erstellen, geben Sie der Datei eine .spyx Endung (anstelle von .sage). Falls Sie mit der Kommandozeile arbeiten, können Sie kompilierten Code genau wie interpretierten Code anhängen und laden. (Im Moment wird das Anhängen von Cythoncode vom Notebook aus nicht unterstützt). Die tatsächliche Kompilierung wird „hinter den Kulissen“ durchgeführt ohne dass Sie explizit etwas tun müssen. Die komplierte „shared object library“ wird unter $HOME/.sage/temp/hostname/pid/spyx gespeichert. Diese Dateien werden gelöscht wenn Sie Sage beenden.

Auf spyx-Dateien wird kein „preparsing“ angewendet, d.h. 1/3 wird in einer spyx-Datei zu 0 ausgewertet, anstelle der rationalen Zahl \(1/3\). Wenn foo eine Funktion in der Sage-Bibliothek ist die Sie verwenden möchten, müssen Sie sage.all importieren und sage.all.foo benutzen.

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

Auf C-Funktionen in separaten Dateien zugreifen

Es ist auch nicht schwer auf C-Funktions zuzugreifen welche in separaten *.c Dateien definiert sind. Hier ist ein Beispiel. Erzeugen Sie die Dateien test.c und test.spyx in dem gleichen Verzeichnis mit den Inhalten:

Der reine C-Code: test.c

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

Der Cython-Code: test.spyx:

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

def test(n):
    return add_one(n)

Dann funktioniert das Folgende:

sage: attach("test.spyx")
Compiling (...)/test.spyx...
sage: test(10)
11
>>> from sage.all import *
>>> attach("test.spyx")
Compiling (...)/test.spyx...
>>> test(Integer(10))
11
attach("test.spyx")
test(10)

Wenn die zusätzliche Bibliothek foo gebraucht wird um den C-Code, der aus einer Cython-Datei generiert wurde zu kompilieren, fügen Sie die Zeile clib foo zu dem Cython-Quellcode hinzu. Auf ähnliche Weise kann eine zusätzliche Datei bar zu der Kompilierung mit der Deklaration cfile bar hinzugefügt werden.

eigenständige Python/Sage Skripte

Das folgende eigenständige Sageskript faktorisiert ganze Zahlen, Polynome, usw.:

#!/usr/bin/env sage

import sys

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])))

Um dieses Skript benutzen zu können muss SAGE_ROOT in ihrer PATH-Umgebungsvariable enthalten sein. Falls das das obige Skript factor genannt wurde, ist hier ein beispielhafter Aufruf:

bash $ ./factor 2006
2 * 17 * 59

Datentypen

Jedes Objekt hat in Sage einen wohldefinierten Datentyp. Python besitzt eine Vielzahl von standardmäßiger elementarer Datentypen und die Sage-Bibliothek fügt noch viele weitere hinzu. Zu Pythons standardmäßigen Datentypen gehören Strings, Listen, Tupel, Ganzzahlen und Gleitkommazahlen, wie hier zu sehen ist:

sage: s = "sage"; type(s)
<... 'str'>
sage: s = 'sage'; type(s)      # Sie können einfache oder doppelte Anführungszeichen verwenden
<... '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'>
>>> from sage.all import *
>>> s = "sage"; type(s)
<... 'str'>
>>> s = 'sage'; type(s)      # Sie können einfache oder doppelte Anführungszeichen verwenden
<... 'str'>
>>> s = [Integer(1),Integer(2),Integer(3),Integer(4)]; type(s)
<... 'list'>
>>> s = (Integer(1),Integer(2),Integer(3),Integer(4)); type(s)
<... 'tuple'>
>>> s = int(Integer(2006)); type(s)
<... 'int'>
>>> s = float(Integer(2006)); type(s)
<... 'float'>
s = "sage"; type(s)
s = 'sage'; type(s)      # Sie können einfache oder doppelte Anführungszeichen verwenden
s = [1,2,3,4]; type(s)
s = (1,2,3,4); type(s)
s = int(2006); type(s)
s = float(2006); type(s)

Hierzu fügt Sage noch viele weitere hinzu. Zum Beispiel Vektorräume:

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'>
>>> from sage.all import *
>>> V = VectorSpace(QQ, Integer(1000000)); V
Vector space of dimension 1000000 over Rational Field
>>> type(V)
<class 'sage.modules.free_module.FreeModule_ambient_field_with_category'>
V = VectorSpace(QQ, 1000000); V
type(V)

Nur bestimmte Funktionen können auf V aufgerufen werden. In anderen mathematischen Softwaresystemem würde dies mit der „Funktionalen“-Notation foo(V,...) geschehen. In Sage sind bestimmte Funktionen an den Typ (oder der Klasse) von V angehängt, und diese werden unter Benutzung einer objektorientierten Syntax, wie in Java oder C++ aufgerufen. Zum Beispiel V.foo(...). Dies hilft dabei eine Überfüllung des globalen Namensraums mit tausenden von Funktionen zu vermeiden. Das bedeutet auch, dass viele verschiedene Funktionen mit unterschiedlichen Funktionsweisen foo genannt werden können, ohne dass der Typ des Arguments überprüft (oder Case-Anweisungen ausgeführt) werden muss, um zu entscheiden welche aufgerufen werden soll. Weiterhin ist die Funktion auch dann noch verfügbar, wenn ihr Name zu einem anderen Zweck verwendet wurde. (Zum Beispiel wenn Sie etwas zeta nennen und dann den Wert der Riemannschen Zeta-Funktion bei 0.5 berechnen wollen, können Sie immernoch s=.5; s.zeta() benutzen).

sage: zeta = -1
sage: s=.5; s.zeta()
-1.46035450880959
>>> from sage.all import *
>>> zeta = -Integer(1)
>>> s=RealNumber('.5'); s.zeta()
-1.46035450880959
zeta = -1
s=.5; s.zeta()

In manchen sehr oft auftretenden Fällen wird auch die gewöhnliche funktionale Notation unterstützt, da dies bequem ist und manche mathematische Ausdrücke in objektorientierter Notation verwirrend aussehen könnten. Hier sind einige Beispiele:

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
>>> from sage.all import *
>>> n = Integer(2); n.sqrt()
sqrt(2)
>>> sqrt(Integer(2))
sqrt(2)
>>> V = VectorSpace(QQ,Integer(2))
>>> V.basis()
    [
    (1, 0),
    (0, 1)
    ]
>>> basis(V)
    [
    (1, 0),
    (0, 1)
    ]
>>> M = MatrixSpace(GF(Integer(7)), Integer(2)); M
Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 7
>>> A = M([Integer(1),Integer(2),Integer(3),Integer(4)]); A
[1 2]
[3 4]
>>> A.charpoly('x')
x^2 + 2*x + 5
>>> charpoly(A, 'x')
x^2 + 2*x + 5
n = 2; n.sqrt()
sqrt(2)
V = VectorSpace(QQ,2)
V.basis()
basis(V)
M = MatrixSpace(GF(7), 2); M
A = M([1,2,3,4]); A
A.charpoly('x')
charpoly(A, 'x')

Um alle Member-Funktionen von \(A\) anzuzeigen, können Sie die Tab-Vervollständigung benutzen. Tippen Sie einfach A., dann die [tab]-Taste auf Ihrer Tastatur, wie es in Rückwärtssuche und Tab-Vervollständigung beschrieben ist.

Listen, Tupel, und Folgen

Der Listen-Datentyp speichert Elemente eines beliebigen Typs. Wie in C, C++, usw. (jedoch anders als in vielen gewöhnlichen Computer-Algebra-Systemen), die Elemente der Liste werden bei \(0\) beginnend indiziert:

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
>>> from sage.all import *
>>> v = [Integer(2), Integer(3), Integer(5), 'x', SymmetricGroup(Integer(3))]; v
[2, 3, 5, 'x', Symmetric group of order 3! as a permutation group]
>>> type(v)
<... 'list'>
>>> v[Integer(0)]
2
>>> v[Integer(2)]
5
v = [2, 3, 5, 'x', SymmetricGroup(3)]; v
type(v)
v[0]
v[2]

(Wenn man auf ein Listenelement zugreift ist es OK wenn der Index kein Python int ist!) Mit einem Sage-Integer (oder Rational, oder mit allem anderen mit einer __index__ Methode) funktioniert es genauso.

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
>>> from sage.all import *
>>> v = [Integer(1),Integer(2),Integer(3)]
>>> v[Integer(2)]
3
>>> n = Integer(2)      # Sage Integer
>>> v[n]       # Perfectly OK!
3
>>> v[int(n)]  # Also OK.
3
v = [1,2,3]
v[2]
n = 2      # Sage Integer
v[n]       # Perfectly OK!
v[int(n)]  # Also OK.

Die range-Funktion erzeugt eine Liste von Python int’s (nicht Sage-Integers):

sage: list(range(1, 15))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> from sage.all import *
>>> list(range(Integer(1), Integer(15)))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
list(range(1, 15))

Dies ist nützlich wenn man List-Comprehensions verwendet um Listen zu konstruieren:

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]
>>> from sage.all import *
>>> L = [factor(n) for n in range(Integer(1), Integer(15))]
>>> L
[1, 2, 3, 2^2, 5, 2 * 3, 7, 2^3, 3^2, 2 * 5, 11, 2^2 * 3, 13, 2 * 7]
>>> L[Integer(12)]
13
>>> type(L[Integer(12)])
 <class 'sage.structure.factorization_integer.IntegerFactorization'>
>>> [factor(n) for n in range(Integer(1), Integer(15)) if is_odd(n)]
[1, 3, 5, 7, 3^2, 11, 13]
L = [factor(n) for n in range(1, 15)]
L
L[12]
type(L[12])
[factor(n) for n in range(1, 15) if is_odd(n)]

Um mehr darüber zu erfahren wie man Listen mit Hilfe von List-Comprehensions erzeugt, lesen Sie [PyT].

List-Slicing ist eine wunderbare Eigenschaft. Wenn L eine Liste ist, dann gibt L[m:n] die Teilliste von L zurück, die erhalten wird wenn man mit dem \(m^{ten}\) Element beginnt und bei dem \((n-1)^{ten}\) Element aufhört, wie unten gezeigt wird.

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]
>>> from sage.all import *
>>> L = [factor(n) for n in range(Integer(1), Integer(20))]
>>> L[Integer(4):Integer(9)]
[5, 2 * 3, 7, 2^3, 3^2]
>>> L[:Integer(4)]
[1, 2, 3, 2^2]
>>> L[Integer(14):Integer(4)]
[]
>>> L[Integer(14):]
[3 * 5, 2^4, 17, 2 * 3^2, 19]
L = [factor(n) for n in range(1, 20)]
L[4:9]
L[:4]
L[14:4]
L[14:]

Tupel sind ähnlich wie Listen, außer dass sie unveränderbar sind, was bedeutet dass sie, sobald sie erzeugt wurden, nicht mehr verändert werden können.

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
>>> from sage.all import *
>>> v = (Integer(1),Integer(2),Integer(3),Integer(4)); v
(1, 2, 3, 4)
>>> type(v)
<... 'tuple'>
>>> v[Integer(1)] = Integer(5)
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
v = (1,2,3,4); v
type(v)
v[1] = 5

Folgen sind ein dritter an Listen angelehnter Sage-Datentyp. Anders als Listen und Tupel, sind Folgen kein gewöhnlicher Python-Datentyp. Standardmäßig sind Folgen veränderbar, mit der Sequence-Klassenmethode set_immutable können sie auf unveränderbar gestellt werden, wie das folgende Beispiel zeigt. Alle Elemente einer Folge haben einen gemeinsamen Obertyp, der das Folgenuniversum genannt wird.

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

Folgen sind von Listen abgeleitet und können überall dort verwendet werden, wo auch Listen benutzt werden können.

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'>
>>> from sage.all import *
>>> v = Sequence([Integer(1),Integer(2),Integer(3),Integer(4)/Integer(5)])
>>> isinstance(v, list)
True
>>> list(v)
[1, 2, 3, 4/5]
>>> type(list(v))
<... 'list'>
v = Sequence([1,2,3,4/5])
isinstance(v, list)
list(v)
type(list(v))

Ein weiteres Beispiel von unveränderbaren Folgen sind Basen von Vektorräumen. Es ist wichtig, dass sie nicht verändert werden können.

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

Dictionaries

Ein Dictionary (manchmal auch assoziativer Array genannt) ist eine Abbildung von ‚hashbaren‘ Objekten (z.B. Strings, Zahlen und Tupel; Lesen Sie die Python documentation http://docs.python.org/tut/node7.html und http://docs.python.org/lib/typesmapping.html für weitere Details) zu beliebigen Objekten.

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
>>> from sage.all import *
>>> d = {Integer(1):Integer(5), 'sage':Integer(17), ZZ:GF(Integer(7))}
>>> type(d)
<... 'dict'>
>>> list(d.keys())
[1, 'sage', Integer Ring]
>>> d['sage']
17
>>> d[ZZ]
Finite Field of size 7
>>> d[Integer(1)]
5
d = {1:5, 'sage':17, ZZ:GF(7)}
type(d)
list(d.keys())
d['sage']
d[ZZ]
d[1]

Der dritte „key“ zeigt, dass Indizes eines Dictionaries kompliziert, also beispielsweise der Ring der ganzen Zahlen, sein können.

Sie können das obige Dictionary auch in eine Liste mit den gleichen Daten umwandeln:

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

Eine häufig vorkommende Ausdrucksweise ist über einem Paar in einem Dictionary zu iterieren:

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

Ein Dictionary ist ungeordnet, wie die letzte Ausgabe verdeutlicht.

Mengen

Python hat einen standardmäßigen Mengen-Datentyp. Sein Hauptmerkmal ist, neben weiteren typischen Mengenoperationen, dass das Nachschlagen ob ein Element zu der Menge gehört oder nicht, sehr schnell geht.

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}
>>> from sage.all import *
>>> X = set([Integer(1),Integer(19),'a']);   Y = set([Integer(1),Integer(1),Integer(1), Integer(2)/Integer(3)])
>>> X   # random sort order
{1, 19, 'a'}
>>> X == set(['a', Integer(1), Integer(1), Integer(19)])
True
>>> Y
{2/3, 1}
>>> 'a' in X
True
>>> 'a' in Y
False
>>> X.intersection(Y)
{1}
X = set([1,19,'a']);   Y = set([1,1,1, 2/3])
X   # random sort order
X == set(['a', 1, 1, 19])
Y
'a' in X
'a' in Y
X.intersection(Y)

Sage besitzt auch einen eigenen Mengen-Datentyp, welcher (manchmal) mit Hilfe des standardmäßigen Python-Mengen-Datentyps implementiert ist, jedoch darüberhinaus manche Sage-spezifischen Funktionen aufweist. Sie können eine Sage-Menge erzeugen indem Sie Set(...) verwenden. Zum Beispiel,

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
>>> from sage.all import *
>>> X = Set([Integer(1),Integer(19),'a']);   Y = Set([Integer(1),Integer(1),Integer(1), Integer(2)/Integer(3)])
>>> X   # random sort order
{'a', 1, 19}
>>> X == Set(['a', Integer(1), Integer(1), Integer(19)])
True
>>> Y
{1, 2/3}
>>> X.intersection(Y)
{1}
>>> print(latex(Y))
\left\{1, \frac{2}{3}\right\}
>>> Set(ZZ)
Set of elements of Integer Ring
X = Set([1,19,'a']);   Y = Set([1,1,1, 2/3])
X   # random sort order
X == Set(['a', 1, 1, 19])
Y
X.intersection(Y)
print(latex(Y))
Set(ZZ)

Iteratoren

Iteratoren sind seit Version 2.2 ein Teil von Python und erweisen sich in mathematischen Anwendungen als besonders nützlich. Wir geben hier ein paar Beispiele an; Lesen Sie [PyT] um weitere Details zu erfahren. Wir erstellen einen Iterator über die Quadrate der nichtnegativen ganzen Zahlen bis \(10000000\).

sage: v = (n^2 for n in range(10000000))
sage: next(v)
0
sage: next(v)
1
sage: next(v)
4
>>> from sage.all import *
>>> v = (n**Integer(2) for n in range(Integer(10000000)))
>>> next(v)
0
>>> next(v)
1
>>> next(v)
4
v = (n^2 for n in range(10000000))
next(v)
next(v)
next(v)

Nun erzeugen wir einen Iterator über den Primzahlen der Form \(4p+1\) wobei auch \(p\) prim ist und schauen uns die ersten Werte an.

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
>>> from sage.all import *
>>> w = (Integer(4)*p + Integer(1) for p in Primes() if is_prime(Integer(4)*p+Integer(1)))
>>> w         # in the next line, 0xb0853d6c is a random 0x number
<generator object at 0xb0853d6c>
>>> next(w)
13
>>> next(w)
29
>>> next(w)
53
w = (4*p + 1 for p in Primes() if is_prime(4*p+1))
w         # in the next line, 0xb0853d6c is a random 0x number
next(w)
next(w)
next(w)

Bestimmte Ringe, z. B. endliche Körper und die ganzen Zahlen, haben zugehörige Iteratoren:

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)
>>> from sage.all import *
>>> [x for x in GF(Integer(7))]
[0, 1, 2, 3, 4, 5, 6]
>>> W = ((x,y) for x in ZZ for y in ZZ)
>>> next(W)
(0, 0)
>>> next(W)
(0, 1)
>>> next(W)
(0, -1)
[x for x in GF(7)]
W = ((x,y) for x in ZZ for y in ZZ)
next(W)
next(W)
next(W)

Schleifen, Funktionen, Kontrollstrukturen und Vergleiche

Wir haben schon ein paar Beispiele gesehen in denen die for-Schleife üblicherweise Verwendung findet. In Python hat eine for-Schleife eine eingerückte Struktur, wie hier:

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

Beachten Sie den Doppelpunkt am Ende der for-Anweisung (dort befindet sich kein „do“ oder „od“ wie in GAP oder Maple) und die Einrückung vor dem Schleifenrumpf, dem print(i). Diese Einrückung ist wichtig. In Sage wird die Einrückung automatisch hinzugefügt wenn Sie nach einem „:“ die enter-Taste drücken, wie etwa im Folgenden Beispiel.

sage: for i in range(5):
....:     print(i)  # now hit enter twice
....:
0
1
2
3
4
>>> from sage.all import *
>>> for i in range(Integer(5)):
...     print(i)  # now hit enter twice
....:
0
1
2
3
4
for i in range(5):
    print(i)  # now hit enter twice

Das Symbol = wird bei Zuweisungen verwendet. Das Symbol == wird verwendet um Gleichheit zu testen:

sage: for i in range(15):
....:     if gcd(i,15) == 1:
....:         print(i)
1
2
4
7
8
11
13
14
>>> from sage.all import *
>>> for i in range(Integer(15)):
...     if gcd(i,Integer(15)) == Integer(1):
...         print(i)
1
2
4
7
8
11
13
14
for i in range(15):
    if gcd(i,15) == 1:
        print(i)

Behalten Sie im Gedächtnis, dass die Block-Struktur von if, for und while Ausdrücken durch die Einrückung bestimmt wird:

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
>>> from sage.all import *
>>> def legendre(a,p):
...     is_sqr_modp=-Integer(1)
...     for i in range(p):
...         if a % p == i**Integer(2) % p:
...             is_sqr_modp=Integer(1)
...     return is_sqr_modp

>>> legendre(Integer(2),Integer(7))
1
>>> legendre(Integer(3),Integer(7))
-1
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
legendre(2,7)
legendre(3,7)

Natürlich ist dies keine effiziente Implementierung des Legendre-Symbols! Dies soll nur bestimmte Aspekte won Python/Sage verdeutlichen. Die Funktion {kronecker}, welche zu Sage gehört, berechnet das Legendre-Symbol effizient mittels eines Aufrufs von PARIs C-Bibliothek.

Schließlich merken wir an, dass Vergleiche wie ==, !=, <=, >=, >, < von zwei Zahlen automatisch beide Zahlen in den gleichen Typ konvertieren, falls dies möglich ist:

sage: 2 < 3.1; 3.1 <= 1
True
False
sage: 2/3 < 3/2;   3/2 < 3/1
True
True
>>> from sage.all import *
>>> Integer(2) < RealNumber('3.1'); RealNumber('3.1') <= Integer(1)
True
False
>>> Integer(2)/Integer(3) < Integer(3)/Integer(2);   Integer(3)/Integer(2) < Integer(3)/Integer(1)
True
True
2 < 3.1; 3.1 <= 1
2/3 < 3/2;   3/2 < 3/1

Nutzen Sie bool für symbolische Ungleichungen:

sage: x < x + 1
x < x + 1
sage: bool(x < x + 1)
True
>>> from sage.all import *
>>> x < x + Integer(1)
x < x + 1
>>> bool(x < x + Integer(1))
True
x < x + 1
bool(x < x + 1)

Beim Vergleichen von Objekten unterschiedlichen Typs versucht Sage in den meisten Fällen eine kanonische Umwandlung beider Objekte in einen gemeinsamen Typ zu finden. Falls erfolgreich wird der Vergleich auf den umgewandelten Objekten durchgeführt; Falls nicht erfolgreich werden die Objekte als ungleich angesehen. Um zu Testen, ob zwei Variablen auf das gleiche Objekt zeigen, verwenden Sie is. Zum Beispiel:

sage: 1 is 2/2
False
sage: 1 is 1
False
sage: 1 == 2/2
True
>>> from sage.all import *
>>> Integer(1) is Integer(2)/Integer(2)
False
>>> Integer(1) is Integer(1)
False
>>> Integer(1) == Integer(2)/Integer(2)
True
1 is 2/2
1 is 1
1 == 2/2

In den folgenden zwei Zeilen ist der erste Gleichheitstest False, da es keinen kanonischen Morphismus \(\QQ\ \to \GF{5}\) gibt, also gibt es keine kanonische Möglichkeit die \(1\) in \(\GF{5}\) mit der \(1 \in \QQ\) zu vergleichen. Im Gegensatz dazu gibt es eine kanonische Abbildung \(\ZZ \to \GF{5}\), also ist der zweite Gleichheitstest True. Beachten Sie auch, dass die Reihenfolge keine Rolle spielt.

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
>>> from sage.all import *
>>> GF(Integer(5))(Integer(1)) == QQ(Integer(1)); QQ(Integer(1)) == GF(Integer(5))(Integer(1))
False
False
>>> GF(Integer(5))(Integer(1)) == ZZ(Integer(1)); ZZ(Integer(1)) == GF(Integer(5))(Integer(1))
True
True
>>> ZZ(Integer(1)) == QQ(Integer(1))
True
GF(5)(1) == QQ(1); QQ(1) == GF(5)(1)
GF(5)(1) == ZZ(1); ZZ(1) == GF(5)(1)
ZZ(1) == QQ(1)

WARNUNG: Vergleiche in Sage sind restriktiver als in Magma, welches die \(1 \in \GF{5}\) gleich der \(1 \in \QQ\) festlegt.

sage: magma('GF(5)!1 eq Rationals()!1')            # optional - magma
true
>>> from sage.all import *
>>> magma('GF(5)!1 eq Rationals()!1')            # optional - magma
true
magma('GF(5)!1 eq Rationals()!1')            # optional - magma

Profiling

Autor des Abschnitts: Martin Albrecht (malb@informatik.uni-bremen.de)

„Premature optimization is the root of all evil.“ - Donald Knuth

Manchmal ist es nützlich nach Engstellen im Code zu suchen, um zu verstehen welche Abschnitte die meiste Berechnungszeit beanspruchen; dies kann ein guter Hinweis darauf sein, welche Teile optimiert werden sollten. Python, und daher auch Sage, stellen mehrere „Profiling“ – so wird dieser Prozess genannt – Optionen zur Verfügung.

Am einfachsten zu Benutzen ist das prun-Kommando in der interaktiven Shell. Es gibt eine Zusammenfassung zurück, die beschreibt welche Funktionen wie viel Berechnungszeit veranschlagt haben. Um die (zu diesem Zeitpunkt langsame) Matrixmultiplikation über endlichen Körpern zu Profilieren, geben Sie z.B. folgendes ein:

sage: k,a = GF(2**8, 'a').objgen()
sage: A = Matrix(k,10,10,[k.random_element() for _ in range(10*10)])
>>> from sage.all import *
>>> k,a = GF(Integer(2)**Integer(8), 'a').objgen()
>>> A = Matrix(k,Integer(10),Integer(10),[k.random_element() for _ in range(Integer(10)*Integer(10))])
k,a = GF(2**8, 'a').objgen()
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)
  ...
>>> from sage.all import *
>>> %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)
  ...
%prun B = A*A

Hier ist ncalls die Anzahl der Aufrufe, tottime ist die Gesamtzeit, die für die Funktion verwendet wurde (ausgenommen der Zeit, die für Unterfunktionsaufrufe verwendet wurde), percall ist der Quotient von tottime geteilt durch ncalls. cumtime ist die Gesamtzeit, die für diese Funktion und alle Unterfunktionsaufrufe (d.h., vom Aufruf bis zum Ende) verwendet wurde, percall ist der Quotient von cumtime geteilt durch die Zeit elementarer Funktionsaufrufe, und filename:lineno(function) stellt die entsprechenden Daten jeder Funktion zur Verfügung. Die Daumenregel ist hier: Je höher die Funktion in dieser Liste steht, desto teurer ist sie. Also ist sie interessanter für Optimierungen.

Wie sonst auch stellt prun? Details zur Benutzung des Profilers und zum Verstehen seines Outputs zur Verfügung.

Die Profilierungsdaten können auch in ein Objekt geschrieben werden um eine weitere Untersuchung zu ermöglichen:

sage: %prun -r A*A
sage: stats = _
sage: stats?
>>> from sage.all import *
>>> %prun -r A*A
>>> stats = _
>>> stats?
%prun -r A*A
stats = _
stats?

Beachten Sie: das Eingeben von stats = prun -r A\*A erzeugt eine Syntaxfehlermeldung, da prun ein IPython-Shell-Kommando ist und keine reguläre Funktion.

Um eine schöne graphische Repräsentation der Profilerdaten zu erhalten, können Sie den „hotshot-Profiler“, ein kleines Skript genannt hotshot2cachetree und das Programm kcachegrind (nur für Unix) benutzen. Hier ist das gleiche Beispiel mit dem „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)
>>> from sage.all import *
>>> k,a = GF(Integer(2)**Integer(8), 'a').objgen()
>>> A = Matrix(k,Integer(10),Integer(10),[k.random_element() for _ in range(Integer(10)*Integer(10))])
>>> import hotshot
>>> filename = "pythongrind.prof"
>>> prof = hotshot.Profile(filename, lineevents=Integer(1))
k,a = GF(2**8, 'a').objgen()
A = Matrix(k,10,10,[k.random_element() for _ in range(10*10)])
import hotshot
filename = "pythongrind.prof"
prof = hotshot.Profile(filename, lineevents=1)
sage: prof.run("A*A")
<hotshot.Profile instance at 0x414c11ec>
sage: prof.close()
>>> from sage.all import *
>>> prof.run("A*A")
<hotshot.Profile instance at 0x414c11ec>
>>> prof.close()
prof.run("A*A")
prof.close()

Dies führt zu einer Datei pythongrind.prof in aktuellen Datenverzeichnis. Diese kann nun zur Visualisierung in das cachegrind-Format konvertiert werden.

Tippen Sie in einer System-Shell:

hotshot2calltree -o cachegrind.out.42 pythongrind.prof

Die Ausgabedatei cachegrind.out.42 kann nun mit kcachegrind untersucht werden. Bitte beachten Sie, dass die Namenskonvention cachegrind.out.XX erhalten bleiben muss.