Skip to content

Commit

Permalink
Merge pull request #48 from videlec/cylinder-diagrams
Browse files Browse the repository at this point in the history
Cylinder diagrams for linear subvariety
  • Loading branch information
videlec authored Mar 28, 2024
2 parents a26dc6c + 872284f commit f45f00b
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 76 deletions.
130 changes: 93 additions & 37 deletions veerer/automaton.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,31 +450,34 @@ def print_statistics(self, f=None):
f.write("%24s %d\n" % (properties_to_string(k), v))

@classmethod
def from_triangulation(self, T, reduced=False, max_size=None, verbosity=0):
if reduced:
A = ReducedCoreAutomaton(verbosity=verbosity)
else:
A = CoreAutomaton(verbosity=verbosity)
def from_triangulation(cls, T, *args, **kwds):
A = cls()
A.set_seed(T)
A.run(max_size)
A.run(**kwds)
return A

# TODO: it is a bit absurd to have this in the generic class since this does not make
# sense for triangulations
# TODO: this is broken for GeometricAutomaton with QuadraticStratum(8)
# TODO: one should be more careful whether one wants the poles to be half edges or vertices
@classmethod
def from_stratum(self, stratum, reduced=False, **kwds):
def from_stratum(self, stratum, **kwds):
r"""
EXAMPLES::
sage: from veerer import *
sage: from surface_dynamics import * # optional - surface_dynamics
sage: CoreAutomaton.from_stratum(AbelianStratum(2)) # optional - surface_dynamics
Core veering automaton with 86 vertices
sage: GeometricAutomaton.from_stratum(AbelianStratum(2)) # optional - surface_dynamics
Geometric veering automaton with 54 vertices
sage: Q = QuadraticStratum(8) # optional - surface_dynamics
sage: A = CoreAutomaton.from_stratum(Q, max_size=100) # optional - surface_dynamics
sage: A # optional - surface_dynamics
Partial core veering automaton with 101 vertices
"""
return self.from_triangulation(VeeringTriangulation.from_stratum(stratum), reduced=reduced, **kwds)
return self.from_triangulation(VeeringTriangulation.from_stratum(stratum), **kwds)

######################
# DFS implementation #
Expand Down Expand Up @@ -935,17 +938,96 @@ def _flip(self, flip_data):
flip_back_data = (edges, self._state.colour(edges[0]))
for e in edges:
self._state.flip(e, col, check=CHECK)
if CHECK and not self._state.is_geometric(backend=self._backend):
raise RuntimeError('that was indeed possible!')
if CHECK:
self._state._check(RuntimeError)
if not self._state.is_geometric(backend=self._backend):
raise RuntimeError
return True, flip_back_data

def _flip_back(self, flip_back_data):
edges, old_col = flip_back_data
for e in edges:
self._state.flip_back(e, old_col, check=CHECK)

def cylinder_diagrams(self, col=RED):
r"""
A cylinder diagram is a cylindrical veering triangulation up to twist action.
EXAMPLES::
class GeometricAutomatonSubspace(Automaton):
sage: from veerer import RED, BLUE, GeometricAutomaton
sage: from surface_dynamics import AbelianStratum # optional - surface_dynamics
sage: A = GeometricAutomaton.from_stratum(AbelianStratum(2)) # optional - surface_dynamics
sage: len(A.cylinder_diagrams(RED)) # optional - surface_dynamics
2
sage: A = GeometricAutomaton.from_stratum(AbelianStratum(1, 1)) # optional - surface_dynamics
sage: len(A.cylinder_diagrams(RED)) # optional - surface_dynamics
4
For Veech surfaces, the cylinder diagrams are in bijection with cusps::
sage: from veerer import VeeringTriangulations, VeeringTriangulationLinearFamily
sage: t, x, y = VeeringTriangulations.L_shaped_surface(1, 1, 1, 1)
sage: f = VeeringTriangulationLinearFamily(t, [x, y])
sage: A = f.geometric_automaton()
sage: len(A.cylinder_diagrams())
2
We check that in H(2) prototypes coincide with cylinder diagrams made of two cylinders::
sage: from veerer.linear_family import VeeringTriangulationLinearFamilies
sage: for D in [5, 8, 9, 12, 13, 16]:
....: if D % 8 == 1:
....: spins = [0, 1]
....: else:
....: spins = [None]
....: for spin in spins:
....: prototypes = list(VeeringTriangulationLinearFamilies.H2_prototype_parameters(D, spin))
....: if not prototypes:
....: continue
....: a, b, c, e = prototypes[0]
....: F = VeeringTriangulationLinearFamilies.prototype_H2(a, b, c, e)
....: A = F.geometric_automaton()
....: cylsRED = A.cylinder_diagrams(RED)
....: n1RED = sum(len(next(iter(vts)).cylinders(RED)) == 1 for vts in cylsRED)
....: n2RED = sum(len(next(iter(vts)).cylinders(RED)) == 2 for vts in cylsRED)
....: cylsBLUE = A.cylinder_diagrams(BLUE)
....: n1BLUE = sum(len(next(iter(vts)).cylinders(BLUE)) == 1 for vts in cylsBLUE)
....: n2BLUE = sum(len(next(iter(vts)).cylinders(BLUE)) == 2 for vts in cylsBLUE)
....: assert n1RED == n1BLUE
....: assert n2RED == n2BLUE
....: assert n2RED == len(prototypes)
....: print(D, spin, n1RED, n2RED)
5 None 0 1
8 None 0 2
9 0 1 1
12 None 0 3
13 None 0 3
16 None 1 2
"""
cylindricals = set()
orbits = []
for x in self._graph:
if x in cylindricals or not x.is_cylindrical(col):
continue
orbit = set()
todo = [x]
while todo:
x = todo.pop()
orbit.add(x)
for y, (edges, new_col) in self._graph[x]:
if new_col == col and y not in orbit:
assert y.is_cylindrical(col)
orbit.add(y)
todo.append(y)
orbits.append(orbit)
if not all(x not in cylindricals for x in orbit):
print("PROBLEM")
cylindricals.update(orbit)
return orbits


class GeometricAutomatonSubspace(GeometricAutomaton):
r"""
Automaton of geometric veering triangulations with a linear subspace constraint.
Expand Down Expand Up @@ -980,9 +1062,6 @@ class GeometricAutomatonSubspace(Automaton):
"""
_name = 'geometric veering linear constraint'

def _setup(self, backend=None):
self._backend = backend

def _seed_setup(self, state):
if not isinstance(state, VeeringTriangulationLinearFamily) or not state.is_geometric(backend=self._backend):
raise ValueError('invalid seed')
Expand All @@ -992,26 +1071,3 @@ def _seed_setup(self, state):
state = state.copy(mutable=True)
state.set_canonical_labels()
return state

def _forward_flips(self, state):
r"""
Return the list of forward flippable edges from ``state``
"""
return state.geometric_flips(backend=self._backend)

def _flip(self, flip_data):
edges, col = flip_data
assert all(self._state.colour(e) == self._state.colour(edges[0]) for e in edges)
flip_back_data = (edges, self._state.colour(edges[0]))
for e in edges:
self._state.flip(e, col, check=CHECK)
if CHECK:
self._state._check(RuntimeError)
if not self._state.is_geometric(backend=self._backend):
raise RuntimeError
return True, flip_back_data

def _flip_back(self, flip_back_data):
edges, old_col = flip_back_data
for e in edges:
self._state.flip_back(e, old_col, check=CHECK)
59 changes: 55 additions & 4 deletions veerer/linear_family.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.matrix.constructor import matrix
from sage.modules.free_module import FreeModule
from sage.geometry.polyhedron.constructor import Polyhedron
from sage.arith.misc import gcd
from sage.categories.number_fields import NumberFields
Expand Down Expand Up @@ -659,6 +660,52 @@ def flip_back(self, e, col, check=True):
VeeringTriangulation.flip_back(self, e, col, Gx=self._subspace, check=check)
self._subspace.echelonize()

def is_cylindrical(self, col=None):
r"""
Return whether this linear family consists of a union of cylinders
whose moduli are allowed to degenerate to infinity.
EXAMPLES::
sage: from veerer import RED, VeeringTriangulation, VeeringTriangulationLinearFamily
sage: faces = "(0,~2,1)(2,6,~3)(3,5,4)"
sage: cols = "RBRRBRB"
sage: K = QuadraticField(5, 'sqrt5')
sage: sqrt5 = K.gen()
sage: x = (1, 0, 1, 1/2*sqrt5 + 1/2, -1, 1/2*sqrt5 - 1/2, -1/2*sqrt5 + 1/2)
sage: y = (0, 1, 1, 0, 1/2*sqrt5 - 1/2, 1/2*sqrt5 - 1/2, 1)
sage: f = VeeringTriangulationLinearFamily(faces, cols, [x, y])
sage: f.is_cylindrical(RED)
False
sage: VeeringTriangulation.is_cylindrical(f, RED)
True
"""
if col is None:
return self.is_cylindrical(BLUE) or self.is_cylindrical(RED)
if col != RED and col != BLUE:
raise ValueError("'col' must be one of RED or BLUE")
if not VeeringTriangulation.is_cylindrical(self, col):
return False
cylinders = list(self.cylinders(col))

C = [] # middle edges
for cyl in cylinders:
c = [0] * self.num_edges() # indicatrix of the middle edges
for e in cyl[0]:
c[self._norm(e)] = 1
C.append(c)

# take intersection of the cylinder twists in the tangent space
F = FreeModule(self.base_ring(), self.num_edges())
H = F.submodule(self._subspace).intersection(F.submodule(C))

mid_edges = set(i for c in C for i, j in enumerate(c) if j)
for b in H.basis_matrix():
for i, j in enumerate(b):
if j:
mid_edges.discard(i)
return not mid_edges

def geometric_polytope(self, x_low_bound=0, y_low_bound=0, hw_bound=0, backend=None):
r"""
Return the geometric polytope.
Expand Down Expand Up @@ -769,15 +816,15 @@ def H2_prototype_parameters(D, spin=None):
sage: list(VeeringTriangulationLinearFamilies.H2_prototype_parameters(5))
[(0, 1, 1, -1)]
sage: list(VeeringTriangulationLinearFamilies.H2_prototype_parameters(8))
[(0, 2, 1, 0), (0, 2, 1, 0), (0, 1, 1, -2)]
[(0, 2, 1, 0), (0, 1, 1, -2)]
sage: list(VeeringTriangulationLinearFamilies.H2_prototype_parameters(9))
[(0, 2, 1, -1)]
sage: list(VeeringTriangulationLinearFamilies.H2_prototype_parameters(12))
[(0, 3, 1, 0), (0, 3, 1, 0), (0, 1, 2, -2), (0, 2, 1, -2)]
[(0, 3, 1, 0), (0, 1, 2, -2), (0, 2, 1, -2)]
sage: list(VeeringTriangulationLinearFamilies.H2_prototype_parameters(13))
[(0, 3, 1, 1), (0, 3, 1, -1), (0, 1, 1, -3)]
sage: list(VeeringTriangulationLinearFamilies.H2_prototype_parameters(16))
[(0, 4, 1, 0), (0, 4, 1, 0), (0, 3, 1, -2)]
[(0, 4, 1, 0), (0, 3, 1, -2)]
sage: list(VeeringTriangulationLinearFamilies.H2_prototype_parameters(17, spin=0))
[(1, 2, 2, -1), (0, 4, 1, 1), (0, 2, 1, -3)]
Expand Down Expand Up @@ -814,7 +861,11 @@ def H2_prototype_parameters(D, spin=None):
for b in bc.divisors():
c = bc // b
# need c + e < b
for s in (1,-1):
if e == 0:
signs = (1,)
else:
signs = (1, -1)
for s in signs:
if c + s*e < b:
for a in range(gcd(b, c)):
if gcd([a, b, c, e]) == 1:
Expand Down
2 changes: 2 additions & 0 deletions veerer/polyhedron/linear_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,8 @@ def cone(self, backend='sage'):
if backend is None:
from .cone import default_backend
backend = default_backend(self.base_ring())
elif not isinstance(backend, str):
raise TypeError("'backend' must be a string")

if backend == 'ppl':
from .cone import Cone_ppl
Expand Down
36 changes: 1 addition & 35 deletions veerer/veering_triangulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,41 +738,6 @@ def _richcmp_(self, other, op):
c = (self._ep > other._ep) - (self._ep < other._ep)
return rich_to_bool(op, c)

def to_core(self, slope=VERTICAL):
r"""
Change the colour of each forward (reps. backward) flippable edge in
PURPLE if ``slope`` is ``VERTICAL`` (resp ``HORIZONTAL``)
EXAMPLES::
sage: from veerer import *
sage: V = VeeringTriangulation("(0,1,2)", 'RRB')
sage: V.to_core()
sage: V
VeeringTriangulation("(0,1,2)", "RPB")
sage: V.forward_flippable_edges()
[1]
"""
if slope == VERTICAL:
COLOR = PURPLE
elif slope == HORIZONTAL:
COLOR = GREEN
else:
raise ValueError("slope should either be HORIZONTAL or VERTICAL")

n = self.num_edges()
ep = self._ep
for e in range(n):
if ep[e] < e:
raise ValueError("veering triangulation not in canonical form")
if slope == VERTICAL:
if self.is_forward_flippable(e, check=False):
self._colouring[e] = self._colouring[ep[e]] = COLOR
else:
if self.is_backward_flippable(e, check=False):
self._colouring[e] = self._colouring[ep[e]] = COLOR

def copy(self, mutable=None):
r"""
Return a copy of this coloured triangulation.
Expand Down Expand Up @@ -2377,6 +2342,7 @@ def is_cylindrical(self, col=None):
continue
b = fp[a]
c = fp[b]
seen[a] = seen[b] = seen[c] = True
if col == PURPLE:
if cols[a] != PURPLE and cols[b] != PURPLE and cols[c] != PURPLE:
return False
Expand Down

0 comments on commit f45f00b

Please sign in to comment.