Filtering Possible \(\mathcal M\)-Parallel Classes

The purpose of this notebook is to use Sage to assist in the classification of rank 2 invariant subvarieties in strata of translation surfaces in genus 3. This classification was already done through other methods by David Aulicino, Duc Man Nguyen, and Alex Wright, but the methods described here, which is used in my forthcoming PhD thesis, can be readily applied to other strata.

Assume \(\mathcal M\) is an invariant subvariety in a stratum \(\mathcal H\). We will start with a horizontally periodic translation surface \(M\in \mathcal M\). A database for the possible cylinder diagrams for \(M\) can be found in surface-dynamics: see here. Here is an example:

from surface_dynamics import CylinderDiagram

CylinderDiagram('(0,2)-(6) (1,4)-(3,5) (3,6)-(2,4) (5)-(0,1)')
(0,2)-(6) (1,4)-(3,5) (3,6)-(2,4) (5)-(0,1)

Drawing of above cylinder diagram

Assume \(M\) has a cylinder diagram with \(n\) cylinders, and label them \(0\) to \(n-1\) from left to right. Assume that these cylinders are partitioned into \(m\) distinct \(\cal M\)-parallel classes, and we want to know all possible ways to do this. The following function uses existing Sage functions to list all possible partitions. The point of this notebook is to filter out some of these candidate partitions by checking certain conditions. Although we won’t use it here, you can set singletons=False if you do not want to allow cylinders in an \(\cal M\)-parallel class by themselves.

from sage.all import Partitions, SetPartitions

def list_partitions(n, m, singletons=True):
    """Return a list of all ways to partition the set [1..n] into m sets.

    If singletons==False, do not allow singleton sets.

    Each element of each partition is a frozen set.
    Each partition is an instance of
    `sage.combinat.set_partition.SetPartitions_setparts_with_category.element_class`."""

    partitions = []

    # Partition the integer n into m nonzero integers
    int_parts = Partitions(n, length=m).list()

    # Checks for singleton sets
    if not singletons:
        int_parts = [l for l in int_parts if not any([i == 1 for i in l])]

    # Converts integer partitions into set partitions
    for each_part in int_parts:
        partitions.extend(SetPartitions(range(n), each_part))
    return partitions

# Tests
assert len(list_partitions(5, 2, singletons=True)) == 15
assert len(list_partitions(6, 2, singletons=False)) == 25
assert len(list_partitions(6, 3, singletons=False)) == 15

list_partitions(4, 2)

# [{{0}, {1, 2, 3}},
#  {{0, 1, 2}, {3}},
#  {{0, 1, 3}, {2}},
#  {{0, 2, 3}, {1}},
#  {{0, 1}, {2, 3}},
#  {{0, 3}, {1, 2}},
#  {{0, 2}, {1, 3}}]
[{{0}, {1, 2, 3}},
 {{0, 1, 2}, {3}},
 {{0, 1, 3}, {2}},
 {{0, 2, 3}, {1}},
 {{0, 1}, {2, 3}},
 {{0, 3}, {1, 2}},
 {{0, 2}, {1, 3}}]

Homologous Condition

The first condition is simply that two homologous cylinders must be in the same \(\cal M\)-parallel class. filter_homologous_condition removes partitions that do not satisfy this condition.

from surface_dynamics.flat_surfaces.twist_space import TwistSpace

def check_homologous_condition(cd, partition):
    """Check that homologous cylinders are in the same M-parallel class
    in `partition`."""
    tw = TwistSpace(cd)
    classes = tw.homologous_cylinders()
    for homology_class in classes:
        homology_class = frozenset(homology_class)
        for f_set in partition:
            intersection = f_set.intersection(homology_class)

            # Every set in the partition must contain the whole homology class
            # or none of it.
            if intersection != frozenset() and intersection != homology_class:
                return False
    return True

def filter_homologous_condition(cd, part_list):
    """rFilter out the partitions in part_list when
    check_homologous_condition=False."""
    return [part for part in part_list if check_homologous_condition(cd, part)]

Example

For the cylinder diagram (0,3)-(0,5) (1,2)-(1,4) (4,6)-(3,7) (5,7)-(2,6), the last two cylinders (numbered 2 and 3) are homologous. Thus, the remaining partitions should be exactly those that put 2 and 3 in the same set.

part_list = list_partitions(4, 2)
cd = CylinderDiagram('(0,3)-(0,5) (1,2)-(1,4) (4,6)-(3,7) (5,7)-(2,6)')
filter_homologous_condition(cd, part_list)
# [{{0}, {1, 2, 3}}, {{0, 2, 3}, {1}}, {{0, 1}, {2, 3}}]
[{{0}, {1, 2, 3}}, {{0, 2, 3}, {1}}, {{0, 1}, {2, 3}}]
# Tests

cd = CylinderDiagram('(0,2,1)-(4,5) (3,5)-(0,2,1) (4)-(3)')
tw = TwistSpace(cd)
assert set(tw.homologous_cylinders()[0]) == {0, 1}
cd = CylinderDiagram('(0,3)-(0,5) (1,2)-(1,4) (4,6)-(3,7) (5,7)-(2,6)')
tw = TwistSpace(cd)
assert set(tw.homologous_cylinders()[0]) == {2, 3}
cd = CylinderDiagram('(0)-(2) (1,2,3)-(4,5) (4)-(3) (5)-(0,1)')
tw = TwistSpace(cd)
assert tw.homologous_cylinders() == []

part_list = list_partitions(3, 2)
cd = CylinderDiagram('(0,2,1)-(4,5) (3,5)-(0,2,1) (4)-(3)')
good_part = filter_homologous_condition(cd, part_list)
assert len(good_part) == 1
part_list = list_partitions(5, 2)
cd = CylinderDiagram('(0)-(2) (1)-(3) (2,4,3)-(5,6) (5)-(4) (6)-(0,1)')
good_part = filter_homologous_condition(cd, part_list)
assert len(good_part) == 15
cd = CylinderDiagram('(0,1)-(0,6) (2)-(5) (3)-(4) (4,5)-(1) (6)-(2,3)')
good_part = filter_homologous_condition(cd, part_list)
assert len(good_part) == 7

Pants Condition

We define a generalized pants to be a pair of pants with potentially more than two pant legs. This gives an equation in homology between the core curves of the cylinders in the pants of the form \(\gamma_1 = \sum_{i=2}^k \gamma_i\). Because of this equation, we have that these cylinders cannot be contained in exactly two distinct \(\cal M\)-parallel classes. To see this, recall that the core curves of \(\cal M\)-parallel cylinders become colinear when projected to the dual of the tangent space of \(M\). filter_pants_condition filters out partitions that do not satisfy this condition.

In general, a homology relation between core curves of cylinders should give a condition of partitions; this case and the previous one are both specific examples of this. It would be nice to have a more general function that finds all homology relations, and determines all such conditions.

def find_cylinder_in_partition(partition, cylinder):
    """Find the M-parallel class in `partition` than contains `cylinder`.

    `partition` is a list of frozen sets.
    `cylinder` is an integer
    Return the index of the element of `partition` that contains `cylinder`."""
    for i, parallel_class in enumerate(partition):
        if cylinder in parallel_class:
            return i

    # If cylinder was not found in partition
    return None

def check_pants_condition(partition, pants_list):
    """Check the partition satisfies any homology conditions coming from
    the pants in pants_list.

    `partition` is a partition of the horizontal cylinders into M-parallel
    classes.

    `pants_list` is a list of pants, where each pants is a frozenset containing
    every cylinder in the pants

    The homology condition is the following:
    the cylinders in the pants cannot be contained in exactly two distinct sets
    in `partition`.
    """

    for pants in pants_list:

        # A list of the cylinder classes that contain a cylinder of pants
        cylinder_classes = map(
            lambda c: find_cylinder_in_partition(partition, c),
            pants
        )
        if len(set(cylinder_classes)) == 2:
            return False
    return True

def find_generalized_pants(digraph):
    """Finds cases when n cylinders are all only attached to the side
    of a single cylinder. This is like a generalized version of a
    topological pair of pants allows n pant legs.

    Input: A cylinder diagram.

    Output: A set of pants. Each pants is a frozenset consisting of the
    cylinders in the pants.

    Note: For Python 3.7 and higher, frozensets should maintain insertion
    order. Thus, the first element of each pants is the waist curve and the
    rest are the pant legs. However, we do not use this in our code."""

    pants_set = set()
    for n in digraph:
        # If every cylinder above `C` is only adjacent to `C` along its
        # bottom, this is a generalized pants.
        neighbors_out = list(digraph.neighbors_out(n))
        if all([list(digraph.neighbors_in(suc)) == [n]
                for suc in neighbors_out]):

            pants = frozenset([n] + neighbors_out)
            pants_set.add(pants)

        # If every cylinder below `C` is only adjacent to `C` along its
        # top, this is a generalized pants.
        neighbors_in = list(digraph.neighbors_in(n))
        if all([list(digraph.neighbors_out(pre)) == [n]
                for pre in neighbors_in]):

            pants = frozenset([n] + neighbors_in)
            pants_set.add(pants)
    return pants_set

def filter_pants_condition(cd, part_list):
    """Filter out the partitions in part_list when check_pants_condition=False.
    """
    cyl_graph = cd.cylinder_graph()
    pants_list = list(find_generalized_pants(cyl_graph))
    return [partition for partition in part_list
                      if check_pants_condition(partition, pants_list)]

Example

For cylinder diagram (0,3)-(5) (1)-(2) (2,5)-(3,4) (4)-(0,1), the first three cylinders {0, 1, 2} form a pair of pants. Thus, they must all be in the same \(\cal M\)-parallel class or all be in different ones. The only partition with two \(\cal M\)-parallel classes is thus {{0, 1, 2}, {3}}.

cd = CylinderDiagram("(0,3)-(5) (1)-(2) (2,5)-(3,4) (4)-(0,1)")
print(f"The pants of this cylinder diagram are: {find_generalized_pants(cd.cylinder_graph())}.")
# {frozenset({0, 1, 2})}

partitions = list_partitions(4, 2)
filter_pants_condition(cd, partitions)
# [{{0, 1, 2}, {3}}]
The pants of this cylinder diagram are: {frozenset({0, 1, 2})}.
[{{0, 1, 2}, {3}}]
# Tests

## check_pants_condition
assert check_pants_condition([{1}, {2}, {3}], [[1, 2, 3]])
assert not check_pants_condition([{0, 1}, {2, 3}], [[1, 2, 3]])
assert check_pants_condition([{0, 1, 2, 3}], [[1, 2, 3]])


# find_generalized_pants
cd = CylinderDiagram("(0)-(2) (1,2,3)-(4,5) (4)-(3) (5)-(0,1)")
cd_g = cd.cylinder_graph()
assert find_generalized_pants(cd_g) == set([frozenset([1, 2, 3])])

cd = CylinderDiagram("(0,3)-(5) (1)-(0) (2,5)-(3,4) (4)-(1,2)")
cd_g = cd.cylinder_graph()
assert find_generalized_pants(cd_g) == set()

cd = CylinderDiagram("(0,2,1)-(3,4,5) (3)-(1) (4)-(2) (5)-(0)")
cd_g = cd.cylinder_graph()
assert find_generalized_pants(cd_g) == set([frozenset([0, 1, 2, 3])])

cd = CylinderDiagram("(0)-(2) (1)-(3) (2,4,3)-(5,6) (5)-(4) (6)-(0,1)")
cd_g = cd.cylinder_graph()
assert find_generalized_pants(cd_g) == set([frozenset({0, 1, 4}), frozenset({2, 3, 4}), frozenset({0, 1, 2, 3})])


# filter_pants_condition
cd = CylinderDiagram("(0,3)-(0,5) (1,2)-(1,4) (4)-(3) (5)-(2)")
partitions = list_partitions(4, 2)
assert len(filter_pants_condition(cd, partitions)) == 7

cd = CylinderDiagram("(0,2,1)-(3,4,5) (3)-(1) (4)-(2) (5)-(0)")
partitions = list_partitions(4, 2)
assert len(filter_pants_condition(cd, partitions)) == 0

Standard Twist Condition

Now assume that \(\cal M\) is rank 2 and \(M\) has at least 3 \(\cal M\)-parallel classes. Let \(\sigma_1,\sigma_2,\sigma_3\) be the standard twists of these classes, and \(p\) be the projection from relative to absolution cohomology. By definition of rank, \(p(\sigma_1), p(\sigma_2), p(\sigma_3)\) are in a 2 dimensional subspace of cohomology, so there must be a linear relation. Up to relabeling, the equation must be of the form \(a_1p(\sigma_1) = a_2p(\sigma_2) + a_3p(\sigma_3)\), where crucially \(a_1,a_2,a_3>0\). Often, this equation has no solutions, which rules out the partition. We can use linear programming to determine exactly when such an equation has a solution. If \(\cal M\) has more than 3 \(\cal M\)-parallel classes, we have one such equation for any triple (although many of these equations will be redundant). filter_standard_twist_condition checks this condition.

from sage.all import block_matrix, matrix, MixedIntegerLinearProgram
from sage.numerical.mip import MIPSolverException

def class_matrix(tw, m_class):
    """Stacks the core curves of the cylinders in an M-parallel class into a
    single matrix."""
    core_curves = list(tw.cylinder_core_curves())
    return (matrix([core_curves[i] for i in m_class]))

def ordered_partition(tw, partition):
    """Checks the standard twist condition on a specific ordered set of the
    equivalence classes."""
    c0 = list(partition[0])
    c1 = list(partition[1])
    c2 = list(partition[2])

    # Convert the equation into the form Ax - b = 0
    A = block_matrix(3, 1,
                        [class_matrix(tw, c0), class_matrix(tw, c1), -class_matrix(tw, c2)],
                        subdivide=False)
    b = -sum(A)
    A = A.T

    # Find any solution to the linear programming problem
    # Ax - b = 0
    # x >= 0
    p = MixedIntegerLinearProgram(maximization=False)
    x = p.new_variable(real=True, nonnegative=True)
    p.add_constraint(A*x == b)
    try:
        p.solve()
        return True
    except MIPSolverException:
        # MIPSolverException means that no solution exists.
        return False

def check_standard_twist_condition(tw, partition):
    """If the partition does not have three equivalence classes, return
    true. Otherwise, check that the equation
    Σa_iα_i = Σb_jβ_j + Σc_kγ_k
    has a solution for a_i,b_j,c_k >= 1."""

    n = len(partition)
    partition = list(partition)
    for i in range(n-2):
        for j in range(i+1, n-1):
            for k in range(j+1, n):
                order1 = [partition[i], partition[j], partition[k]]
                order2 = [partition[j], partition[k], partition[i]]
                order3 = [partition[k], partition[i], partition[j]]
                if not any([ordered_partition(tw, order1),
                            ordered_partition(tw, order2),
                            ordered_partition(tw, order3)]):
                    return False
    return True

def filter_standard_twist_condition(cd, part_list):
    tw = TwistSpace(cd)
    return [part for part in part_list
                 if check_standard_twist_condition(tw, part)]

Examples

  1. Since these exists an invariant subvariety of rank 2 containing a translation surface \(M\) with cylinder diagram (0,3)-(5) (1)-(0) (2,5)-(3,4) (4)-(1,2), whose \(\cal M\)-parallel classes are {{0, 3}, {1}, {2}}, it is a sanity check that this partition has not been filtered out.

part_list = list_partitions(4, 3)
cd = CylinderDiagram("(0,3)-(5) (1)-(0) (2,5)-(3,4) (4)-(1,2)")
filter_standard_twist_condition(cd, part_list)
# [{{0, 3}, {1}, {2}}, {{0}, {1, 2}, {3}}]
[{{0, 3}, {1}, {2}}, {{0}, {1, 2}, {3}}]
  1. The following is a corollary that we can use as a sanity check. If \(M\) contains exactly 3 \(\cal M\)-parallel classes, the dual of the equation \(a_1p(\sigma_1) = a_2p(\sigma_2) + a_3p(\sigma_3)\) gives an equation in homology that contains the core curve of every cylinder of \(M\). For (0)-(2) (1,2,3)-(4,5) (4)-(3) (5)-(0,1), there is only one equation in homology between {1,2,3}, so it cannot satisfy this condition with 3 \(\cal M\)-parallel classes.

part_list = list_partitions(4, 3)
cd = CylinderDiagram('(0)-(2) (1,2,3)-(4,5) (4)-(3) (5)-(0,1)')
filter_standard_twist_condition(cd, part_list)
# []
[]
# Tests

part_list = list_partitions(4, 3)
cd = CylinderDiagram("(0,2,1)-(3,4,5) (3)-(1) (4)-(2) (5)-(0)")
good_part = filter_standard_twist_condition(cd, part_list)
good_part = [set(part) for part in good_part]
possible = {frozenset({0}), frozenset({1, 3}), frozenset({2})}
assert possible in good_part
possible = {frozenset({0}), frozenset({1, 2}), frozenset({3})}
assert possible in good_part
possible = {frozenset({0}), frozenset({1}), frozenset({2, 3})}
assert possible in good_part

part_list = list_partitions(5, 3)
cd = CylinderDiagram("(0,2)-(6) (1)-(3) (3,6)-(4,5) (4)-(0) (5)-(1,2)")
good_part = filter_standard_twist_condition(cd, part_list)
good_part = [set(part) for part in good_part]
possible = {frozenset({0, 4}), frozenset({1, 3}), frozenset({2})}
assert possible in good_part

part_list = list_partitions(5, 3)
cd = CylinderDiagram("(0,6)-(4,5) (1,2)-(3,6) (3)-(2) (4)-(1) (5)-(0)")
good_part = filter_standard_twist_condition(cd, part_list)
good_part = [set(part) for part in good_part]
possible = {frozenset({0, 1}), frozenset({2, 4}), frozenset({3})}
assert possible in good_part

part_list = list_partitions(4, 3)
cd = CylinderDiagram("(0,3)-(5) (1)-(2) (2,5)-(3,4) (4)-(0,1)")
good_part = filter_standard_twist_condition(cd, part_list)
assert good_part == []

part_list = list_partitions(4, 3)
cd = CylinderDiagram("(0,1)-(0,5) (2)-(4) (3,4)-(1) (5)-(2,3)")
good_part = filter_standard_twist_condition(cd, part_list)
assert good_part == []

Leaf Condition

The final condition is quite specific but we include it because it is easy to code up. It is from this paper Lemma 2.11. Let \(\cal M\) be an invariant subvariety with field of definition \(\mathbb Q\) and rank at least 2. Assume \(M\) is horizontally periodic and that the core curves of the horizontal cylinders span a subspace of dimension at least 2 in the dual of the tangent space. Furthermore, assume there is a simple horizontal cylinder \(C\) that is only adjacent to another cylinder \(D\). Then, \(C\) and \(D\) cannot be in the same \(\cal M\)-parallel class. filter_leaf_condition checks this condition.

Define the cylinder graph as the directed graph, where vertices are cylinders and the edges are saddle connections. We call this the leaf condition since \(C\) is a leaf in the cylinder graph i.e. its only neighbor is \(D\).

def is_simple(cd, index):
    """Check if cylinder number `index` is simple."""
    cylinder = cd.cylinders()[index]
    if len(cylinder[0]) == 1 and len(cylinder[1]) == 1:
        return True
    return False

def find_leaves(digraph):
    """Return the tuples (leaf, neighbor) for all leaves of `digraph`.

    A leaf is a vertex such that it only has one neighbor, where we
    count a vertex as a neighbor if there is either an edge coming from it
    or an edge going to it.

    For a given leaf, `neighbor` is it's unique neighbor."""
    leaf_neighbers = []
    for n in digraph:
        neighbors = set(digraph.neighbors_out(n)) | \
                    set(digraph.neighbors_in(n))
        if len(neighbors) == 1:
            leaf_neighbers.append((n, next(iter(neighbors))))
    return leaf_neighbers

def check_leaf_condition(cd, partition):
    """Check for the following condition:
    If a cylinder C is only bordering another cylinder D, then C and D cannot
    be in the same M-parallel class.

    Note that there are some assumptions for this condition.
    Refer to the paper for these assumptions."""
    cyl_graph = cd.cylinder_graph()
    for leaf, neighbor in find_leaves(cyl_graph):
        if is_simple(cd, leaf):
            if find_cylinder_in_partition(partition, leaf) == \
            find_cylinder_in_partition(partition, neighbor):
                return False
    return True

def filter_leaf_condition(cd, part_list):
    """Filter out the partitions in part_list when check_leaf_condition=False.
    """
    return [part for part in part_list if check_leaf_condition(cd, part)]

Example

In the cylinder diagram (0,1)-(0,2) (2)-(3) (3,4)-(1,5) (5)-(4), we see that cylinder 3 is a leaf adjacent only to cylinder 2. Thus, they cannot be in the same set.

part_list = list_partitions(4, 2)
cd = CylinderDiagram("(0,1)-(0,2) (2)-(3) (3,4)-(1,5) (5)-(4)")
filter_leaf_condition(cd, part_list)
# [{{0, 1, 2}, {3}}, {{0, 1, 3}, {2}}, {{0, 3}, {1, 2}}, {{0, 2}, {1, 3}}]
[{{0, 1, 2}, {3}}, {{0, 1, 3}, {2}}, {{0, 3}, {1, 2}}, {{0, 2}, {1, 3}}]
# Tests

cd = CylinderDiagram("(0)-(1) (1,3,4,2)-(5,6) (5)-(0,4) (6)-(2,3)")
good_part = filter_leaf_condition(cd, part_list)
assert len(good_part) == 7
cd = CylinderDiagram("(0,3)-(5) (1)-(2) (2,5)-(3,4) (4)-(0,1)")
good_part = filter_leaf_condition(cd, part_list)
assert len(good_part) == 7

H(3,1)

We go through every cylinder diagram with \(n\) cylinders in a component of a stratum, and we check the partitions with \(m\) \(\cal M\)-parallel classes. \(\cal H(3,1)\) has no rank 2 invariant subvarieties, so we would like to filter out as many partitions as possible.

from surface_dynamics import Stratum
from surface_dynamics.databases.flat_surfaces import CylinderDiagrams

def filter_partitions(cyl_diag_list, num_classes):
    """For each cylinder diagram in cyl_diag_list, list all partitions with
    num_classes number of classes and filter out the invalid ones.

    Stores the output in a dict of
    (cylinder diagram, list of equivalence classes)"""
    output = {}
    for cd in cyl_diag_list:
        part = list_partitions(len(cd.cylinders()), num_classes)
        part = filter_pants_condition(cd, part)
        part = filter_homologous_condition(cd, part)
        part = filter_leaf_condition(cd, part)
        part = filter_standard_twist_condition(cd, part)
        output[cd] = part
    return output

Among the four 4-cylinder diagrams and 2 \(\cal M\)-parallel classes, we rule out two of the four cylinder diagrams. For the remaining ones, we only need to consider one partition each.

H = Stratum([3, 1], k=1).components()[0]
C = CylinderDiagrams()

# Check the partitions with 2 classes
cyl_diag_list = C.get_iterator(H, 4)
valid = filter_partitions(cyl_diag_list, 2)
for i, (k, v) in enumerate(valid.items()):
    print(f"{i+1}. {k}")
    print(v)

# 1. (0)-(2) (1,2,3)-(4,5) (4)-(3) (5)-(0,1)
# []
# 2. (0,3)-(5) (1)-(2) (2,5)-(3,4) (4)-(0,1)
# [{{0, 1, 2}, {3}}]
# 3. (0,1)-(0,2) (2)-(3) (3,4)-(1,5) (5)-(4)
# []
# 4. (0,1)-(0,4,5) (2,3)-(1) (4)-(2) (5)-(3)
# [{{0}, {1, 2, 3}}]
1. (0)-(2) (1,2,3)-(4,5) (4)-(3) (5)-(0,1)
[]
2. (0,3)-(5) (1)-(2) (2,5)-(3,4) (4)-(0,1)
[{{0, 1, 2}, {3}}]
3. (0,1)-(0,2) (2)-(3) (3,4)-(1,5) (5)-(4)
[]
4. (0,1)-(0,4,5) (2,3)-(1) (4)-(2) (5)-(3)
[{{0}, {1, 2, 3}}]

For 3 \(\cal M\)-parallel classes, we rule out all possibilities.

# Check the partitions with 3 classes
cyl_diag_list = C.get_iterator(H, 4)
valid = filter_partitions(cyl_diag_list, 3)
for i, (k, v) in enumerate(valid.items()):
    print(f"{i+1}. {k}")
    print(v)

# 1. (0)-(2) (1,2,3)-(4,5) (4)-(3) (5)-(0,1)
# []
# 2. (0,3)-(5) (1)-(2) (2,5)-(3,4) (4)-(0,1)
# []
# 3. (0,1)-(0,2) (2)-(3) (3,4)-(1,5) (5)-(4)
# []
# 4. (0,1)-(0,4,5) (2,3)-(1) (4)-(2) (5)-(3)
# []
1. (0)-(2) (1,2,3)-(4,5) (4)-(3) (5)-(0,1)
[]
2. (0,3)-(5) (1)-(2) (2,5)-(3,4) (4)-(0,1)
[]
3. (0,1)-(0,2) (2)-(3) (3,4)-(1,5) (5)-(4)
[]
4. (0,1)-(0,4,5) (2,3)-(1) (4)-(2) (5)-(3)
[]

Printing Output

The following script goes through every component of strata in strata, every cylinder diagram with at least 4 cylinders, and checks all partitions for at least 2 \(\cal M\)-parallel classes. The output is saves to a folder called output. This output is used in my thesis to classify rank 2 invariant subvarieties in some genus 3 strata.

def list_cylinder_classes(H, num_cylinders, num_classes):
    """Runs filter_partitions on every cylinder diagram with num_cylinders
    number of cylinders in a stratum H."""
    C = CylinderDiagrams()
    try:
        cyl_diag_list = C.get_iterator(H, num_cylinders)
        valid = filter_partitions(cyl_diag_list, num_classes)
        print(f"{num_classes} classes")
        for i, (k, v) in enumerate(valid.items()):
            print(f"{i+1}. {k}")
            print(v)
        print("")
    except ValueError:
        return
# H = Stratum([3, 1], k=1).components()[0]
# H = Stratum([2, 2], k=1).components()[1]         ## This is H^odd(2,2)
H = Stratum([2, 1, 1], k=1).components()[0]
# H = Stratum([1, 1, 1, 1], k=1).components()[0]

# Cylinder diagrams with 4 or more cylinders.
for num_cylinders in range(4, 7):
    print(f"{num_cylinders} CYLINDERS\n")
    # Partitions with at least 2 classes.
    for i in range(2, num_cylinders):
        list_cylinder_classes(H, num_cylinders, i)
    print("")
4 CYLINDERS

2 classes
1. (0)-(1) (1,3,4,2)-(5,6) (5)-(0,4) (6)-(2,3)
[{{0}, {1, 2, 3}}]
2. (0)-(1,2) (1,4,2,3)-(5,6) (5)-(4) (6)-(0,3)
[]
3. (0)-(3) (1,3,2,4)-(5,6) (5)-(4) (6)-(0,2,1)
[]
4. (0)-(3) (1,4,2,3)-(5,6) (5)-(4) (6)-(0,2,1)
[]
5. (0,1)-(0,2,5) (2)-(3) (3,6)-(1,4) (4,5)-(6)
[{{0}, {1, 2, 3}}]
6. (0,2)-(5) (1,3)-(6) (4)-(0,1) (5,6)-(2,4,3)
[{{0, 1, 3}, {2}}]
7. (0,1)-(0,3) (2,5)-(1,6) (3,6)-(4,5) (4)-(2)
[{{0}, {1, 2, 3}}, {{0, 1, 2}, {3}}, {{0, 3}, {1, 2}}]
8. (0,2)-(0,5) (1,3)-(1,6) (4,5)-(3) (6)-(2,4)
[{{0}, {1, 2, 3}}, {{0, 2, 3}, {1}}, {{0, 1}, {2, 3}}]
9. (0,2)-(6) (1,4)-(3,5) (3,6)-(2,4) (5)-(0,1)
[{{0}, {1, 2, 3}}, {{0, 1, 2}, {3}}, {{0, 1, 3}, {2}}, {{0, 2, 3}, {1}}, {{0, 1}, {2, 3}}, {{0, 3}, {1, 2}}, {{0, 2}, {1, 3}}]
10. (0,1)-(0,2) (2,4)-(6) (3,6)-(1,5,4) (5)-(3)
[]
11. (0,1)-(0,3,5,6) (2,4)-(1) (3,6)-(4) (5)-(2)
[{{0}, {1, 2, 3}}]
12. (0,1,2)-(0,1,6) (3)-(5) (4,5)-(2) (6)-(3,4)
[{{0}, {1, 2, 3}}, {{0, 2, 3}, {1}}, {{0, 1}, {2, 3}}]
13. (0,4,1)-(6) (2)-(3) (3,6)-(4,5) (5)-(0,2,1)
[{{0, 1, 2}, {3}}]
14. (0,3,1)-(6) (2)-(4) (4,6)-(0,5,1) (5)-(2,3)
[{{0, 1, 2}, {3}}]
15. (0,3,1)-(6) (2)-(3) (4,6)-(0,5,1) (5)-(2,4)
[{{0}, {1, 2, 3}}, {{0, 1, 2}, {3}}, {{0, 1, 3}, {2}}, {{0, 2, 3}, {1}}, {{0, 1}, {2, 3}}, {{0, 3}, {1, 2}}, {{0, 2}, {1, 3}}]
16. (0,1,2)-(3,6,4,5) (3,4)-(2) (5)-(0) (6)-(1)
[]
17. (0,1,2)-(0,1,6) (3,6)-(4,5) (4)-(3) (5)-(2)
[]
18. (0,2,1)-(5,6) (3,4)-(0,2,1) (5)-(4) (6)-(3)
[]
19. (0,2,1)-(5,6) (3,6)-(0,4,1) (4)-(2) (5)-(3)
[{{0, 1, 2}, {3}}, {{0, 1, 3}, {2}}, {{0, 1}, {2, 3}}]
20. (0,2,1)-(5,6) (3,6)-(0,4,1) (4)-(3) (5)-(2)
[{{0, 1}, {2, 3}}, {{0, 2}, {1, 3}}]
21. (0,4,2)-(5,6) (1,3)-(0,1,2) (5)-(4) (6)-(3)
[]
22. (0,2,1)-(6) (3,6)-(4,5) (4)-(3) (5)-(0,2,1)
[]
23. (0,1,4)-(0,1,6) (2,3)-(2,5) (5)-(4) (6)-(3)
[{{0}, {1, 2, 3}}, {{0, 2, 3}, {1}}, {{0, 1}, {2, 3}}]
24. (0,5,2)-(3) (1,3)-(1,6) (4)-(5) (6)-(0,4,2)
[{{0, 1, 3}, {2}}, {{0, 2, 3}, {1}}, {{0, 3}, {1, 2}}]
25. (0,1,2)-(0,1,5,6) (3,4)-(2) (5)-(4) (6)-(3)
[{{0}, {1, 2, 3}}]
26. (0,1,2)-(0,5,1,6) (3,4)-(2) (5)-(3) (6)-(4)
[{{0}, {1, 2, 3}}]
27. (0,2,1)-(6) (3,6)-(0,5,4,1) (4)-(3) (5)-(2)
[]
3 classes
1. (0)-(1) (1,3,4,2)-(5,6) (5)-(0,4) (6)-(2,3)
[]
2. (0)-(1,2) (1,4,2,3)-(5,6) (5)-(4) (6)-(0,3)
[]
3. (0)-(3) (1,3,2,4)-(5,6) (5)-(4) (6)-(0,2,1)
[]
4. (0)-(3) (1,4,2,3)-(5,6) (5)-(4) (6)-(0,2,1)
[]
5. (0,1)-(0,2,5) (2)-(3) (3,6)-(1,4) (4,5)-(6)
[]
6. (0,2)-(5) (1,3)-(6) (4)-(0,1) (5,6)-(2,4,3)
[]
7. (0,1)-(0,3) (2,5)-(1,6) (3,6)-(4,5) (4)-(2)
[]
8. (0,2)-(0,5) (1,3)-(1,6) (4,5)-(3) (6)-(2,4)
[]
9. (0,2)-(6) (1,4)-(3,5) (3,6)-(2,4) (5)-(0,1)
[{{0}, {1}, {2, 3}}, {{0, 1}, {2}, {3}}]
10. (0,1)-(0,2) (2,4)-(6) (3,6)-(1,5,4) (5)-(3)
[]
11. (0,1)-(0,3,5,6) (2,4)-(1) (3,6)-(4) (5)-(2)
[]
12. (0,1,2)-(0,1,6) (3)-(5) (4,5)-(2) (6)-(3,4)
[]
13. (0,4,1)-(6) (2)-(3) (3,6)-(4,5) (5)-(0,2,1)
[]
14. (0,3,1)-(6) (2)-(4) (4,6)-(0,5,1) (5)-(2,3)
[]
15. (0,3,1)-(6) (2)-(3) (4,6)-(0,5,1) (5)-(2,4)
[{{0, 3}, {1}, {2}}, {{0}, {1, 2}, {3}}]
16. (0,1,2)-(3,6,4,5) (3,4)-(2) (5)-(0) (6)-(1)
[{{0}, {1, 3}, {2}}, {{0}, {1}, {2, 3}}, {{0}, {1, 2}, {3}}]
17. (0,1,2)-(0,1,6) (3,6)-(4,5) (4)-(3) (5)-(2)
[]
18. (0,2,1)-(5,6) (3,4)-(0,2,1) (5)-(4) (6)-(3)
[{{0, 1}, {2}, {3}}]
19. (0,2,1)-(5,6) (3,6)-(0,4,1) (4)-(2) (5)-(3)
[]
20. (0,2,1)-(5,6) (3,6)-(0,4,1) (4)-(3) (5)-(2)
[{{0}, {1, 3}, {2}}, {{0, 2}, {1}, {3}}]
21. (0,4,2)-(5,6) (1,3)-(0,1,2) (5)-(4) (6)-(3)
[]
22. (0,2,1)-(6) (3,6)-(4,5) (4)-(3) (5)-(0,2,1)
[{{0, 3}, {1}, {2}}]
23. (0,1,4)-(0,1,6) (2,3)-(2,5) (5)-(4) (6)-(3)
[]
24. (0,5,2)-(3) (1,3)-(1,6) (4)-(5) (6)-(0,4,2)
[]
25. (0,1,2)-(0,1,5,6) (3,4)-(2) (5)-(4) (6)-(3)
[]
26. (0,1,2)-(0,5,1,6) (3,4)-(2) (5)-(3) (6)-(4)
[]
27. (0,2,1)-(6) (3,6)-(0,5,4,1) (4)-(3) (5)-(2)
[]


5 CYLINDERS

2 classes
1. (0)-(2) (1)-(3) (2,4,3)-(5,6) (5)-(4) (6)-(0,1)
[]
2. (0,4)-(6) (1)-(0) (2)-(3) (3,6)-(4,5) (5)-(1,2)
[]
3. (0,1)-(0,6) (2)-(5) (3)-(4) (4,5)-(1) (6)-(2,3)
[{{0}, {1, 2, 3, 4}}]
4. (0,2)-(6) (1)-(0) (3,6)-(4,5) (4)-(3) (5)-(1,2)
[]
5. (0,2)-(6) (1)-(3) (3,6)-(4,5) (4)-(0) (5)-(1,2)
[]
6. (0,1)-(0,2) (2)-(3) (3,4)-(5,6) (5)-(4) (6)-(1)
[]
7. (0,6)-(4,5) (1,2)-(3,6) (3)-(2) (4)-(1) (5)-(0)
[]
8. (0,6)-(4,5) (1,2)-(3,6) (3)-(0) (4)-(2) (5)-(1)
[{{0, 1, 3, 4}, {2}}]

3 classes
1. (0)-(2) (1)-(3) (2,4,3)-(5,6) (5)-(4) (6)-(0,1)
[{{0, 1, 4}, {2}, {3}}]
2. (0,4)-(6) (1)-(0) (2)-(3) (3,6)-(4,5) (5)-(1,2)
[{{0, 1}, {2}, {3, 4}}, {{0, 4}, {1, 3}, {2}}]
3. (0,1)-(0,6) (2)-(5) (3)-(4) (4,5)-(1) (6)-(2,3)
[]
4. (0,2)-(6) (1)-(0) (3,6)-(4,5) (4)-(3) (5)-(1,2)
[]
5. (0,2)-(6) (1)-(3) (3,6)-(4,5) (4)-(0) (5)-(1,2)
[{{0}, {1}, {2, 3, 4}}, {{0, 1, 2}, {3}, {4}}, {{0, 3}, {1, 4}, {2}}, {{0, 4}, {1, 3}, {2}}]
6. (0,1)-(0,2) (2)-(3) (3,4)-(5,6) (5)-(4) (6)-(1)
[]
7. (0,6)-(4,5) (1,2)-(3,6) (3)-(2) (4)-(1) (5)-(0)
[{{0, 1}, {2, 4}, {3}}, {{0, 2}, {1, 4}, {3}}]
8. (0,6)-(4,5) (1,2)-(3,6) (3)-(0) (4)-(2) (5)-(1)
[]
4 classes
1. (0)-(2) (1)-(3) (2,4,3)-(5,6) (5)-(4) (6)-(0,1)
[]
2. (0,4)-(6) (1)-(0) (2)-(3) (3,6)-(4,5) (5)-(1,2)
[]
3. (0,1)-(0,6) (2)-(5) (3)-(4) (4,5)-(1) (6)-(2,3)
[]
4. (0,2)-(6) (1)-(0) (3,6)-(4,5) (4)-(3) (5)-(1,2)
[]
5. (0,2)-(6) (1)-(3) (3,6)-(4,5) (4)-(0) (5)-(1,2)
[]
6. (0,1)-(0,2) (2)-(3) (3,4)-(5,6) (5)-(4) (6)-(1)
[]
7. (0,6)-(4,5) (1,2)-(3,6) (3)-(2) (4)-(1) (5)-(0)
[]
8. (0,6)-(4,5) (1,2)-(3,6) (3)-(0) (4)-(2) (5)-(1)
[]


6 CYLINDERS