On visualizing number fields¶
In a group at PCMI with Kate Stange, Ian Whitehead, Hanson Smith, Caleb Springer, and a few others, I explored a few different ways to visualize number fields. We are hoping to come up with something that would be appropriate as images for the LMFDB --- which currently doesn't have number field portraits.
In this note, I summarize some of the ideas that we discussed and give examples. This also includes implementations in sage. This note is also available as a jupyter notebook independently on my website and on
I note that this post was created automatically using a jupyter notebook converter. If this post breaks, please let me know.
Broad ideas and challenges¶
Two natural sets of objects to try to visualize are collections of points in the number field, or collections of points in the ring of integers associated to the number field.
- For the ring of integers, we might visualize
- just the basis elements,
- elements that are small combinations of basis elements, or
- elements that are a power integral basis, i.e. elements $\alpha_i$ such that the ring of integers is given by the ring $\mathbb{Z}[\alpha_1, ..., \alpha_n]$. If the ring of integers is monogenic, then there is a single $\alpha_i$.
- For collections of points in the number field, we might visualize
- all points of height up to some bound, or
- random points.
There is a difference between a number field and embeddings of that number field into the complex numbers. Although number fields have various embeddings into two-dimensional complex space, it is often more natural to consider a degree $n$ number field as living in a natural degree $n$ space spanned by the lattice of the ring of integers.
The possible elements to visualize above are for subsets of elements of the number field. What do we do with the various embeddings?
In many of the visualizations below, we use color to distinguish between the varied embeddings, and plot the same sets of points in different embeddings.
Note that there are already sources of indeterminism:
- Which basis does one choose for the ring of integers?
- How does one order the embeddings?
Characteristics of visualizations¶
Fundamentally, each visualization below are plots of sets of disks of certain sizes and colors. Thus for each visualization below, we specify
- what disks are plotted,
- how we determine the sizes of each disk, and
- how we determine the color of each disk.
Visualizing points of bounded height¶
Given a number field $F$, compute all points of height below some fixed bound $B$ (for some choice of height function). This is cannonical and independent of the chosen basis.
In this plot, we compute all embeddings $\{ \sigma_1, \ldots, \sigma_n \}$ and fix an ordering of them. (Here we use whatever ordering sage uses). For each point $z$ in the number field of height up to $B$ and for each embedding $\sigma_i$, we plot the point $\sigma_i(z)$ with size inversely proportional to the height of $z$, and with color ranging from red (for the first embedding) to blue (for the last embedding).
from sage.plot.colors import red, blue, white
def draw_fieldelts_bdd_height(F,bd):
embs = F.embeddings(CC)
pts = list(F.elements_of_bounded_height(bound=bd))
vis = Graphics()
for emb in embs:
pts_emb = [emb(pt) for pt in pts]
disk_sizer = lambda x : 0.02/(x.global_height())
for index in range(len(pts_emb)):
size = disk_sizer(pts[index])
if size > 0.0001:
vis += disk( (pts_emb[index].real(),pts_emb[index].imag()),
size, (0, 2*pi), alpha=1.0,
color=red.blend(blue, embs.index(emb)/len(embs))
)
return vis
This sort of plot is particularly pleasant for quadratic imaginary number fields, where the lattice structure is completely visible.
Fgauss.<a> = NumberField(x^2 + 1)
gauss = draw_fieldelts_bdd_height(Fgauss, 20)
gauss.show(axes=False)
Feis.<a> = NumberField(x^2 + x + 1)
eis = draw_fieldelts_bdd_height(Feis, 20)
eis.show(axes=False)
These plots reveal that points of low height repel each other. These plots are a bit less satisfying for real quadratic fields.
Frq.<a> = NumberField(x^2 - 2)
fig = draw_fieldelts_bdd_height(Frq, 20)
fig.set_axes_range(xmin=-5, xmax=5, ymin=-1, ymax=1) # it complains that ymin, ymax are both 0 otherwise.
fig.show(axes=False, fig_tight=False) # similarly, with fig_tight it has no height.
/home/davidlowryduda/sagebuild/sage/local/var/lib/sage/venv-python3.8/lib/python3.8/site-packages/matplotlib/patches.py:1189: RuntimeWarning: invalid value encountered in multiply v *= self.r
Fd3.<a> = NumberField(x^3 - x^2 + 2*x - 1)
fig = draw_fieldelts_bdd_height(Fd3, 20)
fig.set_axes_range(xmin=-3, xmax=3, ymin=-3, ymax=3)
fig.show(axes=False)
Fd4.<a> = NumberField(x^4 + 7*x^2 + 13)
fig = draw_fieldelts_bdd_height(Fd4, 20)
fig.set_axes_range(xmin=-3, xmax=3, ymin=-3, ymax=3)
fig.show(axes=False)
For higher degrees, it would probably be necessary to reduce the height bound. But in fact sage uses a sophisticated algorithm to generate points of bounded height, which requires computing the class number of the field. We haven't looked into an alternative, but this prevents us from currently producing high degree visualizations.
We noticed that overlaying different embeddings is confusing. Each individual embedding is natural, but overlaying them obscures the underlying lattice structure. One way around this is to fix an embedding and plotting it. To add some color, we color the point $z$ from red to blue based on the real part of a different fixed embedding of $z$.
def draw_fieldelts_one_embed(F, bd):
embs = F.embeddings(CC)
emb = embs[-1]
emb2 = embs[0]
pts = list(F.elements_of_bounded_height(bound=bd))
pts_emb = [emb(pt) for pt in pts]
disk_sizer = lambda x : 0.02/(x.global_height())
vis = Graphics()
for index in range(len(pts_emb)):
size = disk_sizer(pts[index])
if size > 0.0001:
vis += disk( (pts_emb[index].real(),pts_emb[index].imag()),
size, (0, 2*pi), alpha=1.0,
color=red.blend(white, emb2(pts[index]).real())
)
return vis
gauss = draw_fieldelts_one_embed(Fgauss, 20)
gauss.show(axes=False)
eis = draw_fieldelts_one_embed(Feis, 20)
eis.show(axes=False)
fig = draw_fieldelts_one_embed(Frq, 20)
fig.set_axes_range(xmin=-5, xmax=5, ymin=-1, ymax=1) # it complains that ymin, ymax are both 0 otherwise.
fig.show(axes=False, fig_tight=False) # similarly, with fig_tight it has no height.
fig = draw_fieldelts_one_embed(Fd3, 20)
fig.set_axes_range(xmin=-3, xmax=3, ymin=-3, ymax=3)
fig.show(axes=False)
fig = draw_fieldelts_one_embed(Fd4, 20)
fig.set_axes_range(xmin=-3, xmax=3, ymin=-3, ymax=3)
fig.show(axes=False)
These aren't particularly successful, but it might be reasonable to tweak these.
Choose linear combination of embeddings¶
Choose a linear combination of embeddings. (Below, we choose a random linear combination with coefficients in $(0, 1)$). For each point $z \in O_K$, we plot $\sum_i c_i \sigma_i(z)$. This doesn't use color. Disks are sized to be inversely proportional to the square of the global height of $z$. We plot all points in the ring of integers
import itertools
import matplotlib as mpl
import matplotlib.pyplot as plt
cm = mpl.colormaps.get('inferno')
def random_list(num):
ret = []
for idx in range(num):
ret.append(random())
return ret
def linear_comb_embed_viz(K, size=10, decexp=2):
pts = list(K.elements_of_bounded_height(bound=size))
all_embeds = K.embeddings(CC)
xs = []
ys = []
ss = []
coeffs = random_list(len(all_embeds))
for pt in pts:
z = 0
for idx, embed in enumerate(all_embeds):
z += coeffs[idx] * embed(pt)
x = real(z)
y = imag(z)
xs.append(x)
ys.append(y)
ss.append(float(20. / (pt.global_height()^decexp + 1)))
fig = plt.figure(figsize=[8, 8])
ax = fig.subplots(1, 1)
ax.set_frame_on(False)
ax.set_xticks([])
ax.set_yticks([])
ax.scatter(xs, ys, c='k', s=ss)
return fig
fig = linear_comb_embed_viz(Fgauss, 10)
fig = linear_comb_embed_viz(Feis, 10)
fig = linear_comb_embed_viz(Frq, 10)
fig = linear_comb_embed_viz(Fd4, 10)
We print two versions of the same number field to note that there is a choice of randomness here. This is similar to choosing a random projection of the "simple" points on the lattice to the complex numbers.
fig = linear_comb_embed_viz(Fd4, 10)
Kd8.<a> = NumberField(x^8 + 7*x^3 - 2*x + 3)
fig = linear_comb_embed_viz(Kd8, 75)
Kd3_2.<a> = NumberField(x^3 - 2*x + 3)
fig = linear_comb_embed_viz(Kd3_2, 10)
Ring of integers¶
For the remaining visualizations, we restrict our attention to the lattice of the ring of integers.
First, we produce all linear combinations with coefficients bounded by $B$ of a basis for the ring of integers. We currently choose the basis given by sage --- more generally, we would use the basis from the LMFDB. We plot each embedding of each point, and we color the embedded point via a colormap (below, we use matplotlib
's inferno colormap) based on the index of the embedding in the list of embeddings.
def make_linear_combinations(basis, coefflimit=5):
for prod in itertools.product(
range(-coefflimit, coefflimit+1),
repeat=len(basis)
):
yield sum((coeff*basis_elem for coeff, basis_elem in zip(prod, basis)))
def makepts(elems, embedding):
pts = []
for elem in elems:
z = embedding(elem)
x = real(z)
y = imag(z)
s = 100./abs(elem.norm())
pts.append((x, y, s))
return pts
def ring_of_int_viz(K, climit=5):
Ri = K.ring_of_integers()
basis = Ri.basis()
embeds = K.embeddings(CC)
combos = list(make_linear_combinations(basis, climit))
fig = plt.figure(figsize=[8, 8])
ax = fig.subplots(1, 1)
for idx, embed in enumerate(embeds):
pts = makepts(combos, embed)
xs, ys, ss = list(zip(*pts))
color = cm(idx * 1.0 / len(embeds))
ax.scatter(xs, ys, s=ss, color=color)
ax.set_frame_on(False)
ax.set_xticks([])
ax.set_yticks([])
return fig
fig = ring_of_int_viz(Fgauss, 8)
fig = ring_of_int_viz(Feis, 8)