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.