Crossing the transcendental divide: from Schottky groups to algebraic curves
Samantha Fairchild, Ángel David Ríos Ortiz
This is a Julia 1.8.2 notebook including computations used in the above named paper
All code was run on a MacBook Air with chip Apple M1 2020 and 8 GB memory
[1]:
import Pkg ##Import any necessary packages
Pkg.add("LinearAlgebra")
Pkg.add("ImplicitEquations")
Pkg.add("Plots")
Resolving package versions...
No Changes to `~/.julia/environments/v1.9/Project.toml`
No Changes to `~/.julia/environments/v1.9/Manifest.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.9/Project.toml`
No Changes to `~/.julia/environments/v1.9/Manifest.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.9/Project.toml`
No Changes to `~/.julia/environments/v1.9/Manifest.toml`
[2]:
using LinearAlgebra ##We use these particular Packages
using ImplicitEquations
using Plots
Generating elements of the free group as matrices
It is important to minimize matrix multiplication, so we generate words of bounded length, keeping a tag to keep track of the first and last letter of each word to allow for computing representatives of cosets.
[3]:
##Function for constructing elements of free group up to bounded length
# list is elements of the form (matrix, last letter, first letter)
##INPUT: len is an integer >1
# g is the genus of the curve we are working with
# mat is a list of g 2x2 matrices generating the free group
function all_words_bounded_length(len,g, mat) #generate all words in the free group of length at most len
word_list= [(Matrix{ComplexF64}(I,2,2),0,0)] #initiate identity element in list
for lev in 1:len
old_list = copy(word_list) #take all words of length < lev
word_list = [(Matrix{ComplexF64}(I,2,2),0,0)]
for oldword in old_list #iterate through all words of length = lev-1 and multiply to generate all words of length lev
for j in 1:g
if oldword[3] !=0 #if not multiplying by the identity, first letter is unchaged
if oldword[2] != -j
push!(word_list, (Matrix{ComplexF64}(oldword[1]*mat[j]), j, oldword[3]))
end
if oldword[2] != j
push!(word_list, (Matrix{ComplexF64}(oldword[1]*mat[j]^(-1)), -j, oldword[3]))
end
else #if multiplying by identity, first letter is last letter
if oldword[2] != -j
push!(word_list, (Matrix{ComplexF64}(oldword[1]*mat[j]), j, j))
end
if oldword[2] != j
push!(word_list, (Matrix{ComplexF64}(oldword[1]*mat[j]^(-1)), -j, -j))
end
end
end
end
end
return word_list
end
all_words_bounded_length (generic function with 1 method)
Helper Functions
[4]:
##Function for easier evaluation of Mobius transformations
function mobius(matrix, num) #given a matrix [a b ; c d] and a number z, return az+b/cz+d
mobius = ((matrix[1,1]* num) + matrix[1,2])/((matrix[2,1]*num) + matrix[2,2])
return mobius
end
##Function for constructing centers and radii of the circles
function point_construct(eta, theta, g)
z1 = exp(im*((theta+eta)/2)) *sec((theta-eta)/2) #Center of generating circle
zs = [exp(((j-1)*2*pi*im) / g) * z1 for j in 1:g] #input first tuple of centers z1,....,zg
ws = [exp(((j-1)*2*pi*im) / g)*conj(z1) for j in 1:g] #input second tuple of center w1,...., wg
rs = [tan((theta-eta)/2) for _ in 1:g] #input radii of circles r1,...,rg
return (zs, ws, rs)
end
##Function for Constructing Matrices of the Schottky Group
function matrix_construct(ws, zs, rs)
g = length(ws)
M = zeros(Complex, 2,2)
mat = [M for idx in 1:g] #create an array of length g where ith entry is matrix of f_i
for j in 1:g
argpt = angle(ws[j] -zs[j]) #Angle between isometric circle centers
A = Matrix{ComplexF64}([(exp(-im*argpt)* ws[j])/rs[j] (-(ws[j]*zs[j]* exp(-2im*argpt) + rs[j]^2)/(rs[j]* exp(-im*argpt))); (exp(-im*argpt)/rs[j]) ((-zs[j]* exp(-im*argpt))/rs[j])])
mat[j] = A
end
return mat
end
##Function for evaluating truncated series
function ptheta(samplepts, mat,g, L) #samples Poincare Theta series for all words of length < L, given as a funciton of z
sampleproj = [zeros(Complex,1,g) for idx in samplepts];
list_of_words = all_words_bounded_length(L,g, mat)
for j in 1:g #create the jth coset G/ [mat[j]]
jCoset = []
for pts in list_of_words
if pts[2] != j
if pts[2] != -j
push!(jCoset, pts)
end
end
end
jCoset_mat = [pts[1] for pts in jCoset]
##Aj and Bj are the fixed points
jDisc = sqrt((mat[j][1,1] - mat[j][2,2])^2 + 4*mat[j][1,2]* mat[j][2,1]) #discriminant is sqrt((a-d)^2 + 4bc)
Aj = ((mat[j][1,1] - mat[j][2,2])+ jDisc)/(2*mat[j][2,1])
Bj = ((mat[j][1,1] - mat[j][2,2])- jDisc)/(2*mat[j][2,1])
#println(Aj)
#println(Bj)
#Now for each point in sapmleproj, evaluate truncated series and place into sampleproj[k][j]
for k in 1:length(samplepts)
samplevalue = 0
for sig in jCoset_mat
samplevalue = samplevalue + 1/(samplepts[k] - mobius(sig, Bj)) - 1/(samplepts[k] - mobius(sig, Aj))
end
sampleproj[k][j] = samplevalue
end
end
return sampleproj
end
ptheta (generic function with 1 method)
Finding Weierstrass points in the case of Genus 2
Input - Genus
Output The approximate algebraic curve associated to the constructed curve, where we compute the 6 weierstrass points.
[5]:
function Genus2_WP(eta, theta, L)
g=2 ##We are in the case of genus 2
#L is the length of words that we are generating in the free group
(zs, ws, rs) = point_construct(eta, theta, g)
println("Initialized circles")
##6 Weierstrass points
##Solve for the two intersection points of C1 with circle centered at sec((theta + eta) / 2)
samplepts = [1, -1, exp(im*theta), exp(im*eta), exp(im* (pi-theta)), exp(im*(pi - eta))]
println("Sampled 6 points on the curve")
##CONSTRUCT MATRICES FOR SCHOTTKY GROUP
mat = matrix_construct(ws, zs, rs)
println("Computed Matrices")
##Evaluate the points in P^1 which are approximate images of the algebraic curve
PointsinPg = ptheta(samplepts, mat, g, L)
##Find approximate algebraic curve by finding 6 Weierstrass points
Wpts = [PointsinPg[j][1]/PointsinPg[j][2] for j in 1:6]
return Wpts
end
Genus2_WP (generic function with 1 method)
[6]:
####INPUT
𝜂 = pi /12 #Pick a positive number less than 𝜃
𝜃 =pi/4 # Pick a positive number less than 𝜋/3
L = 8 # L is the length of words that we generate in the free group
#####OUTPUT
p = Genus2_WP(𝜂,𝜃, L)
Initialized circles
Sampled 6 points on the curve
Computed Matrices
6-element Vector{ComplexF64}:
-11.474487171228489 + 1.030089577387785e-12im
-0.08714986431005209 + 1.1554187097143076e-14im
4.084600904724572 - 8.763125896606477e-13im
-11.787692697355707 + 3.887475415761668e-12im
0.24482196016832608 - 1.3653789500384812e-14im
-0.08483424412857071 + 1.9297359926505143e-15im
[7]:
##Verify conjugate pairs
p[1]*p[2] , p[3]*p[5], p[4]*p[6]
(0.9999999999999962 - 2.2235053851668632e-13im, 0.9999999999999878 - 2.7031084686709243e-13im, 1.0000000000000442 - 3.525381733329332e-13im)
[8]:
####Plot graph using Plots and Implicit Equations
f(x,y) = y^2 - ((x-real(p[1]))* (x-real(p[2]))*(x-real(p[3]))* (x-real(p[4]))*(x-real(p[5]))* (x-real(p[6])))
plot(f ⩵ 0, xlims = (-20,10), ylims=(-1000,1000), M=10, N=10, linewidth=2)
#savefig("realcurve_nearerzero.png")
Computing the Riemann Matrix
[9]:
function Riemann_Matrix(eta,theta, L,g)
##We are in the case of genus g
#L is the length of words that we are generating in the free group
(zs, ws, rs) = point_construct(eta, theta, g)
println("Initialized circles")
##CONSTRUCT MATRICES FOR SCHOTTKY GROUP
mat = matrix_construct(ws, zs, rs)
println("Computed Matrices")
R = zeros(ComplexF64,g, g) ##Initialize a gxg zero matrix
list_of_words = all_words_bounded_length(L,g, mat)
for i in 1:g
for j in i:g #create the i-jth double coset mat[i]\ G/ [mat[j]]
jCoset = []
jCoset_mat = []
for pts in list_of_words
if pts[2] != j && pts[2] != -j && pts[3] != i && pts[3] != -i
push!(jCoset, pts)
end
end
jCoset_mat = [pts[1] for pts in jCoset]
popfirst!(jCoset_mat) #remove identity
##Aj and Bj are the fixed points
jDisc = sqrt((mat[j][1,1] - mat[j][2,2])^2 + 4*mat[j][1,2]* mat[j][2,1]) #discriminant is sqrt((a-d)^2 + 4bc)
Aj = ((mat[j][1,1] - mat[j][2,2])+ jDisc)/(2*mat[j][2,1])
Bj = ((mat[j][1,1] - mat[j][2,2])- jDisc)/(2*mat[j][2,1])
muj = (mat[j][1,1] - mat[j][2,1]*Aj)/(mat[j][1,1] - mat[j][2,1]*Bj)
#println(muj)
iDisc = sqrt((mat[i][1,1] - mat[i][2,2])^2 + 4*mat[i][1,2]* mat[i][2,1]) #discriminant is sqrt((a-d)^2 + 4bc)
Ai = ((mat[i][1,1] - mat[i][2,2])+ jDisc)/(2*mat[i][2,1])
Bi = ((mat[i][1,1] - mat[i][2,2])- jDisc)/(2*mat[i][2,1])
mui = (mat[i][1,1] - mat[i][2,1]*Ai)/(mat[i][1,1] - mat[i][2,1]*Bi)
if i != j
samplevalue = 0
for sig in jCoset_mat ##log {Aj, Bj, f Ai, f Bi}
samplevalue = samplevalue + log((Ai-mobius(sig,Aj)) * (Bi - mobius(sig,Bj)) / (Ai - mobius(sig,Bj))/ (Bi - mobius(sig,Aj)))
end
R[i,j] = samplevalue
R[j,i] = samplevalue
else
samplevalue = log(real(mui)) # add log(mui)
for sig in jCoset_mat
samplevalue = samplevalue + log((Ai-mobius(sig,Aj)) * (Bi - mobius(sig,Bj)) / (Ai - mobius(sig,Bj))/ (Bi - mobius(sig,Aj)))
end
R[i,j] = samplevalue
end
end
end
return R
end
Riemann_Matrix (generic function with 1 method)
[10]:
####INPUT
𝜂 = pi/12;
𝜃 =pi/7;
L = 4;
mat = Riemann_Matrix(𝜂,𝜃 , L,6) ##Compute the genus g Riemann matrix
Initialized circles
Computed Matrices
6×6 Matrix{ComplexF64}:
-3.94483+1.5077e-14im 0.0148278-2.80867e-14im … 0.0148278-1.33155e-14im
0.0148278-2.80867e-14im -3.94483-2.27271e-14im 0.050031-2.27587e-14im
0.050031+1.96342e-14im 0.0148278+3.92626e-14im 0.0326767-3.25849e-14im
0.0326767-2.45312e-14im 0.050031-1.06096e-14im 0.050031+8.20058e-15im
0.050031-2.44069e-14im 0.0326767+2.28302e-14im 0.0148278-7.85421e-15im
0.0148278-1.33155e-14im 0.050031-2.27587e-14im … -3.94483-3.75163e-14im
[11]:
imag(mat/ (2*pi*im)) ##Normalize to have positive definite imaginary part
eigvals(imag(mat/ (2*pi*im))) ## See the eigenvalues are indeed positive
6-element Vector{Float64}:
0.6019929775465542
0.6218339994008196
0.6329607807358603
0.6329607807358741
0.6386422623267828
0.638642262326798
Approximating the Quartic in Genus 3
[12]:
Pkg.add("Distributions") ##Add additional necessary packages
Pkg.add("DynamicPolynomials")
Resolving package versions...
No Changes to `~/.julia/environments/v1.9/Project.toml`
No Changes to `~/.julia/environments/v1.9/Manifest.toml`
Resolving package versions...
No Changes to `~/.julia/environments/v1.9/Project.toml`
No Changes to `~/.julia/environments/v1.9/Manifest.toml`
[13]:
using Distributions
using DynamicPolynomials
Input - Two numbers
Output The approximate algebraic curve associated to the constructed curve, optimized by the method of least squares on the coefficients of basis of monomials over the real points.
[14]:
𝜂 = pi/12;
𝜃 =pi/4;
L = 8; #Length of word trunction
N= 50; #Number of sample points
@polyvar z[1:3] #Define variables for the monomial basis
mons = monomials(z,4)
@polyvar c[1:length(mons)]
mons
15-element MonomialVector{DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, Graded{LexOrder}}:
z₃⁴
z₂z₃³
z₂²z₃²
z₂³z₃
z₂⁴
z₁z₃³
z₁z₂z₃²
z₁z₂²z₃
z₁z₂³
z₁²z₃²
z₁²z₂z₃
z₁²z₂²
z₁³z₃
z₁³z₂
z₁⁴
[15]:
function Genus3_Example(eta, theta, N, L) ## Constructs apporixmate polynomial and real points
g=3 ##We are in the case of genus 3
#L is the length of words that we are generating in the free group
#N is the number of points that we sample on the curve, N must be bigger than number of monomials in g variables
(zs, ws, rs) = point_construct(eta, theta, g)
println("Initialized circles")
##NOW SAMPLE N POINTS UNIFORMLY in the circle
dunif = Uniform( 0, 2*pi) ##Initialize the uniform distribution
samplepts = [exp(im*j) for j in rand(dunif, N)] #sample according to uniform distribution
#Warning: The series do not converge at fixed points, to ensure these are not accidentally sampled
#one could alternatively restrict to the subset of the circle contained in the fundamental domain
println("Sampled N points on the curve")
##CONSTRUCT MATRICES FOR SCHOTTKY GROUP AND COMPUTE EIGENVALUES
mat = matrix_construct(ws, zs, rs)
println("Computed Matrices")
##Evaluate the points in P^g which are approximate images of the algebraic curve
PointsinPg = ptheta(samplepts, mat, g, L)
#return PointsinPg #end early and return image points in Pg
nPoints = [] ##normalized points by looking in the chart where p[3] != 0
for pt in PointsinPg
push!(nPoints, (real(pt[1]/pt[3]), real(pt[2]/pt[3])))
end
println("Computed sample points in Pg")
#return nPoints #end early and return only sampled points on the real curve
##Find approximate algebraic curve by solving least squares
A = zeros(ComplexF64,Int(N), length(mons))
for k in 1:N
for j in 1:length(mons)
A[k,j]= mons[j](Tuple(PointsinPg[k])) #A[k,j]= subs(mons[j], z=> Tuple(PointsinPg[k]))
end
end
#Notice the vector of solutions A vec = 0 is given by (I-pseudoinverse(A)A)w for w any fixed vector
vec = (Matrix(I,15,15) - pinv(A)*A)*[1 for pt in 1:15]
v = vec/norm(vec) #normalize to length 1
polyf = 0 ##Construct the approximate polynomial
for j in 1:length(mons)
polyf = polyf + v[j]*mons[j]
end
return polyf, nPoints #return the polynomial and the real points
end
Genus3_Example (generic function with 1 method)
[16]:
p1, realpoints = Genus3_Example(𝜂,𝜃 , N, L) ##Note that if the sampling is bad, then we might end up with a bad approximation. Rerun, and see warning above to avoid this
p1
#realpoints
Initialized circles
Sampled N points on the curve
Computed Matrices
Computed sample points in Pg
Plotting approximate curve via a contour plot and the real points
[17]:
f(x, y) = real(p1(x,y,1)) #consider the real part of the polynomial
x = -10:.03:6 #x_0: r:x_1 means sample from x_0 to x_1 in intervals of .005
y = -10:.03:6
w = @. f(x',y)
myplot = scatter([pts[1] for pts in realpoints], [pts[2] for pts in realpoints], lab = "sampled real points")
myplot = contour!(x, y, w, color=:turbo, lw = 3)#, aspect_ratio=:equal)
myplot
#savefig(myplot, "etapi/12_thetapi/4_N50_L8.png")
[18]:
### Zooming in to a smaller range
x = -.15:.00001:-.12#range(-20, 10, length=5000)
y = -.15:.00001:-.12#range(-20, 10, length=5000)
w = @. f(x',y)
scatterx = []
scattery=[]
for pts in realpoints
if pts[1] >= -.2 && pts[1] <=-.05
if pts[2] >= -.2 && pts[2] <=-.05
push!(scatterx, pts[1])
push!(scattery, pts[2])
end
end
end
smallplot = scatter(scatterx, scattery, lab = "sampled real points")
smallplot = contour!(x, y, w, color=:turbo, lw = 3)#, aspect_ratio=:equal)
[19]:
png(myplot, "etapi12_thetapi4_N50_L8.png")
"etapi12_thetapi4_N50_L8.png"
[20]:
png(smallplot, "etapi12_thetapi4_N50_L8_zoom")
"etapi12_thetapi4_N50_L8_zoom.png"