hilbert-modular-group 0.1.3__cp312-cp312-macosx_15_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hilbert-modular-group might be problematic. Click here for more details.

@@ -0,0 +1,411 @@
1
+ #cython: profile=True
2
+ """
3
+ Cython versions of Pullback algorithms.
4
+
5
+ Note: These are the algorithms that needs optimizing to make it all faster.
6
+ """
7
+ from sage.functions.other import ceil
8
+ from sage.functions.other import floor
9
+ from sage.categories.sets_cat import cartesian_product
10
+ from sage.arith.misc import gcd
11
+ from sage.structure.sage_object cimport SageObject
12
+ from hilbert_modgroup.upper_half_plane cimport UpperHalfPlaneProductElement__class
13
+ from sage.rings.number_field.number_field_element cimport NumberFieldElement
14
+
15
+ cpdef integral_coordinates_in_box(bounds):
16
+ """
17
+ Find all integral points inside a box in R^n.
18
+
19
+ INPUT:
20
+ - `bounds` -- a list of upper and lower bounds defining a parallelepiped of positive volume.
21
+
22
+ EXAMPLES::
23
+
24
+ sage: from hilbert_modgroup.pullback_cython import integral_coordinates_in_box
25
+ sage: integral_coordinates_in_box([(-1,1),(-1,1)])
26
+ [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1)]
27
+ sage: integral_coordinates_in_box([(-1,1),(-1.5,1.5)])
28
+ [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1)]
29
+ sage: integral_coordinates_in_box([(-1,1),(-1,1),(-0.5,0.5)])
30
+ [(-1, -1, 0),
31
+ (-1, 0, 0),
32
+ (-1, 1, 0),
33
+ (0, -1, 0),
34
+ (0, 0, 0),
35
+ (0, 1, 0),
36
+ (1, -1, 0),
37
+ (1, 0, 0),
38
+ (1, 1, 0)]
39
+ sage: integral_coordinates_in_box([(-1,1),(-1,1),(-0.5,0.5)])
40
+ [(-1, -1, 0),
41
+ (-1, 0, 0),
42
+ (-1, 1, 0),
43
+ (0, -1, 0),
44
+ (0, 0, 0),
45
+ (0, 1, 0),
46
+ (1, -1, 0),
47
+ (1, 0, 0),
48
+ (1, 1, 0)]
49
+ """
50
+ if not isinstance(bounds, list):
51
+ raise ValueError
52
+ n = len(bounds)
53
+ coordinates = []
54
+ for i in range(n):
55
+ lower = ceil(bounds[i][0])
56
+ upper = floor(bounds[i][1])
57
+ if lower > upper:
58
+ raise ValueError("Bounds must give interval of positive length.")
59
+ coordinates.append(range(lower, upper + 1))
60
+ # result = [tuple(x) for x in cartesian_product(coordinates)]
61
+ # return result
62
+ return list(cartesian_product(coordinates))
63
+
64
+ cpdef lattice_elements_in_box(lattice_basis, lattice_bounds, coordinate_bounds, norm_bound=None):
65
+ """
66
+ Return all coordinates of elements in a lattice in R^n restricted to a specific box and with
67
+ coordinates in another box.
68
+
69
+ INPUT:
70
+ - ``lattice_basis`` -- a list of lists of a basis of the embedded lattice
71
+ - ``lattice_bounds`` -- the bounds for the embeddings
72
+ - ``coordinate_bounds`` -- the bounds for the coordinates
73
+ - ``norm_bound`` -- bounds for the norm (default None - meaning that no bounds are applied)
74
+
75
+ EXAMPLES::
76
+
77
+ sage: from hilbert_modgroup.pullback_cython import lattice_elements_in_box
78
+ sage: lattice_elements_in_box([[0,1],[1,0]],[(-1,1),(-1,1)],[(-1,1),(-1,1)])
79
+ [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1)]
80
+ sage: lattice_elements_in_box([[2,0],[0,2]],[(-2,2),(-1,1)],[(-1,1),(-1,1)])
81
+ [(-1, 0), (0, 0), (1, 0)]
82
+ sage: from hilbert_modgroup.all import *
83
+ sage: P1 = HilbertPullback(HilbertModularGroup(5))
84
+ sage: lb, ib = P1._get_lattice_and_ideal_basis()
85
+ sage: lattice_elements_in_box(lb,[(-2,2),(-2,2)],[(-1,1),(-1,1)])
86
+ [(-1, -1), (-1, 0), (0, -1), (0, 0), (0, 1), (1, 0), (1, 1)]
87
+ sage: lattice_elements_in_box(lb,[(-2,2),(-2,2)],[(-1,1),(0,1)])
88
+ [(-1, 0), (0, 0), (0, 1), (1, 0), (1, 1)]
89
+
90
+ """
91
+ coordinates = integral_coordinates_in_box(coordinate_bounds)
92
+ result = []
93
+ n = len(lattice_basis[0])
94
+ for coordinate_vector in coordinates:
95
+ is_within_bounds = True
96
+ if norm_bound:
97
+ norm = 1
98
+ for i in range(n):
99
+ alpha_i = 0.0
100
+ for j in range(n):
101
+ alpha_i = alpha_i + lattice_basis[i][j] * coordinate_vector[j]
102
+ if alpha_i < lattice_bounds[i][0] or alpha_i > lattice_bounds[i][1]:
103
+ # We need to discard this
104
+ is_within_bounds = False
105
+ break
106
+ if norm_bound:
107
+ norm = norm*alpha_i
108
+ if norm_bound and abs(norm) > norm_bound:
109
+ is_within_bounds = False
110
+ # If we are within the bounds we add the number field element.
111
+ if is_within_bounds:
112
+ result.append(coordinate_vector)
113
+ return result
114
+
115
+ cpdef coordinates_to_ideal_elements(coordinates,ideal_basis):
116
+ r"""
117
+ Return elements of an ideal given by coordinates and a basis.
118
+
119
+ INPUT:
120
+ - `coordinates` -- a list of coordinates (as tuples or lists) of the same length as the basis.
121
+ - `ideal_basis` -- the basis of an ideal.
122
+
123
+ EXAMPLES::
124
+
125
+ sage: from hilbert_modgroup.pullback_cython import coordinates_to_ideal_elements
126
+ sage: from hilbert_modgroup.all import *
127
+ sage: P1 = HilbertPullback(HilbertModularGroup(5))
128
+ sage: lb, ib = P1._get_lattice_and_ideal_basis()
129
+ sage: coordinates_to_ideal_elements([[1,1],(2,3)],ib)
130
+ [1/2*a + 1/2, 3/2*a + 1/2]
131
+
132
+ TESTS::
133
+
134
+ sage: coordinates_to_ideal_elements([[1,1],(2,2,1)],ib)
135
+ Traceback (most recent call last):
136
+ ...
137
+ ValueError: Coordinate need to have same length as basis!
138
+
139
+ """
140
+ result = []
141
+ n = len(ideal_basis)
142
+ for coordinate_vector in coordinates:
143
+ if len(coordinate_vector) != n:
144
+ raise ValueError("Coordinate need to have same length as basis!")
145
+ element = 0
146
+ for i,b in enumerate(ideal_basis):
147
+ element += b * coordinate_vector[i]
148
+ result.append(element)
149
+ return result
150
+
151
+
152
+ cpdef find_candidate_cusps(p, z, use_lll=True, use_norm_bound=True, return_sigma_candidates=False,
153
+ initial_bd_d=None,
154
+ use_initial_bd_d=True):
155
+ r"""
156
+ Return candidates for closest cusp to the point ``z`` in the upper half-plane.
157
+
158
+ INPUT:
159
+
160
+ - ``p`` -- object of type HilbertPullback
161
+ - ``z`` -- point in the upper half-plane
162
+ - ``use_lll`` -- boolean (default: `True`) Use the LLL method to find a preliminary bounds
163
+ - ``use_norm_bound`` -- boolean (default: `True`) Use the norm bound together ith the embedding bounds
164
+ - ``return_sigma_candidates`` -- boolean (default: `False`) Return a list of sigma candidates only
165
+ - ``initial_bd_d`` -- positive number (default: `None`) - an initial bound for the distance to nearest cusp.
166
+ - ``use_initial_bd_d`` -- boolean (default: `False`) Use the initial bound
167
+
168
+ OUTPUT:
169
+ - list of tuples (sigma,rho) representing cusps
170
+
171
+ EXAMPLES::
172
+
173
+ sage: from hilbert_modgroup.all import *
174
+ sage: from hilbert_modgroup.pullback_cython import find_candidate_cusps
175
+ sage: H1 = HilbertModularGroup(5)
176
+ sage: P1 = HilbertPullback(H1)
177
+ sage: z=UpperHalfPlaneProductElement([CC(0,1),CC(0,1)])
178
+ sage: find_candidate_cusps(P1,z)
179
+ [(1, 0), (-1, -1), (0, 1), (1, -1)]
180
+ sage: z=UpperHalfPlaneProductElement([CC(0,0.5),CC(0,1)])
181
+ sage: find_candidate_cusps(P1,z)
182
+ [(0, 1)]
183
+ sage: H2 = HilbertModularGroup(10)
184
+ sage: P2 = HilbertPullback(H2)
185
+ sage: z=UpperHalfPlaneProductElement([CC(0,1),CC(0,1)])
186
+ sage: find_candidate_cusps(P2,z)
187
+ [(1, 0),
188
+ ...
189
+ sage: len(_)
190
+ 10
191
+ sage: z=UpperHalfPlaneProductElement([CC(2.58,0.5),CC(0.5,0.5)]) # long time (1 second)
192
+ sage: len(find_candidate_cusps(P2,z)) # long time (1 second)
193
+ 271
194
+
195
+ """
196
+ ideal_basis = p._construct_ideal(1).integral_basis()
197
+ lattice_basis = p.basis_matrix_ideal()
198
+ n = len(lattice_basis[0])
199
+ # Make lattice basis to a nested list to avoid creation of FreeModule elements
200
+ lattice_basis = [[lattice_basis[i][j] for j in range(n)] for i in range(n)]
201
+ ## Initial bound:
202
+ if use_lll:
203
+ candidate_cusp = p.get_heuristic_closest_cusp(z)
204
+ else:
205
+ candidate_cusp = None
206
+ if candidate_cusp and use_initial_bd_d:
207
+ dist = distance_to_cusp(p, candidate_cusp[0], candidate_cusp[1], z)
208
+ else:
209
+ dist = None
210
+ if use_norm_bound:
211
+ norm_bound_sigma = p._bound_for_sigma_norm(z, dist)
212
+ norm_bound_sigma = norm_bound_sigma*(1+2**(-z[0].prec()/2)) # correct for numerical errors
213
+ else:
214
+ norm_bound_sigma = None
215
+ if use_initial_bd_d and initial_bd_d and (not dist or dist > initial_bd_d):
216
+ dist = initial_bd_d
217
+ # norm_bound_rho = p._bound_for_rho_norm(z, dist)
218
+ one = p.number_field()(1)
219
+ coordinate_bounds = p._bound_for_sigma_coordinates(z, initial_bd_d=dist,use_initial_bd_d=use_initial_bd_d)
220
+ embedding_bounds = p._bound_for_sigma_embeddings(z, initial_bd_d=dist,use_initial_bd_d=use_initial_bd_d)
221
+ coordinate_bounds = [(-b,b) for b in coordinate_bounds]
222
+ embedding_bounds = [(-b,b) for b in embedding_bounds]
223
+ sigma_candidates_coordinates = lattice_elements_in_box(lattice_basis,
224
+ embedding_bounds,
225
+ coordinate_bounds, norm_bound=norm_bound_sigma)
226
+ sigma_candidates = coordinates_to_ideal_elements(sigma_candidates_coordinates,
227
+ ideal_basis)
228
+ if return_sigma_candidates:
229
+ return sigma_candidates
230
+ # To remove duplicates we keep track of the quotients rho/sigma (for sigma !=0)
231
+ quotients = {}
232
+ if not candidate_cusp or candidate_cusp[1] == 0:
233
+ result = [(p.number_field()(1), p.number_field()(0))]
234
+ elif candidate_cusp[0] == 0:
235
+ result = [(p.number_field()(0), p.number_field()(1))]
236
+ else:
237
+ # We always have infinity since sigma=0 is always within the bounds.
238
+ result = [candidate_cusp,(p.number_field()(1), p.number_field()(0))]
239
+ q = candidate_cusp[0]/candidate_cusp[1]
240
+ ngcd = gcd(candidate_cusp[0].norm(),candidate_cusp[1].norm())
241
+ quotients = {q: (ngcd,(candidate_cusp[0],candidate_cusp[1]))}
242
+ for s in sigma_candidates:
243
+ if s == 0:
244
+ continue
245
+ rho_coordinate_bounds = p._bound_for_rho_coordinates(z, s, initial_bd_d=dist, use_initial_bd_d=use_initial_bd_d)
246
+ rho_coordinate_bounds = [(-b,b) for b in rho_coordinate_bounds]
247
+ rho_embedding_bounds = p._bound_for_rho_embeddings(z, s, initial_bd_d=dist, use_initial_bd_d=use_initial_bd_d)
248
+
249
+ rho_candidates_coordinates = lattice_elements_in_box(lattice_basis,
250
+ rho_embedding_bounds,
251
+ rho_coordinate_bounds)
252
+ rho_candidates = coordinates_to_ideal_elements(rho_candidates_coordinates,
253
+ ideal_basis)
254
+
255
+ for r in rho_candidates:
256
+ if r == 0 and (r,one) not in result:
257
+ result.append((r,one))
258
+ if r == 0:
259
+ continue
260
+ quo = r/s
261
+ ngcd = gcd(r.norm(), s.norm())
262
+ if quo in quotients:
263
+ # We try to pick the representative cusp that has the smallest norm gcd
264
+ if ngcd < quotients[quo][0]:
265
+ result.remove(quotients[quo][1])
266
+ else:
267
+ continue
268
+ result.append((r,s))
269
+ quotients[quo] = (ngcd,(r,s))
270
+ return result
271
+
272
+ cpdef find_closest_cusp(p, z, return_multiple=False, use_lll=True, use_norm_bound=True):
273
+ r"""
274
+ Return the closest cusp(s) to z
275
+
276
+ INPUT:
277
+ - ``p`` -- A HilbertPullback object
278
+ - ``z`` -- point in the upper half-plane
279
+ - ``return_multiple`` -- boolean (default False), set to True to return multiple closest cusp if it is no unique.
280
+ - ``use_lll`` -- boolean (default True) if True uses the LLL method to obtain an initial bound
281
+ - ``use_norm_bound`` -- boolean (default True) if True uses the norm bound as well as the embedding bounds
282
+
283
+ OUTPUT:
284
+ - tuple representing a cusp (if return_multiple == False)
285
+ - list of tuples representing cusps (if return_multiple == True)
286
+
287
+ EXAMPLES::
288
+
289
+ sage: from hilbert_modgroup.all import *
290
+ sage: from hilbert_modgroup.pullback_cython import find_closest_cusp
291
+ sage: H1 = HilbertModularGroup(5)
292
+ sage: P1 = HilbertPullback(H1)
293
+ sage: z=UpperHalfPlaneProductElement([CC(0,1),CC(0,1)])
294
+ sage: find_closest_cusp(P1,z)
295
+ (1, 0)
296
+ sage: find_closest_cusp(P1,z,return_multiple=True)
297
+ [(1, 0), (0, 1)]
298
+ sage: z=UpperHalfPlaneProductElement([CC(0,0.5),CC(0,1)])
299
+ sage: find_closest_cusp(P1,z)
300
+ (0, 1)
301
+ sage: find_closest_cusp(P1,z,return_multiple=True)
302
+ [(0, 1)]
303
+
304
+ sage: H2 = HilbertModularGroup(10)
305
+ sage: P2 = HilbertPullback(H2)
306
+ sage: z=UpperHalfPlaneProductElement([CC(0,1),CC(0,1)])
307
+ sage: find_closest_cusp(P2,z)
308
+ (1, 0)
309
+ sage: find_closest_cusp(P2,z,return_multiple=True)
310
+ [(1, 0), (0, 1)]
311
+ sage: z=UpperHalfPlaneProductElement([CC(-2.58,0.5),CC(0.5,0.5)])
312
+ sage: find_closest_cusp(P2,z,return_multiple=True)
313
+ [(-a + 2, -2)]
314
+
315
+ """
316
+ cusp_candidates = find_candidate_cusps(p, z, use_lll=use_lll, use_norm_bound=use_norm_bound)
317
+ min_cusp = cusp_candidates[0]
318
+ min_d = distance_to_cusp(p,min_cusp[0], min_cusp[1], z)
319
+ if return_multiple:
320
+ min_cusp = [min_cusp]
321
+ min_cusp_bound = p._bound_for_closest_cusp()
322
+ eps = 2.0**(4-53) # Numerical precision for when we consider cusps to be equally close.
323
+ # There may be many identical cusps in this list.
324
+ # In particular there might be many
325
+ for c in cusp_candidates[1:]:
326
+ d = distance_to_cusp(p, c[0],c[1], z)
327
+ if abs(d-min_d)<eps and return_multiple:
328
+ min_cusp.append(c)
329
+ if d < min_d - 2*eps:
330
+ if return_multiple:
331
+ min_cusp = [c]
332
+ else:
333
+ min_cusp = c
334
+ min_d = d
335
+ if d < min_cusp_bound:
336
+ break
337
+ # We have already filtered away identical cusps but as a final stage
338
+ # we also check if the minimal cusps are equal to any of the fixed representatives.
339
+ # other than infinity which is already taken care of
340
+ if p.group().ncusps() == 1:
341
+ return min_cusp
342
+ result = []
343
+ for cusp in p.group().cusps()[1:]:
344
+ c, d = cusp.numerator(),cusp.denominator()
345
+ quo = c/d
346
+ if return_multiple:
347
+ for r,s in min_cusp:
348
+ if s == 0:
349
+ result.append((r, s))
350
+ elif r == 0 and (0,1) not in result:
351
+ result.append((0,1))
352
+ elif r == 0:
353
+ continue
354
+ elif r/s == quo and (c,d) not in result:
355
+ result.append((c,d))
356
+ elif r/s != quo:
357
+ result.append((r,s))
358
+ elif min_cusp[1] != 0 and min_cusp[0]/min_cusp[1] == quo:
359
+ result = (c,d)
360
+ else:
361
+ result = min_cusp
362
+ return result
363
+
364
+ cpdef distance_to_cusp(SageObject p, NumberFieldElement r, NumberFieldElement s,
365
+ UpperHalfPlaneProductElement__class z):
366
+ r"""
367
+ Return the distance from an element in the upper half-plane to a cusp.
368
+
369
+ INPUT:
370
+ - ``p`` - Should be an object of type HilbertPullback (but this is not recognized by Cython so I use SageObject instead)
371
+ - ``r`` - Number field element, numerator of cusp
372
+ - ``s`` - Number field element, denomnator of cusp
373
+ - ``z`` - Point in the upper half-plane
374
+
375
+ EXAMPLES::
376
+
377
+ sage: from hilbert_modgroup.all import *
378
+ sage: from hilbert_modgroup.pullback_cython import distance_to_cusp
379
+ sage: H1 = HilbertModularGroup(5)
380
+ sage: P1 = HilbertPullback(H1)
381
+ sage: K = P1.number_field()
382
+ sage: z=UpperHalfPlaneProductElement([CC(0,1),CC(0,1)])
383
+ sage: distance_to_cusp(P1,K(0),K(1),z) # abs tol 1e-10
384
+ 1.0
385
+ sage: distance_to_cusp(P1,K(1),K(1),z) # abs tol 1e-10
386
+ 2.0
387
+ sage: z=UpperHalfPlaneProductElement([CC(0,0.5),CC(0,1)])
388
+ sage: distance_to_cusp(P1,K(0),K(1),z) # abs tol 1e-10
389
+ 0.707106781186548
390
+ sage: H2 = HilbertModularGroup(10)
391
+ sage: P2 = HilbertPullback(H2)
392
+ sage: K = P2.number_field()
393
+ sage: z=UpperHalfPlaneProductElement([CC(0,1),CC(0,1)])
394
+ sage: distance_to_cusp(P2,K(0),K(1),z) # abs tol 1e-10
395
+ 1.0
396
+ sage: z=UpperHalfPlaneProductElement([CC(-2.58,0.5),CC(0.5,0.5)])
397
+ sage: a = K.gen()
398
+ sage: distance_to_cusp(P2,a-5,-a,z) # abs tol 1e-10
399
+ 1.01308408502928
400
+
401
+ """
402
+ cdef list rlist,slist
403
+ ideal_rs = r.parent().fractional_ideal(r,s)
404
+ rlist = r.complex_embeddings()
405
+ slist = s.complex_embeddings()
406
+ n = len(slist)
407
+ d = 1
408
+ for i in range(n):
409
+ d = d* ((-z._x[i] * slist[i] + rlist[i]) ** 2 * z._y[i] ** -1 + slist[i] ** 2 * z._y[i])
410
+ d = ideal_rs.norm()**-1*d.sqrt()
411
+ return d
@@ -0,0 +1,59 @@
1
+ from sage.structure.element cimport Element
2
+ from sage.structure.parent cimport Parent
3
+ from sage.rings.complex_mpc cimport MPComplexField_class, MPComplexNumber
4
+ from sage.rings.real_mpfr cimport RealNumber
5
+ from sage.categories.map cimport Map
6
+
7
+ cdef class ComplexPlaneProduct__class(Parent):
8
+ cdef int _degree
9
+ cdef int _prec
10
+
11
+ cdef class UpperHalfPlaneProduct__class(ComplexPlaneProduct__class):
12
+ pass
13
+
14
+ cdef class AnytoCPP(Map):
15
+ pass
16
+ cdef class ComplexPlaneProductElement__class(Element):
17
+ # cdef double *_x
18
+ # cdef double *_y
19
+ cdef int _degree
20
+ cdef list _x
21
+ cdef list _y
22
+ cdef list _z
23
+ cdef int _prec
24
+ cdef int _verbose
25
+ cdef MPComplexField_class _base_ring
26
+
27
+ cdef MPComplexNumber _norm
28
+ cdef RealNumber _imag_norm
29
+ cdef RealNumber _real_norm
30
+ cdef RealNumber _abs_square_norm
31
+ cdef int _norm_set
32
+ cdef int _imag_norm_set
33
+ cdef int _real_norm_set
34
+ cdef int _abs_square_norm_set
35
+ cdef int _is_in_upper_half_plane
36
+ cpdef imag(self)
37
+ cpdef real(self)
38
+ cpdef _is_equal(self, ComplexPlaneProductElement__class other)
39
+ cpdef __copy__(self)
40
+ cpdef z(self)
41
+ cpdef set_prec(self,prec)
42
+ cpdef abs(self)
43
+ cpdef imag_norm(self)
44
+ cpdef real_norm(self)
45
+ cpdef abs_square_norm(self)
46
+ cpdef imag_trace(self)
47
+ cpdef real_trace(self)
48
+ cpdef trace(self)
49
+ cpdef reflect(self)
50
+
51
+ cpdef prec(self)
52
+ cpdef norm(self)
53
+ cpdef vector(self)
54
+ cpdef vector_norm(self, p=?)
55
+ cpdef apply(self, m)
56
+
57
+ cdef class UpperHalfPlaneProductElement__class(ComplexPlaneProductElement__class):
58
+ pass
59
+