Skip to content

Commit 7ddb44c

Browse files
committed
Adding method to determine whether a number is a Margulis number to dev.
1 parent 400f789 commit 7ddb44c

1 file changed

Lines changed: 185 additions & 0 deletions

File tree

dev/margulis_number.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
from snappy.geometric_structure.cusp_neighborhood.tiles_for_cusp_neighborhood import mcomplex_for_tiling_cusp_neighborhoods
2+
from snappy.geometric_structure.geodesic.add_core_curves import add_r13_core_curves
3+
from snappy.geometric_structure.geodesic.geodesic_start_point_info import (
4+
GeodesicStartPointInfo, compute_geodesic_start_point_info)
5+
from snappy.geometric_structure.geodesic.tiles_for_geodesic import compute_tiles_for_geodesic
6+
from snappy.geometric_structure import (
7+
add_r13_geometry, add_filling_information)
8+
from snappy.hyperboloid.distances import (
9+
distance_r13_horoballs, distance_r13_lines, distance_r13_horoball_line)
10+
from snappy.math_basics import correct_min, correct_max, is_RealIntervalFieldElement
11+
from snappy.snap.t3mlite import Mcomplex
12+
from snappy.tiling.floor import floor_as_integers
13+
14+
import itertools
15+
16+
def _ceil(v):
17+
if is_RealIntervalFieldElement(v):
18+
return v.ceil().upper().round()
19+
else:
20+
return int(v.ceil())
21+
22+
def epsilon_thin_tube_radius_candidate(cosh_epsilon, lambda_):
23+
c = lambda_.imag().cos()
24+
f = ((cosh_epsilon - c) /
25+
(lambda_.real().cosh() - c))
26+
RIF = f.parent()
27+
return correct_max([f, RIF(1)]).sqrt().arccosh()
28+
29+
def epsilon_this_tube_radius(epsilon, lambda_):
30+
cosh_epsilon = epsilon.cosh()
31+
max_power = _ceil(epsilon / lambda_.real()) + 1
32+
return correct_max(
33+
[ epsilon_thin_tube_radius_candidate(cosh_epsilon, n * lambda_)
34+
for n in range(1, max_power) ])
35+
36+
def length_shortest_slope(cusp_shape):
37+
RF = cusp_shape.real().parent()
38+
39+
one = RF(1)
40+
half = one / 2
41+
42+
result = one
43+
for q in itertools.count(start=1):
44+
if abs(q * cusp_shape.imag()) > result:
45+
return result
46+
z = q * cusp_shape
47+
for p in floor_as_integers(z.real() + half):
48+
result = correct_min([result, (z - p).abs()])
49+
50+
def epsilon_thin_cusp_area(epsilon, cusp_shape):
51+
h = 2 * (epsilon / 2).sinh()
52+
l = length_shortest_slope(cusp_shape)
53+
return cusp_shape.imag() * (h / l) ** 2
54+
55+
def compute_tiles_for_cusp(vertex, cusp_area, tet_to_thin_tiles):
56+
scale = (cusp_area / vertex.cusp_area).sqrt()
57+
d = scale.log()
58+
59+
for tile in vertex.tiles():
60+
if tile.lower_bound_distance > d:
61+
break
62+
tet_to_thin_tiles[tile.lifted_tetrahedron.tet.Index].append(
63+
('Cusp',
64+
vertex.Index,
65+
tile.inverse_lifted_geometric_object.defining_vec / scale))
66+
67+
def compute_tiles_for_tube(mcomplex, index, word, radius, tet_to_thin_tiles):
68+
info = compute_geodesic_start_point_info(mcomplex, word)
69+
for tile in compute_tiles_for_geodesic(mcomplex, info):
70+
if tile.lower_bound_distance > radius:
71+
break
72+
tet_to_thin_tiles[tile.lifted_tetrahedron.tet.Index].append(
73+
('Geodesic',
74+
index,
75+
tile.inverse_lifted_geometric_object))
76+
77+
def is_margulis_number(M, epsilon, bits_prec=None, verified=False):
78+
"""
79+
Given a cusped (unfilled) Manifold M and epsilon, returns
80+
(True, None, cusp_areas, geodesic_tubes) if epsilon is a Margulis number
81+
for M.
82+
83+
Otherwise, returns
84+
(False, intersection_info, cusp_areas, geodesic_tubes).
85+
and (False, INFO) otherwise.
86+
87+
If verified=True, then epsilon has to be an element of SageMath's
88+
RealIntervalField.
89+
90+
sage: M = Manifold("m004")
91+
sage: is_margulis_number(M, RIF(0.9624), bits_prec=53, verified=True)
92+
(True, None, [3.463918425009?], [])
93+
sage: is_margulis_number(M, RIF(0.9625), bits_prec=53, verified=True)
94+
(False, (('Cusp', 0, None), ('Cusp', 0, None)), [3.464693049062?], [])
95+
96+
>>> M=Manifold("o9_10000")
97+
>>> is_margulis_number(M, 1.224, bits_prec=100, verified=False)
98+
(True,
99+
None,
100+
[1.72792668345645],
101+
[(0, 'f', 1.39210481741114),
102+
(1, 'cd', 0.826529632065272),
103+
(2, 'a', 0.645587876523417)])
104+
>>> is_margulis_number(M, 1.225, bits_prec=100, verified=False)
105+
(False,
106+
(('Geodesic', 2, 'a'), ('Geodesic', 1, 'cd')),
107+
[1.73109597506533],
108+
[(0, 'f', 1.39308748906063),
109+
(1, 'cd', 0.827185488132424),
110+
(2, 'a', 0.646218515259472)])
111+
"""
112+
113+
cusp_shapes = M.cusp_info(
114+
'shape', bits_prec=bits_prec, verified=verified)
115+
cusp_areas = [ epsilon_thin_cusp_area(epsilon, cusp_shape)
116+
for cusp_shape in cusp_shapes ]
117+
118+
geodesics = M.length_spectrum_alt(
119+
max_len=epsilon, bits_prec=bits_prec, verified=verified)
120+
121+
geodesic_tubes = [
122+
(index,
123+
geodesic['word'],
124+
epsilon_this_tube_radius(epsilon, geodesic['length']))
125+
for index, geodesic in enumerate(geodesics) ]
126+
127+
mcomplex = mcomplex_for_tiling_cusp_neighborhoods(
128+
M, verified=verified, bits_prec=bits_prec)
129+
add_filling_information(mcomplex, M)
130+
add_r13_core_curves(mcomplex, M)
131+
132+
# List for each ideal tetrahedron of the fundamental polyhedron,
133+
# lifts of the cusp neighborhoods or geodesic tubes
134+
# intersecting that tetrahedron.
135+
tet_to_thin_tiles = [ [] for tet in mcomplex.Tetrahedra ]
136+
137+
for vertex, cusp_area in zip(mcomplex.Vertices, cusp_areas):
138+
# Add the lifts of the this cusp neighborhood as triples
139+
# ('Cusp', vertex index, light-like vector)
140+
# where light-like vector defines the horoball.
141+
compute_tiles_for_cusp(vertex, cusp_area, tet_to_thin_tiles)
142+
143+
for index, word, radius in geodesic_tubes:
144+
# Add the lifts of this geodesic tube as triples
145+
# ('Geodesic', index of geodesic, snappy.hyperboloid.line.R13Line)
146+
# where R13Line is the core curve of the tube.
147+
compute_tiles_for_tube(
148+
mcomplex, index, word, radius, tet_to_thin_tiles)
149+
150+
for tiles in tet_to_thin_tiles:
151+
for i, (tile_type0, index0, object0) in enumerate(tiles):
152+
for tile_type1, index1, object1 in tiles[:i]:
153+
if tile_type0 == 'Cusp':
154+
w0, r0 = (None, 0)
155+
else:
156+
_, w0, r0 = geodesic_tubes[index0]
157+
if tile_type1 == 'Cusp':
158+
w1, r1 = (None, 0)
159+
else:
160+
_, w1, r1 = geodesic_tubes[index1]
161+
162+
if tile_type0 == 'Cusp':
163+
if tile_type1 == 'Cusp':
164+
d = distance_r13_horoballs(object0, object1)
165+
else:
166+
d = distance_r13_horoball_line(object0, object1)
167+
else:
168+
if tile_type1 == 'Cusp':
169+
d = distance_r13_horoball_line(object1, object0)
170+
else:
171+
d = distance_r13_lines(object0, object1)
172+
173+
r = r0 + r1
174+
175+
if d < r:
176+
return False, ((tile_type0, index0, w0),
177+
(tile_type1, index1, w1)), cusp_areas, geodesic_tubes
178+
if d > r:
179+
continue
180+
raise Exception(
181+
"Insufficient precision to determine for %s %d (%s) and %s %d (%s)" % (
182+
tile_type0, index0, format_word(w0),
183+
tile_type1, index1, format_word(w1)))
184+
185+
return True, None, cusp_areas, geodesic_tubes

0 commit comments

Comments
 (0)