Skip to content

Commit e6f1c03

Browse files
committed
Refactor Schottky and Theta classes for improved functionality and performance
1 parent 3b6e5fe commit e6f1c03

File tree

2 files changed

+64
-71
lines changed

2 files changed

+64
-71
lines changed

darmonpoints/schottky.py

+41-42
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from .divisors import Divisors, DivisorsElement
2828
from .meromorphic import *
2929
from .theta import *
30-
from .util import muted
30+
from .util import muted, our_sqrt
3131

3232
infinity = Infinity
3333

@@ -100,17 +100,20 @@ def uniq(lst):
100100

101101

102102
@muted
103-
@cached_function
104103
def find_eigenvector_matrix(g):
104+
K = g.parent().base_ring()
105105
t = g.trace()
106106
n = g.determinant()
107-
delta = (t * t - 4 * n).sqrt()
108-
vaps = sorted([(t + delta) / 2, (t - delta) / 2], key=lambda o: o.valuation())
109-
veps = sum(((g - l).right_kernel().basis() for l in vaps), [])
107+
delta = (t * t - K(4) * n).sqrt()
108+
vaps = sorted([(t + delta) / K(2), (t - delta) / K(2)], key=lambda o: o.valuation())
109+
assert len(vaps) == 2
110+
veps = []
111+
for l in vaps:
112+
B = g - l
113+
veps.append((B[0,1], -B[0,0])) # The right kernel of a rank-1 2x2 matrix
110114
return g.matrix_space()(veps).transpose()
111115

112116

113-
@cached_function
114117
def find_parameter(g, ball, pi=None, check=True):
115118
if pi is None:
116119
pi = g.parent().base_ring().uniformizer()
@@ -210,6 +213,8 @@ def theta_naive(self, m, a=ZZ(0), b=ZZ(1), z=None, gamma=None, **kwargs):
210213
K = self.K
211214
if z is Infinity:
212215
return K(1)
216+
if isinstance(z, Divisors): # z is a divisor!
217+
return prod(self.theta_naive(m, a, b, P, gamma, **kwargs)**n for P, n in z)
213218
if gamma is not None:
214219
if b != ZZ(1):
215220
raise ValueError("If gamma is specified, then b should not")
@@ -225,9 +230,9 @@ def theta_naive(self, m, a=ZZ(0), b=ZZ(1), z=None, gamma=None, **kwargs):
225230
can_stop = True
226231
for _, g in self.enumerate_group_elements(i):
227232
ga = act(g, a)
228-
tmp1 = K(1) if ga is Infinity else z - ga
233+
tmp1 = K(1) if ga is Infinity or z == ga else z - ga
229234
gb = act(g, b)
230-
tmp2 = K(1) if gb is Infinity else z - gb
235+
tmp2 = K(1) if gb is Infinity or z == gb else z - gb
231236
new_num *= tmp1
232237
new_den *= tmp2
233238
try:
@@ -353,7 +358,7 @@ def construct_tree(self, level):
353358
return self.NJtree
354359

355360
@cached_method
356-
@muted
361+
# @muted # DEBUG
357362
def fundamental_domain(self, level=1, check=True):
358363
while True:
359364
level += 1
@@ -364,7 +369,7 @@ def fundamental_domain(self, level=1, check=True):
364369
except ValueError as e:
365370
verbose(str(e))
366371

367-
@muted
372+
# @muted # DEBUG
368373
def _fundamental_domain(self, i0=None, check=True):
369374
tree = self.NJtree
370375
if i0 is None:
@@ -546,13 +551,14 @@ def test_fundamental_domain(self):
546551
gens = self._generators
547552
test_fundamental_domain(gens, balls)
548553

549-
def balls(self):
554+
def balls(self, generators=None):
550555
try:
551556
return self._balls
552557
except AttributeError:
553558
pass
554559
K = self.base_ring()
555-
generators = self._generators
560+
if generators is None:
561+
generators = self._generators
556562
G = PreSchottkyGroup(K, generators)
557563
gp = G.group()
558564
good_gens, good_balls, _ = G.fundamental_domain()
@@ -608,22 +614,18 @@ def a_point(self):
608614
return x0
609615
raise RuntimeError("Reached maximum iterations (100)")
610616

611-
def find_point(self, gamma, eps=1, idx=None):
617+
def find_point(self, gamma, eps=1, idx=None, check=True):
612618
balls = self.balls()
613619
gens = self.gens_extended()
614620
if idx is not None:
615621
B = balls[-idx]
616622
if idx > 0:
617623
if self._generators[idx - 1] != gamma:
618624
B1 = next(balls[-i] for i, g in gens if g == gamma)
619-
# print(f'!! {B = } but {B1 = }')
620-
# print(balls)
621625
B = B1
622626
else:
623627
if self._generators[-idx - 1] ** -1 != gamma:
624628
B1 = next(balls[-i] for i, g in gens if g == gamma)
625-
# print(f'!! {B = } but {B1 = }')
626-
# print(balls)
627629
B = B1
628630
else:
629631
B = next(balls[-i] for i, g in gens if g == gamma)
@@ -632,11 +634,10 @@ def find_point(self, gamma, eps=1, idx=None):
632634
except TypeError:
633635
raise RuntimeError(
634636
"Fundamental domain has balls with fractional radius. \
635-
Try with a quadratic (ramified) extension."
637+
Try with a quadratic (ramified) extension."
636638
)
637-
test = lambda x: self.in_fundamental_domain(x, strict=False)
638-
639-
try:
639+
if check:
640+
test = lambda x: self.in_fundamental_domain(x, strict=False)
640641
if not test(ans):
641642
raise RuntimeError(
642643
f"{ans = } should be in fundamental domain, but it is not."
@@ -646,11 +647,6 @@ def find_point(self, gamma, eps=1, idx=None):
646647
raise RuntimeError(
647648
f"gamma * ans = {ga} should be in fundamental domain, but it is not."
648649
)
649-
except RuntimeError:
650-
new_idx = -idx if idx is not None else None
651-
ginv = gamma**-1
652-
ans = self.find_point(ginv, eps, new_idx)
653-
return act(ginv, ans)
654650
return ans
655651

656652
def to_fundamental_domain(self, x):
@@ -731,7 +727,7 @@ def find_equivalent_divisor(self, D):
731727
if e == 0:
732728
continue
733729
zz = self.find_point(g, idx=i + 1)
734-
ans -= Div([(e, zz), (-e, act(g, zz))])
730+
ans += Div([(-e, zz), (e, act(g, zz))])
735731
return ans
736732

737733
def theta(self, prec, a, b=None, **kwargs):
@@ -767,9 +763,7 @@ def theta(self, prec, a, b=None, **kwargs):
767763
s = kwargs.pop("s", None)
768764
if s is not None:
769765
D0 += s * D0
770-
recursive = kwargs.get("recursive", True)
771-
if recursive:
772-
D0 = self.find_equivalent_divisor(D0)
766+
D0 = self.find_equivalent_divisor(D0)
773767
ans = ThetaOC(self, a=D0, b=None, prec=prec, base_ring=K, **kwargs)
774768
z = kwargs.pop("z", None)
775769
improve = kwargs.pop("improve", True)
@@ -788,32 +782,37 @@ def u_function(self, gamma, prec, a=None, **kwargs):
788782
if z is None:
789783
kwargs.pop("z", None)
790784
return lambda z: prod(
791-
self._u_function(gens[abs(i) - 1], prec, a=a)(z) ** ZZ(sgn(i))
785+
self._u_function(abs(i) - 1, prec, a=a)(z) ** ZZ(sgn(i))
792786
for i in wd
793787
)
794788
return prod(
795-
self._u_function(gens[abs(i) - 1], prec, a=a)(z) ** ZZ(sgn(i))
789+
self._u_function(abs(i) - 1, prec, a=a)(z) ** ZZ(sgn(i))
796790
for i in wd
797791
)
792+
naive = kwargs.get('naive', None)
798793
if z is None:
799-
return lambda z: self._u_function(gens[wd[0] - 1], prec, a=a)(z)
794+
return lambda z: self._u_function(wd[0] - 1, prec, a=a, naive=naive)(z)
800795
else:
801-
return self._u_function(gens[wd[0] - 1], prec, a=a)(z)
796+
return self._u_function(wd[0] - 1, prec, a=a, naive=naive)(z)
802797

803798
@cached_method
804-
def _u_function(self, gamma, prec, a):
799+
def _u_function(self, i, prec, a, naive=False):
805800
r"""
806801
Compute u_gamma
807802
"""
808-
(i,) = self.word_problem(gamma)
809-
assert i > 0
803+
gamma = self._generators[i]
810804
if a is None:
811-
a = self.a_point() # self.find_point(gamma, idx=g)
812-
a = self.base_ring()(1) * a
813-
K = a.parent()
805+
a = self.find_point(gamma, idx=i) # self.a_point()
806+
K = a.parent()
807+
else:
808+
a = self.base_ring()(1) * a
809+
K = a.parent()
814810
D = self.find_equivalent_divisor(Divisors(K)([(1, a), (-1, act(gamma, a))]))
815-
ans = ThetaOC(self, a=D, b=None, prec=prec, base_ring=K)
816-
ans = ans.improve(prec)
811+
if naive:
812+
return lambda z : self.theta_naive(prec, z=z, a=a, gamma=gamma)
813+
else:
814+
ans = ThetaOC(self, a=D, b=None, prec=prec, base_ring=K)
815+
ans = ans.improve(prec)
817816
return ans
818817

819818
@cached_method

darmonpoints/theta.py

+23-29
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def __init__(self, G, a=None, b=None, prec=None, **kwargs):
100100
self.K = K
101101
self.G = G
102102
self.p = G.pi
103-
generators = G.generators()
104-
balls = G.balls()
103+
_ = G.generators()
104+
_ = G.balls()
105105
if prec is None:
106106
try:
107107
self.prec = K.precision_cap()
@@ -123,20 +123,21 @@ def __init__(self, G, a=None, b=None, prec=None, **kwargs):
123123
self.b = b
124124
self.m = 1
125125
self.z = K["z"].gen()
126-
126+
self.MM = MeromorphicFunctions(self.K, self.p, self.prec)
127127
params = G.parameters
128128
gens_ext = G.gens_extended()
129-
# self.val will contain the 0 and 1 terms
130-
D1 = sum((g * D for (_, g), _ in zip(gens_ext, params)), self.Div([]))
131-
self.val = D + D1
132-
# self.val = self.z.parent()(1)
133-
# self.val *= prod((self.z - P) ** n for P, n in (D + D1) if P != Infinity)
134-
self.MM = MeromorphicFunctions(self.K, self.p, self.prec)
135-
self.Fnlist = [{}]
136-
D1dict = {i: g * D for (i, g), tau in zip(gens_ext, params)}
137-
for (i, g), tau in zip(gens_ext, params):
138-
gD = sum(g * val for j, val in D1dict.items() if j != -i)
139-
self.Fnlist[0][i] = self.MM(gD, tau)
129+
130+
# Corresponding to words of length exactly 1
131+
D1dict = {i : g * D for i, g in gens_ext}
132+
133+
# Corresponding to words of length exactly 2
134+
# D2dict = {i : sum(g * val) for j, val in D1dict.items() if j != i for i, g in gens_ext }
135+
136+
# self.val will contain the 0, 1 and 2 terms
137+
self.val = sum(D1dict.values(), D)
138+
self.Fnlist = [{ i :
139+
self.MM(sum(g * val for j, val in D1dict.items() if j != -i), tau)
140+
for (i, g), tau in zip(gens_ext, params)}]
140141

141142
def improve(self, m):
142143
gens_ext = self.G.gens_extended()
@@ -151,12 +152,13 @@ def improve(self, m):
151152
)
152153
for it in range(m):
153154
if self.m >= self.prec:
154-
self.Fnlist = [
155+
if len(self.Fnlist) > 0:
156+
self.Fnlist = [
155157
{
156158
ky: sum((F[ky] for F in self.Fnlist[1:]), self.Fnlist[0][ky])
157159
for ky in self.Fnlist[0]
158160
}
159-
]
161+
]
160162
return self
161163
tmp = {}
162164
for (i, gi), tau in zip(gens_ext, params):
@@ -177,9 +179,6 @@ def improve(self, m):
177179
]
178180
return self
179181

180-
def improve_one(self):
181-
return self.improve(1)
182-
183182
def _repr_(self):
184183
a, b = self.a, self.b
185184
if b is None:
@@ -221,23 +220,18 @@ def __call__(self, z, **kwargs):
221220
return self.evaluate(z, **kwargs)
222221

223222
def evaluate(self, z, **kwargs):
224-
# recursive = kwargs.get("recursive", True)
225223
if not isinstance(z, DivisorsElement):
226224
z = self.Div([(1, z)])
227225
if z.degree() != 0:
228-
# inf, wd = self.G.to_fundamental_domain(Infinity)
229226
z -= self.Div([(z.degree(), Infinity)])
230227
z = self.G.find_equivalent_divisor(z)
231-
ans = prod(eval_rat(self.val, P) ** n for P, n in z)
232-
newans = prod(
233-
F(P) ** n for FF in self.Fnlist for ky, F in FF.items() for P, n in z
234-
)
235-
ans *= newans
236-
return ans
228+
ans0 = prod(eval_rat(self.val, P) ** n for P, n in z)
229+
ans1 = prod(F(z) for FF in self.Fnlist for F in FF.values())
230+
return ans0 * ans1
237231

238232
def eval_derivative(self, z, return_value=False):
239-
assert not isinstance(z, DivisorsElement)
240-
# z = self.Div([(1,z)])
233+
if isinstance(z, DivisorsElement):
234+
raise NotImplementedError
241235
z0, wd = self.G.to_fundamental_domain(z)
242236
gens = (
243237
[None]

0 commit comments

Comments
 (0)