diff --git a/veerer/automaton.py b/veerer/automaton.py index e1247ff..ba061e3 100644 --- a/veerer/automaton.py +++ b/veerer/automaton.py @@ -450,17 +450,18 @@ 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:: @@ -468,13 +469,15 @@ def from_stratum(self, stratum, reduced=False, **kwds): 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 # @@ -935,8 +938,10 @@ 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): @@ -944,8 +949,85 @@ def _flip_back(self, 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. @@ -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') @@ -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) diff --git a/veerer/linear_family.py b/veerer/linear_family.py index 2ef061f..90c393d 100644 --- a/veerer/linear_family.py +++ b/veerer/linear_family.py @@ -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 @@ -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. @@ -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)] @@ -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: diff --git a/veerer/polyhedron/linear_expression.py b/veerer/polyhedron/linear_expression.py index dda2621..a8bfc5d 100644 --- a/veerer/polyhedron/linear_expression.py +++ b/veerer/polyhedron/linear_expression.py @@ -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 diff --git a/veerer/veering_triangulation.py b/veerer/veering_triangulation.py index bdd588e..694267b 100644 --- a/veerer/veering_triangulation.py +++ b/veerer/veering_triangulation.py @@ -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. @@ -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