Siegel-Veech Constants

We count the number of cylinders of circumference at most \(L\) in a surface as a step in a potential computation of Siegel-Veech constants that David Aulicino was interested in.

Note that parts of this code use the C++ library libflatsurf. Please consult our installation instructions if this library is not available on your system yet.

We start by creating a surface with sage-flatsurf.

from flatsurf import translation_surfaces

S = translation_surfaces.mcmullen_L(1, 1, 1, 1)
S.plot()
../_images/1914be6c9155bc0e5c02e665e65a0b566558be7f4081728cb93fd236061b3842.png

Decomposition of a surface into cylinders is implemented in pyflatsurf. We triangulate our surface and make sure that its vertices are singularities.

S = S.pyflatsurf().codomain().flat_triangulation()
S = S.eliminateMarkedPoints().surface()

We will iterate over all directions coming from saddle connections of length at most L (ignoring connections that have the same slope).

L = int(16)

directions = S.connections().bound(L).slopes()

For each direction we want to compute a decomposition into cylinders and minimal components. Note that sometimes our algorithm cannot decide whether a component is minimal. However, this is not an issue here: we can stop the decomposition process when a component has become so stretched out that it has no hope of producing a cylinder of circumference \(≤L\) anymore.

Here we define the target of the decomposition, i.e., a predicate that determines when a decomposition of a component can be stopped:

def target(component):
    if component.cylinder():
        # This component is a cylinder. No further decomposition needed.
        return True
    if component.withoutPeriodicTrajectory():
        # This component is minimal. Further decomposition will not produce any cylinders.
        return True

    height = component.height()

    # This height bounds the size of any cylinder. However, it is stretched by the length of the vector
    # defining the vertical direction. (That vector is not normalized because that is hard to do in
    # general rings…)
    from pyflatsurf import flatsurf

    denom = flatsurf.Bound.upper(component.vertical().vertical()).squared()
    return (height * height) / denom > L

Now we perform the actual decomposition and collect the cylinders of circumference \(≤L\):

circumferences = []

for direction in directions:
    from pyflatsurf import flatsurf

    decomposition = flatsurf.makeFlowDecomposition(S, direction.vector())
    decomposition.decompose(target)
    for component in decomposition.components():
        if component.cylinder():
            circumference = component.circumferenceHolonomy()
            if circumference > L:
                continue
            circumferences.append(circumference)

We plot a histogram of all the cylinders we found, ordered by length. It would be easy to plot this differently, weighted by the area, …

lengths = [sqrt(float(v.x()) ** 2 + float(v.y()) ** 2) for v in circumferences]

import matplotlib.pyplot as plot

_ = plot.hist(lengths)
_ = plot.xlim(0, L)
_ = plot.title(f"{len(circumferences)} cylinders with length at most {L}")
_ = plot.xlabel("Length")
_ = plot.ylabel("Count")
../_images/5a7cf61355ae7c75b944b03254c14a96e7992eb1fdc4dd11dc23112900ca5691.png