Sample flipper scripts¶
Here are several small sample scripts that demonstrate some of the features of flipper.
Mapping classes¶
We can compute some basic properties of a mapping class:
import flipper
S = flipper.load('S_1_2')
word = 'aCBACBacbaccbAaAcAaBBcCcBBcCaBaaaABBabBcaBbCBCbaaa'
h = S.mapping_class(word)
print('Built the mapping class h := \'%s\'.' % word)
print('h has order %s (where 0 == infinite).' % h.order())
print('h is %s.' % h.nielsen_thurston_type())
try:
print('h leaves L := %s projectively invariant.' % h.invariant_lamination().projective_string())
print('and dilates it by a factor of %s.' % h.dilatation())
except flipper.AssumptionError:
print('We cannot find a projectively invariant lamination for h as it is not pseudo-Anosov.')
All words¶
Flipper can systematically generate all words in a given generating set. This is useful for exhaustively searching for mapping classes with rare properties:
from time import time
import flipper
length = 7
S = flipper.load('S_1_2') # Get an EquippedTriangulation.
start_time = time()
all_words = list(S.all_words(length))
print('Built %d words in %0.3fs.' % (len(all_words), time() - start_time))
# In parallel:
start_time = time()
all_words2 = list(S.all_words(length, cores=2))
print('Built %d words in %0.3fs.' % (len(all_words2), time() - start_time))
assert len(all_words) == len(set(all_words)) and set(all_words) == set(all_words2)
Invariant laminations¶
We can see just how good flipper is at finding invariant laminations:
from time import time
import flipper
times = {}
surface = 'S_3_1'
length = 20
num_samples = 100
S = flipper.load(surface)
for index in range(num_samples):
monodromy = S.random_word(length)
h = S.mapping_class(monodromy)
start_time = time()
try:
h.invariant_lamination()
times[monodromy] = time() - start_time
print('%3d/%d: %s %s, Time: %0.3f' % (index+1, num_samples, surface, monodromy, times[monodromy]))
except flipper.AssumptionError:
times[monodromy] = time() - start_time
print('%3d/%d: %s %s, not pA, Time: %0.3f' % (index+1, num_samples, surface, monodromy, times[monodromy]))
print('Average time: %0.3f' % (sum(times.values()) / num_samples))
print('Slowest: %s, Time: %0.3f' % (max(times, key=lambda w: times[w]).replace('.', ''), max(times.values())))
print('Total time: %0.3f' % sum(times.values()))
Pseudo-Anosov distributions¶
Since flipper can determine the Nielsen–Thupyon type of a mapping class we can use it to explore how the percentage of pseudo-Anosovs grows with respect to word length:
import flipper
length = 10
num_samples = 100
S = flipper.load('S_2_1')
for i in range(length):
pA_samples = sum(1 if S.mapping_class(i).is_pseudo_anosov() else 0 for _ in range(num_samples))
print('Length %d: %0.1f%% pA' % (i, float(pA_samples) * 100 / num_samples))
Conjugacy classes¶
Flipper can partition pseudo-Anosov mapping classes into conjugacy classes:
import flipper
S = flipper.load('S_1_1')
length = 6
buckets = [] # All the different conjugacy classes that we have found.
# We could order the buckets by something, say dilatation.
for index, word in enumerate(S.all_words(length)):
h = S.mapping_class(word)
# Currently, we can only determine conjugacy classes for
# pseudo-Anosovs, so we had better filter by them.
if h.is_pseudo_anosov():
# Check if this is conjugate to a mapping class we have seen.
for bucket in buckets:
# Conjugacy is transitive, so we only have to bother checking
# if h is conjugate to the first entry in the bucket.
if bucket[0].is_conjugate_to(h):
bucket.append(h)
break
else: # We have found a new conjugacy class.
buckets.append([h])
print('%d words in %d conjugacy classes.' % (index, len(buckets)))
print(buckets)
Bundles¶
Flipper can interface with SnapPy to build the mapping tori associated to a mapping class. When the mapping class is pseudo-Anosov, flipper builds Agol’s veering triangulation of the fulling punctured mapping torus and installs the correct Dehn fillings:
import snappy
import flipper
# A pseudo-Anosov mapping class.
h = flipper.load('S_1_2').mapping_class('abC')
# Build Agol's veering triangulation of the bundle.
# This will fail with an AssumptionError if h is not pseudo-Anosov.
bundle = h.bundle()
print('It has %d cusp(s) with the following properties:' % bundle.triangulation3.num_cusps)
for index, (real, fibre, degeneracy) in enumerate(zip(bundle.cusp_types(), bundle.fibre_slopes(), bundle.degeneracy_slopes())):
print('\tCusp %s (%s): Fibre slope %s, degeneracy slope %s' % (index, 'Real' if real else 'Fake', fibre, degeneracy))
# Fake cusps filled.
M = snappy.Manifold(bundle)
print(M.identify())
# Can also build a non-veering triangulation of the bundle.
# This works for all mapping classes.
M2 = snappy.Manifold(h.bundle(veering=False))
print(M2.identify())
# If we don't fill the fake cusps we may get a differnt manifold.
N = snappy.Manifold(bundle.snappy_string(filled=False))
print(N.identify())
Twister¶
We can check that the mapping tori built by Twister and flipper agree:
import snappy
import flipper
def match(surface, monodromy):
M = snappy.twister.Surface(surface).bundle(monodromy)
N = snappy.Manifold(flipper.load(surface).mapping_class(monodromy).bundle())
return M.is_isometric_to(N)
assert match('S_1_1', 'aB')
assert match('S_1_2', 'abC')
assert match('S_2_1', 'abbbCddEdaa')
Censuses¶
Flipper includes large censuses of monodromies for fibred knots and manifolds:
from time import time
import snappy
import flipper
for _, row in flipper.census('CHW').iterrows():
start_time = time()
M = snappy.Manifold(row.manifold)
N = snappy.Manifold(flipper.load(row.surface).mapping_class(row.monodromy).bundle())
assert M.is_isometric_to(N) # Never fails for these examples.
print('Matched %s over %s with %s in %0.3fs.' % (row.monodromy, row.surface, row.manifold, time() - start_time))
Knot cusp orders¶
Flipper can find fibred knots where the stable lamination has two (6_2) or even one (8_20) prong coming out of the knot:
import flipper
for _, row in flipper.census('knots').iterrows():
stratum = flipper.load(row.surface).mapping_class(row.monodromy).stratum()
vertex_orders = [stratum[singularity] for singularity in stratum]
real_vertex_orders = [stratum[singularity] for singularity in stratum if not singularity.filled]
print('%s (%s over %s) has singularities %s with %s real singularities.' % (row.manifold, row.monodromy, row.surface, vertex_orders, real_vertex_orders))
Hard invariant laminations¶
There is also a database of mapping classes that flipper has previously had a hard time finding invariant laminations for. These may be useful test cases for other pieces of software or be worth exploring for interesting mathematical properties:
from time import time
import flipper
times = {}
examples = flipper.census('hard')
for index, row in examples.iterrows():
h = flipper.load(row.surface).mapping_class(row.monodromy)
start_time = time()
try:
h.invariant_lamination()
times[row.monodromy] = time() - start_time
print('%3d/%d: %s %s, Time: %0.3f' % (index+1, len(examples), row.surface, row.monodromy, times[row.monodromy]))
except flipper.AssumptionError:
times[row.monodromy] = time() - start_time
print('%3d/%d: %s %s, not pA, Time: %0.3f' % (index+1, len(examples), row.surface, row.monodromy, times[row.monodromy]))
print('Average time: %0.3f' % (sum(times.values()) / len(examples)))
print('Slowest: %s, Time: %0.3f' % (max(times, key=lambda w: times[w]), max(times.values())))
print('Total time: %0.3f' % sum(times.values()))