Sage Quickstart for Number Theory#

This Sage quickstart tutorial was developed for the MAA PREP Workshop “Sage: Using Open-Source Mathematics Software with Undergraduates” (funding provided by NSF DUE 0817071). It is licensed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA).

Since Sage began life as a project in algebraic and analytic number theory (and this continues to be a big emphasis), it is no surprise that functionality in this area is extremely comprehensive. And it’s also just enjoyable to explore elementary number theory by computer.

Modular Arithmetic#

Conveniently, the ring of integers modulo \(n\) is always available in Sage, so we can do modular arithmetic very easily. For instance, we can create a number in \(\ZZ/11\ZZ\). The type command tells us that \(a\) is not a regular integer.

sage: a = mod(2,11); a; type(a); a^10; a^1000000
<class 'sage.rings.finite_rings.integer_mod.IntegerMod_int'>

Note that we verified Fermat’s “Little Theorem” in the previous cell, for the prime \(p=11\) and input \(a=2\).

Recalling the basic programming construct called a loop , we can verify this for all \(a\) in the integers modulo \(p\). Here, instead of looping over an explicit list like [0,1,2,3,...8,9,10], we loop over a Sage object.

sage: for a in Integers(11):
....:     print("{} {}".format(a, a^10))
0 0
1 1
2 1
3 1
4 1
5 1
6 1
7 1
8 1
9 1
10 1

Notice that Integers(11) gave us an algebraic object which is the ring of integers modulo the prime ideal generated by the element 11.

This works for much bigger numbers too, of course.

sage: p=random_prime(10^200,proof=True)
sage: Zp=Integers(p) # Here we give ourselves shorthand for the modular integers
sage: a=Zp(2) # Here we ask for 2 as an element of that ring
sage: p; a; a^(p-1); a^(10^400)

Whenever you encounter a new object, you should try tab-completion to see what you can do to it. Try it here, and pick one (hopefully one that won’t be too long!).

sage: Zp.

Here’s one that sounds interesting.

sage: Zp.zeta?

And we use it to find the fifth roots of unity in this field. We use the list comprehension (set-builder) notation from the programming tutorial this time.

sage: root_list = Zp.zeta(5,all=True); root_list
[80199770388563324100334548626345240081294273289109866436996652525328268652922508892946068538641538316054373187019168781211876036849531337824832226216684677717580165592175377569174402189281574130719978, 69839783895572286297568834485025073557885364348071061715465477061873400359794989367423407683971299361817245213947182344090739843367076197016322541936552333837227080274674865687645877633828974738751695, 57407444219199061498801298672323590163238592201574572482836619025676869537007609315386800852204337587805249250896651467970585450518157701115893749407382500580682168292929753154872678880962261809848942, 41936641877539676652531567723249367917108638987131879521606243741814402095343724540844607212999297064816230828621064026263296602805535970091696570138772210094329631491849238240260893562065879302446837]

Are these really fifth roots of unity?

sage: [root^5 for root in root_list]
[1, 1, 1, 1]

Luckily, it checked out. (If you didn’t get any, then your random prime ended up being one for which there are no fifth roots of unity – try doing the sequence over again!)

More basic functionality#

Similarly to the situation in linear algebra, there is much more we can access at the elementary level, such as

  • primitive roots,

  • ways to write a number as a sum of squares,

  • Legendre symbols,

  • modular solving of basic equations,

  • etc.

A good way to use Sage in this context is to allow students to experiment with pencil and paper first, then use Sage to see whether patterns they discover hold true before attempting to prove them.

sage: p = 13
sage: primitive_root(p); two_squares(p); is_prime(p)
(2, 3)

This makes it easy to construct elementary cryptographic examples as well. Here is a standard example of a Diffie-Hellman key exchange, for instance. If we didn’t do the second line, exponentiation would be impractical.

sage: p=random_prime(10^20,10^30) # a random prime between these numbers
sage: q=mod(primitive_root(p),p) # makes the primitive root a number modulo p, not an integer
sage: n=randint(1,p) # Alice's random number
sage: m=randint(1,p) # Bob's random number
sage: x=q^n; y=q^m
sage: x; y; x^m; y^n

The final line of the cell first requests Alice and Bob’s (possibly) public information, and then verifies that the private keys they get are the same.

It is hard to resist including just one interact. How many theorems do you see here?

sage: @interact
sage: def power_table_plot(p=(7,prime_range(50))):
....:     P=matrix_plot(matrix(p-1,[mod(a,p)^b for a in range(1,p) for b in srange(p)]),cmap='jet')
....:     show(P)

This is a graphic giving the various powers of integers modulo \(p\) as colors, not numbers. The columns are the powers, so the first column is the zeroth power (always 1) and the second column gives the colors for the numbers modulo the given prime (first power).

One more very useful object is the prime counting function \(\pi(x)\). This comes with its own custom plotting.

sage: prime_pi(100); plot(prime_pi,1,100)
Graphics object consisting of 1 graphics primitive

A very nice aspect of Sage is combining several aspects of mathematics together. It can be very eye-opening to students to see analytic aspects of number theory early on. (Note that we have to reassign \(x\) to a variable, since above it was a cryptographic key!)

sage: var('x')
sage: plot(prime_pi,2,10^6,thickness=2)+plot(Li,2,10^6,color='red')+plot(x/ln(x),2,10^6,color='green')
Graphics object consisting of 3 graphics primitives

Advanced Number Theory#

For those who are interested, more advanced number-theoretic objects are easy to come by; we end with a brief sampler of these.

In the first example, \(K\) is the field extension \(\QQ(\sqrt{-14})\), where the symbol a plays the role of \(\sqrt{-14}\); we discover several basic facts about \(K\) in the next several cells.

sage: K.<a> = NumberField(x^2+14); K
Number Field in a with defining polynomial x^2 + 14
sage: K.discriminant(); K.class_group().order(); K.class_group().is_cyclic()

Various zeta functions are also available; here is a complex plot of the Riemann zeta.

sage: complex_plot(zeta, (-30,30), (-30,30))
Graphics object consisting of 1 graphics primitive


Sage supports various more advanced cryptographic procedures as well as some basic pedagogical ones natively. This example is adapted from the documentation.

sage: from sage.crypto.block_cipher.sdes import SimplifiedDES
sage: sdes = SimplifiedDES(); sdes
Simplified DES block cipher with 10-bit keys
sage: bin = BinaryStrings()
sage: P = [0,1,0,0,1,1,0,1] # our message
sage: K = sdes.random_key() # generate a random key
sage: C = sdes.encrypt(P, K) # encrypt our message
sage: plaintxt = sdes.decrypt(C, K) # decrypt it
sage: plaintxt # print it
[0, 1, 0, 0, 1, 1, 0, 1]

See also the cryptography example in the discrete math quickstart.