Source code for surface_dynamics.flat_surfaces.quadratic_strata

r"""
Strata of quadratic differentials on Riemann surfaces

More precisely, we are interested in meromorphic quadratic differentials
with at most simple poles on closed compact connected Riemann surfaces,
which are not globally the square of an abelian differential.

The moduli space of such quadratic differentials on Riemann surfaces
of a given genus is a complex orbifold, stratified by the degrees of
zeros (zeros of degree -1, or simple poles, being allowed). The strata
themselves are complex orbifolds. Most strata are connected but some
(infinitely many) are not.

A stratum corresponds to the Sage object
:class:`~QuadraticStratum`.

The classification of connected components of strata of quadratic
differentials was established by Erwan Lanneau in [Lan08],
after a similar classification was established by Kontsevich
and Zorich in [KonZor03]_ in the Abelian case.

Each stratum has one or two connected components and each
component is associated to an extended Rauzy class. The
:meth:`~surface_dynamics.flat_surfaces.strata.Stratum.components`
method gives the decomposition of a stratum into its connected components.

A representative for each connected component of stratum is given by Zorich in [Zor08]_.

This is implemented here following [Zor08]_:

- genus zero stratum :meth:`~GenusZeroQuadraticStratumComponent.permutation_representative`

- genus one stratum :meth:`~GenusOneQuadraticStratumComponent.permutation_representative`

- genus two hyperellitic component :meth:`~GenusTwoHyperellipticQuadraticStratumComponent.permutation_representative`

- genus two non-hyperellitic component :meth:`~ConnectedQuadraticStratumComponent.permutation_representative`

- connected component
  :meth:`~ConnectedQuadraticStratumComponent.permutation_representative`

- hyperelliptic component :meth:`~HyperellipticQuadraticStratumComponent.permutation_representative`

- non-hyperelliptic component is similar to connected components

- regular component of exceptional stratum :meth:`~RegularExceptionalQuadraticStratumComponent.permutation_representative`

- irregular component of exceptional stratum :meth:`~IrregularExceptionalQuadraticStratumComponent.permutation_representative`

The inverse operation, i.e., starting from a permutation, determine
the connected component it lives in, is partially written in [KonZor03]_.
See:
:meth:`~surface_dynamics.interval_exchanges.template.PermutationLI.stratum_component`.

The code here implements the descriptions in [Zor08]_. Zorich already
implemented all this for Mathematica in [ZS]_.

See also :mod:`~surface_dynamics.flat_surfaces.abelian_strata` for Abelian strata.

AUTHORS:

- Vincent Delecroix (2009-09-29): initial version

- Samuel Lelievre (2010-10-08): quadratic strata

EXAMPLES::

    sage: from surface_dynamics import *

Construction of a stratum from a list of singularity degrees::

    sage: a = QuadraticStratum(2,2)
    sage: a
    Q_2(2^2)
    sage: a.genus()
    2

::

    sage: a = QuadraticStratum(4,3,2,2,1)
    sage: a
    Q_4(4, 3, 2^2, 1)
    sage: a.genus()
    4

By convention, the degrees are always written in decreasing order::

    sage: a1 = QuadraticStratum(7,5,3,1)
    sage: a1
    Q_5(7, 5, 3, 1)
    sage: a2 = QuadraticStratum(3,1,7,5)
    sage: a2
    Q_5(7, 5, 3, 1)
    sage: a1 == a2
    True

List the connected components of a stratum::

    sage: a = QuadraticStratum(6,2)
    sage: a.components()
    [Q_3(6, 2)^hyp, Q_3(6, 2)^nonhyp]

::

    sage: a = QuadraticStratum(12)
    sage: cc = a.components()
    sage: cc
    [Q_4(12)^reg, Q_4(12)^irr]
    sage: for c in cc:
    ....:     print(c)
    ....:     print(c.permutation_representative())
    Q_4(12)^reg
    0 1 2 1 2 3 4 3 4 5
    5 6 7 6 7 0
    Q_4(12)^irr
    0 1 2 3 4 5 6 5
    7 6 4 7 3 2 1 0

::

    sage: a = QuadraticStratum(1, 1, 1, 1)
    sage: a.components()
    [Q_2(1^4)^hyp]
    sage: c = a.components()[0]
    sage: p = c.permutation_representative(); p
    0 1 2 3 1 4 5
    2 6 5 4 6 3 0
"""
#*****************************************************************************
#       Copyright (C) 2010 Vincent Delecroix <20100.delecroix@gmail.com>
#       Copyright (C) 2010 Samuel Lelievre <samuel.lelievre@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#  as published by the Free Software Foundation; either version 2 of
#  the License, or (at your option) any later version.
#                  https://www.gnu.org/licenses/
#*****************************************************************************

from __future__ import print_function, absolute_import
from six.moves import range, map, filter, zip
from six import iteritems

import numbers

from sage.structure.unique_representation import UniqueRepresentation
from sage.rings.infinity import Infinity
from sage.structure.parent import Parent

from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets
from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets

from sage.structure.sage_object import SageObject
from sage.rings.integer import Integer
from sage.rings.rational import Rational


from surface_dynamics.flat_surfaces.strata import Stratum, StratumComponent,Strata

[docs] class QuadraticStratum(Stratum): r""" Stratum of quadratic differentials. EXAMPLES:: sage: from surface_dynamics import * sage: Q = QuadraticStratum(15,-1,-1,-1); Q Q_4(15, -1^3) sage: Q.components() [Q_4(15, -1^3)^c] sage: Q = QuadraticStratum(6,6); Q Q_4(6^2) sage: Q.components() [Q_4(6^2)^hyp, Q_4(6^2)^reg, Q_4(6^2)^irr] """ _name = "Q" _latex_name = "\\mathcal{Q}" @staticmethod def __classcall_private__(self, *l, **kwds): r""" TESTS:: sage: from surface_dynamics import * sage: QuadraticStratum(-1,-1,-1,-1) is QuadraticStratum({-1:4}) True """ genus = kwds.pop('genus', None) if kwds: raise ValueError('unsupported keyword arguments') if len(l) == 1 and not isinstance(l[0], numbers.Integral): l = l[0] elif (len(l) == 3 and isinstance(l[0], tuple) and isinstance(l[1], numbers.Integral) and isinstance(l[2], numbers.Integral)): l = l[0] + (-1,) * l[1] + (0,) * l[2] if not l: raise ValueError("the list must be nonempty") if isinstance(l, dict): l = sum(([v]*e for v,e in iteritems(l)),[]) allzeros = sorted(map(Integer, l), reverse=True) nb_poles = allzeros.count(-1) nb_fake_zeros = allzeros.count(0) zeros = tuple(allzeros[:len(allzeros)-nb_fake_zeros-nb_poles]) if genus is not None: g = sum(zeros) + 4 - nb_poles if 4 * genus > g: raise ValueError elif 4 * genus < g: nb_poles += g - 4*genus return UniqueRepresentation.__classcall__(QuadraticStratum, zeros, nb_poles, nb_fake_zeros) def __init__(self, zeros, nb_poles, nb_fake_zeros): """ TESTS:: sage: from surface_dynamics import * sage: a = QuadraticStratum(4,-1,-1,-1,-1,0) sage: loads(dumps(a)) == a True sage: QuadraticStratum([]) Traceback (most recent call last): ... ValueError: the list must be nonempty """ self._zeros = zeros self._nb_poles = nb_poles self._nb_fake_zeros = nb_fake_zeros # nfz (for "non fake zeros") nfz = self._zeros + (-1,) * self._nb_poles genus = sum(nfz) + 4 if genus%4: raise ValueError("the sum of the zeros should sum up to an integer congruent to 0 mod 4") genus //= 4 # Lanneau classification of connected components #TODO: check genus 2 components #TODO: in genus 2, decide between GTHQSC/GTNQSC and HQSC/NQSC if genus == 0: self._cc = (GZQSC,) #TODO: all genus 1 strata are connected, but two are hyperelliptic; give the component a different name then? elif genus == 1: if self._zeros == (): # print "The stratum Q(0) is empty!" self._cc = () elif self._zeros == (1,): # print "The stratum Q(1,-1) is empty!" self._cc = () else: self._cc = (GOQSC,) elif genus == 2: if self._zeros == (4,): # print "The stratum Q(4) is empty!" self._cc = () elif self._zeros == (3,1): # print "The stratum Q(3,1) is empty!" self._cc = () elif self._zeros == (6,): self._cc = (HQSC,GTNQSC) elif self._zeros == (3,3): self._cc = (HQSC,GTNQSC) elif self._zeros == (2,2): self._cc = (GTHQSC,) elif self._zeros == (2,1,1): self._cc = (GTHQSC,) elif self._zeros == (1,1,1,1): self._cc = (GTHQSC,) else: self._cc = (GTNQSC,) elif genus == 3 and self._nb_poles == 1 and all((z == -1 or z%3 == 0) for z in self._zeros): self._cc = (REQSC, IEQSC) elif genus == 4 and self._nb_poles == 0 and all(z%3 == 0 for z in self._zeros): if self._zeros in [(12,),(9,3)]: self._cc = (REQSC, IEQSC) elif self._zeros in [(6,6),(6,3,3),(3,3,3,3)]: self._cc = (HQSC, REQSC, IEQSC) else: if len(nfz) == 2 and nfz[0]%4 == 2 and nfz[1]%4 == 2: self._cc = (HQSC,NQSC) elif len(nfz) == 4 and nfz[0] == nfz[1] and nfz[2] == nfz[3] and nfz[0]%2 and nfz[2]%2: self._cc = (HQSC,NQSC) elif len(nfz) == 3 and nfz[0] == nfz[1] and nfz[0]%2 and nfz[2]%4 == 2: self._cc = (HQSC,NQSC) elif len(nfz) == 3 and nfz[1] == nfz[2] and nfz[1]%2 and nfz[0]%4 == 2: self._cc = (HQSC,NQSC) else: self._cc = (CQSC,)
[docs] def zeros(self, poles=True, fake_zeros=True): r""" Returns the list of zeros of self. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum({-1:4}).zeros() (-1, -1, -1, -1) sage: QuadraticStratum({1:8}).zeros() (1, 1, 1, 1, 1, 1, 1, 1) sage: QuadraticStratum({-1:4}).zeros(poles=False) () """ z = self._zeros if fake_zeros: z += (0,) * self._nb_fake_zeros if poles: z += (-1,) * self._nb_poles return z
[docs] def nb_zeros(self, poles=True, fake_zeros=True): r""" EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum({-1:4}).nb_zeros() 4 sage: QuadraticStratum({-1:4,1:4}).nb_zeros() 8 """ n = Integer(len(self._zeros)) if poles: n += self._nb_poles if fake_zeros: n += self._nb_fake_zeros return n
[docs] def nb_poles(self): r""" Return the number of poles of this quadratic stratum. """ return self._nb_poles
[docs] def nb_fake_zeros(self): r""" Return the number of fake zeros of this quadratic stratum. """ return self._nb_fake_zeros
[docs] def genus(self): r""" Returns the genus. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(-1,-1,-1,-1).genus() 0 """ return Integer(sum(self.zeros(poles=True,fake_zeros=False))//4 + 1)
[docs] def dimension(self): r""" Returns the complex dimension of this stratum. The complex dimension is the number of intervals minus one of any linear involution associated to this stratum. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum({-1:4}).dimension() 2 """ return 2 * self.genus() + self.nb_zeros() - 2
[docs] def rank(self): r""" Return the rank of this GL(2,R)-invariant manifold (half dimension of the absolute part of the tangent space). EXAMPLES:: sage: from surface_dynamics import QuadraticStratum, QuadraticStrata sage: QuadraticStratum({-1: 4}).rank() 1 sage: QuadraticStratum({-1:4, 0:5}).rank() 1 Complete list of rank 2 quadratic strata listed by dimension:: sage: for dim in range(4, 9): ....: quad = [Q for Q in QuadraticStrata(dimension=dim) if Q.rank() == 2] ....: print("%d: %s" % (dim, ", ".join(map(str, quad)))) 4: Q_2(5, -1), Q_1(1^2, -1^2), Q_1(3, -1^3), Q_0(1, -1^5) 5: Q_3(8), Q_2(2, 1^2), Q_2(4, 1, -1), Q_2(3, 2, -1), Q_2(6, -1^2), Q_1(2, 1, -1^3), Q_1(4, -1^4), Q_0(2, -1^6) 6: Q_3(6, 2), Q_3(4^2), Q_2(2^2, 1, -1), Q_2(4, 2, -1^2), Q_1(2^2, -1^4) 7: Q_3(4, 2^2), Q_2(2^3, -1^2) 8: Q_3(2^4) """ return self.genus() + sum(z % 2 for z in self.zeros(poles=True))//2 - 1
[docs] def orientation_cover(self, fake_zeros=False): r""" Return the stratum of Abelian differentials which contains the set of orientation cover of quadratic differentials in this stratum. OPTIONS: - ``fake_zeros`` - boolean - if True, add fake zeros which corresponds to the double cover of poles. EXAMPLES:: sage: from surface_dynamics import * sage: q = QuadraticStratum({4:1,-1:4}); q Q_1(4, -1^4) sage: a1 = q.orientation_cover(); a1 H_3(2^2) sage: q.orientation_cover(fake_zeros=True) H_3(2^2, 0^4) For hyperelliptic strata (orientation cover of quadratic strata of the form `Q(n,-1^{n+4})`) the dimension coincide. From [Lan08] we know that it only happens for those ones:: sage: q = QuadraticStratum(4,genus=0); q Q_0(4, -1^8) sage: q.dimension() 7 sage: q.orientation_cover().dimension() 7 sage: q = QuadraticStratum(3,1,genus=0); q Q_0(3, 1, -1^8) sage: q.dimension() 8 sage: a = q.orientation_cover(); a H_4(4, 2) sage: a.dimension() 9 TESTS:: sage: QuadraticStratum({-1:4}).orientation_cover() H_1(0) """ from surface_dynamics.flat_surfaces.abelian_strata import AbelianStratum l = [] for z in self.zeros(): if z == -1: if fake_zeros: l.append(0) elif z % 2: l.append(z+1) else: l.append(z//2) l.append(z//2) if not l: l = [0] return AbelianStratum(l)
[docs] def spin(self): r""" Return the spin structure (None, 0 or 1) of that component. Any quadratic differential has a canonic double cover (called the orientation cover) which is a surface, generally of higher genera, with an Abelian differential. The *spin structure* of the quadratic differential is the spin structure of that double cover. The spin is None if any degree of zero of the quadratic differential is congruent to 2 mod 4. Otherwise, denoting respectively k1 and k3 the number of degree of zero congruent to 1 and 3 modulo 4 we have ..MATH:: spin = ((k1-k3)/4) mod 2 The proof of that formula is the object of [Lan04]_. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(1,3).spin() 0 sage: QuadraticStratum(1,3,genus=1).spin() 1 sage: QuadraticStratum(2,2).spin() is None True """ k = [0,0,0,0] for z in self.zeros(): zz = z%4 if zz == 2: return None k[zz] += 1 return (abs(k[1]-k[3])//4) % 2
[docs] def has_hyperelliptic_component(self): r""" Returns True if and only if self has a connected component which contains only hyperelliptic surfaces. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(2,2).has_hyperelliptic_component() True sage: QuadraticStratum(3,1).has_hyperelliptic_component() False """ return HQSC in self._cc or GTHQSC in self._cc
[docs] def hyperelliptic_component(self): r""" Returns the hyperelliptic component of self (or raise a ValueError). EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(2,2).hyperelliptic_component() Q_2(2^2)^hyp sage: QuadraticStratum(3,1).hyperelliptic_component() Traceback (most recent call last): ... ValueError: the stratum has no hyperelliptic component """ if HQSC in self._cc: return HQSC(self) if GTHQSC in self._cc: return GTHQSC(self) raise ValueError("the stratum has no hyperelliptic component")
[docs] def has_non_hyperelliptic_component(self): r""" Test whether this stratum has a non hyperelliptic component. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(10,10).has_non_hyperelliptic_component() True sage: QuadraticStratum(6,6).has_non_hyperelliptic_component() False """ return NQSC in self._cc or GTNQSC in self._cc
[docs] def non_hyperelliptic_component(self): r""" Returns the non hyperelliptic component of this stratum (or raise a ValueError). EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(10,10).non_hyperelliptic_component() Q_6(10^2)^nonhyp sage: QuadraticStratum(2,2).non_hyperelliptic_component() Traceback (most recent call last): ... ValueError: no non hyperelliptic component """ if NQSC in self._cc: return NQSC(self) elif GTNQSC in self._cc: return GTNQSC(self) raise ValueError("no non hyperelliptic component")
[docs] def has_regular_and_irregular_components(self): r""" Test whether this component has a pair of regular, irregular components. The list of strata of quadratic differentials that admit such a pair are: in genus 3: Q(9,-1), Q(6,3,-1), Q(3,3,3,-1) in genus 4: Q(12), Q(9,3), Q(6,6), Q(6,3,3), Q(3,3,3,3) EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(9,-1).has_regular_and_irregular_components() True sage: QuadraticStratum(11,1).has_regular_and_irregular_components() False """ return REQSC in self._cc
[docs] def regular_component(self): r""" Returns the regular component of that stratum or raise a ValueError. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(12).regular_component() Q_4(12)^reg sage: QuadraticStratum(2,2,2,2).regular_component() Traceback (most recent call last): ... ValueError: no regular component for this stratum """ if REQSC in self._cc: return REQSC(self) raise ValueError("no regular component for this stratum")
[docs] def irregular_component(self): r""" Returns the irregular component of that stratum or raise a ValueError. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(3,3,3,-1).irregular_component() Q_3(3^3, -1)^irr sage: QuadraticStratum(2,2).irregular_component() Traceback (most recent call last): ... ValueError: no irregular component for this stratum """ if IEQSC in self._cc: return IEQSC(self) raise ValueError("no irregular component for this stratum")
[docs] def random_cylindric_permutation(self): r""" Return a random cylindric permutation that belongs to this stratum. EXAMPLES:: sage: from surface_dynamics import * sage: Q = QuadraticStratum(4,4) sage: Q.random_cylindric_permutation() # random 0 1 2 3 4 5 1 5 2 6 3 6 4 0 """ return self.random_component().random_cylindric_permutation()
[docs] class QuadraticStratumComponent(StratumComponent): r""" Generic class for component of quadratic stratum. """ _name = 'c'
[docs] def orientation_cover_component(self, fake_zeros=False): r""" Return the connected component of Abelian stratum component which contains the set of orientation cover of quadratic differentials in this connected component. ALGORITHM: The spin only depends on the component and the only components for which the double cover belongs to a hyperelliptic component are Q(k,-1^k+4) and Q(2g-1,2g-1,-1,-1)^hyp OPTIONS: - ``fake_zeros`` - boolean - if True, add fake zeros which corresponds to the double cover of poles. EXAMPLES:: sage: from surface_dynamics import * sage: cc = QuadraticStratum(5,genus=0).unique_component() sage: cc.orientation_cover_component() H_4(6)^hyp sage: cc = QuadraticStratum(5,genus=1).unique_component() sage: cc.orientation_cover_component() H_4(6)^odd sage: cc = QuadraticStratum(5,genus=2).unique_component() sage: cc.orientation_cover_component() H_4(6)^even sage: cc = QuadraticStratum(1,1,genus=0).unique_component() sage: cc.orientation_cover_component() H_3(2^2)^odd sage: cc = QuadraticStratum(4,genus=0).unique_component() sage: cc.orientation_cover_component() H_3(2^2)^hyp sage: cc = QuadraticStratum(1,1,genus=1).unique_component() sage: cc.orientation_cover_component() H_3(2^2)^hyp """ stratum = self.stratum() zeros = stratum.zeros(poles=False, fake_zeros=False) astratum = stratum.orientation_cover(fake_zeros=fake_zeros) # 0) easy case: the stratum is connected if astratum.is_connected(): return astratum.unique_component() # 1) canonical construction of Abelian hyperelliptic components if len(zeros) == 1 and stratum.genus() == 0: return astratum.hyperelliptic_component() # 2) other possibility for the hyperelliptic components (low genus # special case for Q(1,1,-1,-1)) if zeros == (1,1) and stratum.nb_poles() == 2: return astratum.hyperelliptic_component() # 3) composant with spin spin = stratum.spin() if spin == 0: return astratum.even_component() elif spin == 1: return astratum.odd_component() # 4) as connected components are treated in 0 there remains only non # hyperelliptic components return astratum.non_hyperelliptic_component()
[docs] def random_cylindric_permutation(self, nsteps=64): r""" Return a cylindric permutation of the form ``p = ((0,...),(..., 0))`` where 0 can be any label. EXAMPLES:: sage: from surface_dynamics import * sage: Q = QuadraticStratum({4:1,-1:4}); Q Q_1(4, -1^4) sage: c = Q.unique_component() sage: p = c.random_cylindric_permutation() sage: p.stratum_component() Q_1(4, -1^4)^c sage: Q = QuadraticStratum(6,6) sage: c_hyp, c_reg, c_irr = Q.components() sage: (c_hyp, c_reg, c_irr) (Q_4(6^2)^hyp, Q_4(6^2)^reg, Q_4(6^2)^irr) sage: all(c_hyp.random_cylindric_permutation().stratum_component() == c_hyp for _ in range(4)) True sage: all(c_reg.random_cylindric_permutation().stratum_component() == c_reg for _ in range(4)) True sage: all(c_irr.random_cylindric_permutation().stratum_component() == c_irr for _ in range(4)) True """ import sage.misc.prandom as prandom p = self.permutation_representative() for _ in range(nsteps): while not p.has_rauzy_move(0): p = p.rauzy_move(1) continue while not p.has_rauzy_move(1): p = p.rauzy_move(0) continue rd = prandom.random() if rd < 0.1: # inplace (symmetric) p._inversed_twin() p._reversed_twin() elif rd < 0.55: p = p.rauzy_move(0) else: p = p.rauzy_move(1) while p[0][0] != p[1][-1]: rd = prandom.random() if rd < 0.1: # inplace (symmetric) p._inversed_twin() p._reversed_twin() elif not p.has_rauzy_move(0): p = p.rauzy_move(1) elif not p.has_rauzy_move(1) or rd < .55: p = p.rauzy_move(0) else: p = p.rauzy_move(1) return p
[docs] def one_cylinder_diagram(self): r""" Return a separatrix diagram with one cylinder that belongs to this component of stratum. EXAMPLES:: sage: from surface_dynamics import * sage: Q = QuadraticStratum({1:1,-1:5}) sage: c = Q.unique_component().one_cylinder_diagram() sage: c (0,0,1,1,2,2)-(3,3) sage: c.stratum() == Q True sage: Q = QuadraticStratum(5,-1) sage: c = Q.unique_component().one_cylinder_diagram() sage: c (0,1,1,2)-(3,0,3,2) sage: c.stratum() == Q True sage: QuadraticStratum({-1:4}).unique_component().one_cylinder_diagram() (0,0)-(1,1) sage: QuadraticStratum({-1:4,0:1}).unique_component().one_cylinder_diagram() (0,0)-(1,1,2,2) """ from surface_dynamics.flat_surfaces.separatrix_diagram import QuadraticCylinderDiagram p = self.permutation_representative(reduced=True).to_cylindric() if p[0][0] == p[1][-1]: top = [x-1 for x in p[0][1:]] bot = [x-1 for x in p[1][-2::-1]] elif p[0][-1] == p[1][0]: top = p[0][:-1] bot = p[1][1:] else: raise RuntimeError('non cylindric permutation for {}\n{}'.format(self, p)) return QuadraticCylinderDiagram([(bot,top)])
# The code below can be used to compute a *covering*!! # def one_cylinder_diagram(self,fake_zeros=False,verbose=False): # r""" # """ # from separatrix_diagram import CylinderDiagram # p = self.permutation_representative() # g = self.stratum().genus() # n = len(p)-1 # p.alphabet(range(n+1)) # assert p[0][0] == p[1][-1] # # t0,t1 = p._twin # # f = lambda (i,j): (i,j-1) if i == 0 else (i,j) # # t0 = map(f,t0[1:]) # t1 = map(f,t1[:-1]) # # s0 = set(j for (i,j) in t1 if i == 0) # s0_p = set(j for (i,j) in t0 if i == 0 and abs(t0[j][1]-j) == 1) # s1 = set(j for (i,j) in t0 if i == 1) # s1_p = set(j for (i,j) in t1 if i == 1 and abs(t1[j][1]-j) == 1) # # p0,p1 = p # p0 = [i-1 for i in p0[1:]] # p1 = [i-1 for i in p1[:-1]] # # if verbose: # print("p0 = %s" % p0) # print("p1 = %s" % p1) # print("t0 = %s" % t0) # print("t1 = %s" % t1) # print("s0 = %s" % s0) # print("s1 = %s" % s1) # # # if g == 0: # top = [None] * len(p1) # bot = [None] * len(p1) # # for i,j in enumerate(p1): # if verbose: print("i,j=",i,j) # ii = t1[i][1] # if verbose: print("same side") # bot[i] = j # top[ii] = j # if not fake_zeros and i in s1_p: # bot[ii] = -1 # top[i] = -1 # else: # bot[ii] = j+n # top[i] = j+n # if verbose: # print("c0 =",c0_bot,c0_top) # print("c1 =",c1_bot,c1_top) # # dom = dict((j,i-1) for (i,j) in enumerate(sorted(set(bot+top)))) # if verbose: # print(dom) # # bot = [dom[i] for i in bot if i != -1] # top = [dom[i] for i in top if i != -1] # # return CylinderDiagram([(bot,top)]) # # else: # # c0_top = [None] * len(p0) # c1_bot = [None] * len(p0) # c0_bot = [None] * len(p1) # c1_top = [None] * len(p1) # # for i,j in enumerate(p0): # if verbose: print("i,j=",i,j,) # if i in s0: # if verbose: print("other side") # c0_top[i] = j # c1_bot[i] = j+n # elif c0_top[i] is None: # ii = t0[i][1] # if verbose: print("same side") # c0_top[i] = j # c1_bot[ii] = j # if not fake_zeros and i in s0_p: # c0_top[ii] = -1 # c1_bot[i] = -1 # else: # c0_top[ii] = j+n # c1_bot[i] = j+n # elif verbose: # print("skip") # # if verbose: # print("c0 =",c0_bot,c0_top) # print("c1 =",c1_bot,c1_top) # for i,j in enumerate(p1): # if verbose: print("i,j=",i,j,) # if i in s1: # if verbose: print("other side") # c0_bot[i] = j # c1_top[i] = j+n # elif c0_bot[i] is None: # ii = t1[i][1] # if verbose: print("same side") # c0_bot[i] = j # c1_top[ii] = j # if not fake_zeros and i in s1_p: # c0_bot[ii] = -1 # c1_top[i] = -1 # else: # c0_bot[ii] = j+n # c1_top[i] = j+n # elif verbose: # print("skip") # if verbose: # print("c0 =",c0_bot,c0_top) # print("c1 =",c1_bot,c1_top) # # if verbose: print((c0_bot,c0_top),(c1_bot,c1_top)) # # dom = dict((j,i-1) for (i,j) in enumerate(sorted(set(c0_bot+c0_top+c1_bot+c1_top)))) # if verbose: # print(dom) # # c0_bot = [dom[i] for i in c0_bot if i != -1] # c0_top = [dom[i] for i in c0_top if i != -1] # c1_bot = [dom[i] for i in c1_bot if i != -1] # c1_top = [dom[i] for i in c1_top if i != -1] # # return CylinderDiagram([(c0_bot[::-1],c0_top),(c1_bot,c1_top[::-1])])
[docs] def lyapunov_exponents_H_plus(self, *args, **kargs): r""" Compute the `H^+` part of Lyapunov exponents spectrum. All arguments and keywords are sent to ? EXAMPLES:: sage: from surface_dynamics import * sage: R = QuadraticStratum([3,3,3,-1]).regular_component() sage: R.lyapunov_exponents_H_plus(nb_iterations=2**21) # abs tol .05 [0.596, 0.405, 0.202] sage: sum(_) # abs tol .05 1.2 sage: R = QuadraticStratum([2,2,2,2]).unique_component() sage: R.lyapunov_exponents_H_plus(nb_iterations=2**21) # abs tol .05 [0.651, 0.469, 0.243] sage: sum(_) # abs tol .05 1.3636 """ return self.permutation_representative(reduced=False).lyapunov_exponents_H_plus(*args, **kargs)
[docs] def lyapunov_exponents_H_minus(self, **kargs): r""" Compute the `H^-` Lyapunov exponents. EXAMPLES:: sage: from surface_dynamics import * sage: Q = QuadraticStratum({1:3, -1:3}).unique_component() sage: Q.lyapunov_exponents_H_minus(nb_iterations=2**21) # abs tol .05 [1.000, 0.369, 0.176] sage: R = QuadraticStratum([3,3,3,-1]).regular_component() sage: R.lyapunov_exponents_H_minus(nb_iterations=2**21) # abs tol .05 [1.000, 0.328, 0.1899, 0.0820] """ perm = self.permutation_representative(reduced=False).orientation_cover() if 'isotypic_decomposition' not in kargs: kargs['isotypic_decomposition'] = (1,-1) return perm.lyapunov_exponents_H_plus(**kargs)[0]
QSC = QuadraticStratumComponent
[docs] class GenusZeroQuadraticStratumComponent(QSC): r""" This class is intended to be called internally rather than directly. Call only with appropriate parameters, in particular correct genus: no consistency check inside, no prediction as to what may happen otherwise. """
[docs] def permutation_representative(self, reduced=True, alphabet=None, relabel=True): r""" Returns a generalized permutation representative. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum(-1,-1,-1,-1).permutation_representative() 0 1 1 2 2 0 sage: QuadraticStratum(1,genus=0).permutation_representative() 0 1 1 2 2 3 3 4 4 0 sage: QuadraticStratum(2,genus=0).permutation_representative() 0 1 1 2 2 3 3 4 4 5 5 0 sage: QuadraticStratum([1,1],genus=0).permutation_representative() 0 1 1 2 2 3 4 4 5 5 3 6 6 0 sage: QuadraticStratum([2,1],genus=0).permutation_representative() 0 1 1 2 2 3 3 4 5 5 6 6 4 7 7 0 """ p = self._stratum.nb_poles() f = self._stratum.nb_fake_zeros() z = self._stratum.zeros(fake_zeros=False,poles=False) ll = [0] if f: ll = list(map(lambda x:'0'+str(x),range(f+1))) l0 = ll + [2*p-6,2*p-6] l1 = [] for k in range(1,p-3): l1.extend([2*k,2*k,2*k+1]) for k in range(len(z)): for i in range(sum(z[:k])+1,sum(z[:k+1])): del l1[2*i+k] l2 = [2 * sum(z[:k+1]) + 1 for k in range(len(z))] l2.reverse() l1.extend(l2) l1.extend([1,1]) l1.extend(ll) from surface_dynamics.interval_exchanges.constructors import GeneralizedPermutation p = GeneralizedPermutation(l0, l1, reduced=reduced) if alphabet is not None: p.alphabet(alphabet) elif relabel: p.alphabet(range(len(p))) return p
GZQSC = GenusZeroQuadraticStratumComponent
[docs] class GenusOneQuadraticStratumComponent(QSC): r""" This class is intended to be called internally rather than directly. Call only with appropriate parameters, in particular correct genus: no consistency check inside, no prediction as to what may happen otherwise. """
[docs] def permutation_representative(self, reduced=True, alphabet=None, relabel=True): r""" Returns a generalized permutation representative. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStratum([2],genus=1).permutation_representative() 0 1 2 2 1 3 3 0 sage: QuadraticStratum([3],genus=1).permutation_representative() 0 1 2 2 3 3 1 4 4 0 sage: QuadraticStratum([1,1],genus=1).permutation_representative() 0 1 2 3 3 2 1 4 4 0 sage: Q = QuadraticStratum([2,1],genus=1) sage: Q.permutation_representative(alphabet='abcdef') a b c c d e e d b f f a TESTS:: sage: from surface_dynamics import * sage: QuadraticStratum([1],genus=1).permutation_representative() Traceback (most recent call last): ... EmptySetError: The stratum is empty sage: Q = QuadraticStratum(2,-1,-1,0) sage: p = Q.permutation_representative() sage: p 0 1 2 3 3 2 4 4 0 1 sage: p.stratum() == Q True """ p = self._stratum.nb_poles() f = self._stratum.nb_fake_zeros() z = self._stratum.zeros(fake_zeros=False, poles=False) l0 = [0,1] for k in range(1,p): l0.extend([2*k, 2*k+1, 2*k+1]) l1 = [2 * (sum(z[:k])) for k in range(1,len(z))] l1.extend([1, 2*p, 2*p, 0]) for k in range(len(z)): for i in range(sum(z[:k])+1,sum(z[:k+1])): del l0[2*i+k] if f: l0[:1] = ['0%d' % i for i in range(f+1)] l1[-1:] = ['0%d' % i for i in range(f+1)] from surface_dynamics.interval_exchanges.constructors import GeneralizedPermutation p = GeneralizedPermutation(l0, l1, reduced=reduced) if alphabet is not None: p.alphabet(alphabet) elif relabel: p.alphabet(range(len(p))) return p
GOQSC = GenusOneQuadraticStratumComponent
[docs] class GenusTwoHyperellipticQuadraticStratumComponent(QSC): r""" This class is intended to be called internally rather than directly. Call only with appropriate parameters, in particular correct genus: no consistency check inside, no prediction as to what may happen otherwise. """ _name = 'hyp'
[docs] def permutation_representative(self, reduced=True, alphabet=None, relabel=True): r""" Returns a generalized permutation representative. TESTS: sage: from surface_dynamics import * sage: Q = QuadraticStratum([1,1,1,1],genus=2) sage: H = Q.hyperelliptic_component() sage: H.permutation_representative() 0 1 2 3 1 4 5 2 6 5 4 6 3 0 sage: Q = QuadraticStratum([2,1,1],genus=2) sage: H = Q.hyperelliptic_component() sage: H.permutation_representative(alphabet='abcdef') a b c b d e f e d f c a sage: Q = QuadraticStratum([2,2],genus=2) sage: H = Q.hyperelliptic_component() sage: H.permutation_representative() 0 1 2 1 3 4 3 4 2 0 """ p = self._stratum.nb_poles() f = self._stratum.nb_fake_zeros() z = self._stratum.zeros(fake_zeros=False,poles=False) if f: ll = list(map(lambda x:'0'+str(x),range(f+1))) else: ll = [0] if z == (2,2): l0 = ll + [6, 5, 6, 4] l1 = [2, 4, 2, 5] elif z == (2,1,1): l0 = ll + [6, 5, 6, 4, 3] l1 = [2, 3, 4, 2, 5] elif z == (1,1,1,1): l0 = ll + [6, 1, 5, 6, 4, 3] l1 = [1, 2, 3, 4, 2, 5] else: raise ValueError("Wrong stratum for GenusTwoHyperellipticQuadraticStratumComponent") l1.extend(ll) from surface_dynamics.interval_exchanges.constructors import GeneralizedPermutation p = GeneralizedPermutation(l0, l1, reduced=reduced) if alphabet is not None: p.alphabet(alphabet) elif relabel: p.alphabet(range(len(p))) return p
GTHQSC = GenusTwoHyperellipticQuadraticStratumComponent
[docs] class GenusTwoNonhyperellipticQuadraticStratumComponent(QSC): r""" This class is intended to be called internally rather than directly. Call only with appropriate parameters, in particular correct genus: no consistency check inside, no prediction as to what may happen otherwise. """ _name = 'nonhyp'
[docs] def permutation_representative(self, reduced=True, alphabet=None, relabel=True): r""" Returns a generalized permutation representative. TESTS:: sage: from surface_dynamics import * sage: Q = QuadraticStratum([6],genus=2) sage: N = Q.non_hyperelliptic_component() sage: N.permutation_representative() 0 1 2 1 3 3 4 4 5 5 2 0 sage: Q = QuadraticStratum([3,3],genus=2) sage: N = Q.non_hyperelliptic_component() sage: N.permutation_representative() 0 1 2 1 3 4 3 5 5 4 6 6 2 0 """ p = self._stratum.nb_poles() f = self._stratum.nb_fake_zeros() z = self._stratum.zeros(fake_zeros=False,poles=False) if z == (4,): raise ValueError("The stratum Q(4) is empty!") if z == (3,1): raise ValueError("The stratum Q(3,1) is empty!") if z in [(2,2),(2,1,1),(1,1,1,1)]: raise ValueError("This stratum has no non-hyperelliptic component!") if f: ll = list(map(lambda x:'0'+str(x),range(f+1))) else: ll = [0] l0 = ll + [2*p+6,1,2*p+5,2*p+6,p+4,p+3] l1 = list(range(1,p+5)) for k in range(p+5,2*p+5): l1.extend([2*p+7-k,k,k]) l1.extend([2,2*p+5]) l1.extend(ll) for k in range(len(z)): for i in range(sum(z[:k])+1,sum(z[:k+1])): if i in l0: del l0[l0.index(i)] if i in l0: del l0[l0.index(i)] if i in l1: del l1[l1.index(i)] if i in l1: del l1[l1.index(i)] from surface_dynamics.interval_exchanges.constructors import GeneralizedPermutation p = GeneralizedPermutation(l0, l1, reduced=reduced) if alphabet is not None: p.alphabet(alphabet) elif relabel: p.alphabet(range(len(p))) return p
GTNQSC = GenusTwoNonhyperellipticQuadraticStratumComponent
[docs] class HyperellipticQuadraticStratumComponent(QSC): r""" This class is intended to be called internally rather than directly. Call only with appropriate parameters, in particular correct genus: no consistency check inside, no prediction as to what may happen otherwise. """ _name = 'hyp'
[docs] def permutation_representative(self, reduced=True, alphabet=None, relabel=True): r""" Returns a generalized permutation representative. EXAMPLES:: sage: from surface_dynamics import * sage: cc = QuadraticStratum([6],genus=2).hyperelliptic_component() sage: cc.permutation_representative() 0 1 2 3 4 1 5 4 3 2 5 0 sage: cc = QuadraticStratum([3,3],genus=2).hyperelliptic_component() sage: cc.permutation_representative() 0 1 2 3 4 5 1 6 5 4 3 2 6 0 sage: cc = QuadraticStratum(10,10).hyperelliptic_component() sage: cc.permutation_representative() 0 1 2 3 4 5 6 1 7 8 9 10 11 11 10 9 8 7 12 6 5 4 3 2 12 0 """ p = self._stratum.nb_poles() f = self._stratum.nb_fake_zeros() z = self._stratum.zeros(fake_zeros=False,poles=True) if f: ll = ['0' + str(x) for x in range(f + 1)] else: ll = [0] if len(z) == 2: r = z[0]//2 s = z[1]//2 elif len(z) == 4: r = z[0]+1 s = z[2]+1 elif len(z) == 3 and z[0]%2: r = z[0]+1 s = z[2]//2 elif len(z) == 3: r = z[0]//2 s = z[2]+1 else: raise ValueError("This stratum has no hyperelliptic component!") l0 = ll + ['A'] l0.extend(range(1,r+1)) l0.append('A') l0.extend(range(r+1,r+s+1)) l1 = list(range(r+s,r,-1)) l1.append('B') l1.extend(range(r,0,-1)) l1.append('B') l1.extend(ll) from surface_dynamics.interval_exchanges.constructors import GeneralizedPermutation p = GeneralizedPermutation(l0, l1, reduced=reduced) if alphabet is not None: p.alphabet(alphabet) elif relabel: p.alphabet(range(len(p))) return p
HQSC = HyperellipticQuadraticStratumComponent
[docs] class ConnectedQuadraticStratumComponent(QSC): r""" Connected component of stratum of quadratic differentials. This class is intended to be called internally rather than directly. Call only with appropriate parameters, in particular correct genus: no consistency check inside, no prediction as to what may happen otherwise. """ _name = 'c'
[docs] def permutation_representative(self, reduced=True, alphabet=None, relabel=True): r""" Returns a generalized permutation representative. NOTES: The representative is made by constructing two lists l0 and l1 which correspond to a generalized permutation representative for the stratum with simple zeros, and then erasing some elements from l0 and l1 (this corresponds to collapsing saddle connections to merge zeros). It may be possible to find a faster way to obtain the desired l0 and l1 than by constructing the long versions and erasing symbols; the current implementation has a loop with "del l0[l0.index(i)]" and "del l1[l1.index(i)]". EXAMPLES:: sage: from surface_dynamics import * sage: cc = QuadraticStratum(6,-1,-1).non_hyperelliptic_component() sage: p = cc.permutation_representative(); p 0 1 2 1 3 3 4 4 5 5 2 0 sage: p.stratum_component() Q_2(6, -1^2)^nonhyp sage: cc = QuadraticStratum([3,3],genus=2).non_hyperelliptic_component() sage: p = cc.permutation_representative(); p 0 1 2 1 3 4 3 5 5 4 6 6 2 0 sage: p.stratum_component() Q_2(3^2, -1^2)^nonhyp sage: cc = QuadraticStratum(8).unique_component() sage: p = cc.permutation_representative(); p 0 1 2 1 2 3 4 5 4 5 3 0 sage: p.stratum_component() Q_3(8)^c sage: Q = QuadraticStratum(4,4).unique_component() sage: p = Q.permutation_representative(); p 0 1 2 3 2 3 4 5 6 5 6 1 4 0 sage: p.stratum_component() Q_3(4^2)^c sage: Q = QuadraticStratum({12:1,-1:4}).unique_component() sage: p = Q.permutation_representative() sage: p 0 1 2 1 2 3 3 4 4 5 5 6 6 7 8 9 8 9 7 0 sage: p.stratum() Q_3(12, -1^4) """ p = self._stratum.nb_poles() f = self._stratum.nb_fake_zeros() z = self._stratum.zeros(fake_zeros=False,poles=False) g = (sum(z)-p)//4+1 if f: ll = list(map(lambda x:'0'+str(x),range(f+1))) else: ll = [0] l0 = ll + [1,2,3] l1 = [4*g-3+p,3,4*g-2+p,2,4*g-3+p,1,4*g-2+p] for k in range(g-2): # V(k) in Zorich 2008 l0.extend([4+4*k,4*g-1+p+2*k,7+4*k,4*g+p+2*k,6+4*k,4*g-1+p+2*k,5+4*k,4*g+p+2*k]) for l in range(p): # W(l) in Zorich 2008 l0.extend([4*g-4+l,6*g+p-5+l,6*g+p-5+l]) l0.append(4*g-4+p) l1.extend(range(4,4*g-3+p)) l1.extend(ll) for k in range(len(z)): for i in range(sum(z[:k])+1,sum(z[:k+1])): del l0[l0.index(i)] del l1[l1.index(i)] from surface_dynamics.interval_exchanges.constructors import GeneralizedPermutation p = GeneralizedPermutation(l0, l1, reduced=reduced) if alphabet is not None: p.alphabet(alphabet) elif relabel: p.alphabet(range(len(p))) return p
CQSC = ConnectedQuadraticStratumComponent
[docs] class NonhyperellipticQuadraticStratumComponent(CQSC): r""" Non hyperelliptic component of stratum of quadratic differentials. """ _name = 'nonhyp'
NQSC = NonhyperellipticQuadraticStratumComponent
[docs] class RegularExceptionalQuadraticStratumComponent(QSC): r""" This class is intended to be called internally rather than directly. Call only with appropriate parameters, in particular correct genus: no consistency check inside, no prediction as to what may happen otherwise. """ _name = 'reg'
[docs] def permutation_representative(self, reduced=True, alphabet=None, relabel=True): r""" Returns a generalized permutation representative. EXAMPLES:: sage: from surface_dynamics import * sage: cc = QuadraticStratum(9,-1).regular_component() sage: p = cc.permutation_representative(); p 0 1 2 1 2 3 3 4 5 6 5 6 4 0 sage: p.stratum_component() Q_3(9, -1)^reg sage: cc = QuadraticStratum(6,3,-1).regular_component() sage: p = cc.permutation_representative(); p 0 1 2 3 1 2 4 4 5 6 7 6 7 3 5 0 sage: p.stratum_component() Q_3(6, 3, -1)^reg sage: cc = QuadraticStratum(12).regular_component() sage: p = cc.permutation_representative(); p 0 1 2 1 2 3 4 3 4 5 5 6 7 6 7 0 sage: p.stratum_component() Q_4(12)^reg """ p = self._stratum.nb_poles() f = self._stratum.nb_fake_zeros() z = self._stratum.zeros(fake_zeros=False,poles=True) if f: ll = list(map(lambda x: '0' + str(x), range(f+1))) else: ll = [0] if z == (12,): l0 = ll + [1, 2, 1, 2, 3, 4, 3, 4, 5] l1 = [5, 6, 7, 6, 7] elif z == (9, 3): l0 = ll + [1, 2, 3, 4, 2, 'A', 5, 6] l1 = [1, 4, 5, 7, 6, 7, 'A', 3] elif z == (6, 6): l0 = ll +[1, 2, 3, 4, 'A', 2, 5, 6, 'A'] l1 = [1, 4, 5, 7, 6, 7, 3] elif z == (6, 3, 3): l0 = ll + [1, 2, 3, 'B', 4, 2, 'A', 5, 6] l1 = [1, 4, 5, 7, 6, 7, 'B', 'A', 3] elif z == (3, 3, 3, 3): l0 = ll + [1, 2, 3, 'B', 4, 2, 'A', 5, 6] l1 = [1, 4, 5, 7, 6, 'C', 7, 'C', 'B', 'A', 3] elif z == (9, -1): l0 = ll + [1, 2, 1, 2, 3, 3, 4] l1 = [5, 6, 5, 6, 4] elif z == (6, 3, -1): l0 = ll + [1, 2, 3, 1, 2, 4, 4, 5] l1 = [6, 7, 6, 7, 3, 5] elif z == (3, 3, 3, -1): l0 = ll + [1, 2, 3, 4, 2, 3, 5, 5, 6] l1 = [7, 1, 8, 7, 8, 4, 6] else: raise ValueError("RegularExceptionalQuadraticStratumComponent applies only to the 4 exceptional strata") l1.extend(ll) from surface_dynamics.interval_exchanges.constructors import GeneralizedPermutation p = GeneralizedPermutation(l0, l1, reduced=reduced) if alphabet: p.alphabet(alphabet) elif relabel: p.alphabet(range(len(p))) return p
REQSC = RegularExceptionalQuadraticStratumComponent
[docs] class IrregularExceptionalQuadraticStratumComponent(QSC): r""" This class is intended to be called internally rather than directly. Call only with appropriate parameters, in particular correct genus: no consistency check inside, no prediction as to what may happen otherwise. """ _name = 'irr'
[docs] def permutation_representative(self, reduced=True, alphabet=None, relabel=True): r""" Returns a generalized permutation representative. EXAMPLES:: sage: from surface_dynamics import * sage: cc = QuadraticStratum(9,-1).irregular_component() sage: p = cc.permutation_representative(); p 0 1 2 3 4 1 2 3 4 5 5 6 6 0 sage: p.stratum_component() # optional Q_3(9, -1)^irr sage: cc = QuadraticStratum(6,3,-1).irregular_component() sage: p = cc.permutation_representative(); p 0 1 2 3 4 5 1 2 3 4 5 6 6 7 7 0 sage: p.stratum_component() Q_3(6, 3, -1)^irr sage: cc = QuadraticStratum(12).irregular_component() sage: p = cc.permutation_representative(); p 0 1 2 3 4 5 6 5 7 6 4 7 3 2 1 0 sage: p.stratum_component() Q_4(12)^irr """ p = self._stratum.nb_poles() f = self._stratum.nb_fake_zeros() z = self._stratum.zeros(fake_zeros=False,poles=True) if f: ll = list(map(lambda x:'0'+str(x),range(f+1))) else: ll = [0] if z == (12,): l0 = ll + [1, 2, 3, 4, 5, 6, 5] l1 = [7, 6, 4, 7, 3, 2, 1] elif z == (9, 3): l0 = ll + [1, 2, 3, 4, 'A', 3, 'A', 5, 6] l1 = [1, 5, 7, 4, 2, 6, 7] elif z == (6, 6): l0 = ll + [1, 2, 3, 4, 3, 'A', 5, 6] l1 = [1, 5, 7, 4, 2, 6, 'A', 7] elif z == (6, 3, 3): l0 = ll + [1, 2, 'B', 3, 4, 'A', 3, 'A', 5, 6] l1 = [1, 5, 7, 'B', 4, 2, 6, 7] elif z == (3, 3, 3, 3): l0 = ll + [1, 2, 'B', 3, 4, 'A', 3, 'A', 5, 'C', 6] l1 = [1, 5, 7, 'B', 4, 2, 6, 'C', 7] elif z == (9, -1): l0 = ll + [1, 2, 3, 4, 1, 2, 3, 4, 5] l1 = [5, 6, 6] elif z == (6, 3, -1): l0 = ll + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6] l1 = [6, 7, 7] elif z == (3, 3, 3, -1): l0 = ll + [1, 2, 3, 4, 5, 1, 6, 2, 3, 4, 5, 6, 7] l1 = [7, 8, 8] else: raise ValueError("IrregularExceptionalQuadraticStratumComponent applies only to the 4 exceptional strata") l1.extend(ll) from surface_dynamics.interval_exchanges.constructors import GeneralizedPermutation p = GeneralizedPermutation(l0, l1, reduced=reduced) if alphabet: p.alphabet(alphabet) elif relabel: p.alphabet(range(len(p))) return p
IEQSC = IrregularExceptionalQuadraticStratumComponent
[docs] def QuadraticStrata(genus=None, dimension=None, min_nb_poles=None, max_nb_poles=None, nb_poles=None, fake_zeros=False): r""" Quadratic strata. INPUT: - ``genus`` - a non negative integer or None - ``dimension`` - a non negative integer or None - ``min_nb_poles``, ``max_nb_poles`` - the minimum and maximum number of poles allowed - ``nb_poles`` - integer - the number of poles (if the option is set then the options ``min_nb_poles`` and ``max_nb_poles`` are ignored) - ``fake_zeros`` - boolean - whether to allow fake zeros or not EXAMPLES:: sage: from surface_dynamics import * sage: Q = QuadraticStrata(genus=2); Q Quadratic strata of genus 2 surfaces sage: Q.cardinality() +Infinity sage: i = iter(Q) sage: next(i) Q_2(2^2) sage: next(i) Q_2(2, 1^2) sage: next(i) Q_2(1^4) sage: next(i) Q_2(5, -1) sage: Q = QuadraticStrata(dimension=5); Q Quadratic strata of dimension 5 sage: Q = QuadraticStrata(genus=3,max_nb_poles=6); Q Quadratic strata of genus 3 surfaces with at most 6 poles sage: Q.cardinality() 463 sage: for q in Q: print(q) Q_3(8) Q_3(7, 1) Q_3(6, 2) ... Q_3(2^2, 1^10, -1^6) Q_3(2, 1^12, -1^6) Q_3(1^14, -1^6) sage: Q = QuadraticStrata(genus=2,nb_poles=0); Q Quadratic strata of genus 2 surfaces with no pole sage: for q in Q: print(q) Q_2(2^2) Q_2(2, 1^2) Q_2(1^4) sage: Q = QuadraticStrata(dimension=7,min_nb_poles=1,max_nb_poles=3); Q Quadratic strata of dimension 7 with at least 1 and at most 3 poles sage: for q in Q: print(q) Q_3(8, 1, -1) Q_3(7, 2, -1) Q_3(6, 3, -1) Q_3(5, 4, -1) Q_2(2, 1^3, -1) Q_3(10, -1^2) Q_2(4, 1^2, -1^2) Q_2(3, 2, 1, -1^2) Q_2(2^3, -1^2) Q_2(6, 1, -1^3) Q_2(5, 2, -1^3) Q_2(4, 3, -1^3) sage: Q = QuadraticStrata(dimension=6, genus=0) sage: Q Quadratic strata of genus 0 surfaces and dimension 6 sage: for q in Q: print(q) Q_0(1^2, -1^6) Q_0(3, -1^7) sage: Q = QuadraticStrata(dimension=5, genus=1, fake_zeros=True, nb_poles=0) sage: for q in Q: print(q) """ if nb_poles is not None: min_nb_poles = max_nb_poles = Integer(nb_poles) else: if min_nb_poles is None: min_nb_poles = Integer(0) else: min_nb_poles = Integer(min_nb_poles) if max_nb_poles is None: max_nb_poles = Infinity else: if max_nb_poles == Infinity: max_nb_poles = Infinity else: max_nb_poles = Integer(max_nb_poles) if max_nb_poles < min_nb_poles: raise ValueError("min_nb_poles should be less or equal than max_nb_poles") if genus is None: if dimension is None: return QuadraticStrata_all() dimension = Integer(dimension) return QuadraticStrata_d(dimension,min_nb_poles,max_nb_poles,fake_zeros) genus = Integer(genus) if dimension is None: if fake_zeros: raise ValueError('fake_zeros only allowed if dimension is fixed') return QuadraticStrata_g(genus,min_nb_poles,max_nb_poles) dimension = Integer(dimension) return QuadraticStrata_gd(genus,dimension,min_nb_poles,max_nb_poles,fake_zeros)
# TODO: there is no need to have multiple class # just let the attribute be None when this is not set # as a constraint
[docs] class QuadraticStrata_class(Strata): r""" Base class for strata of quadratic differentials. """ def _repr_(self): r""" TESTS:: sage: from surface_dynamics import * sage: repr(QuadraticStrata(genus=3)) #indirect doctest 'Quadratic strata of genus 3 surfaces' """ s = self._repr_base_() if self._min_nb_poles != 0 and self._max_nb_poles != Infinity: if self._min_nb_poles == self._max_nb_poles: s += " with %d pole"%(self._min_nb_poles) if self._min_nb_poles != 1: s += 's' else: s += " with at least %d and at most %d poles"%(self._min_nb_poles,self._max_nb_poles) elif self._min_nb_poles != 0: s += " with at least %d pole"%(self._min_nb_poles) if self._min_nb_poles != 1: s += 's' elif self._max_nb_poles == 0: s += " with no pole" elif self._max_nb_poles != Infinity: s += " with at most %d pole"%(self._max_nb_poles) if self._max_nb_poles != 1: s += 's' return s
[docs] class QuadraticStrata_g(QuadraticStrata_class): r""" Stratas of genus g surfaces. EXAMPLES:: sage: from surface_dynamics import * sage: Q = QuadraticStrata(genus=3); Q Quadratic strata of genus 3 surfaces sage: Q.cardinality() +Infinity sage: i = iter(Q) sage: next(i) Q_3(8) sage: Q = QuadraticStrata(genus=2,max_nb_poles=1); Q Quadratic strata of genus 2 surfaces with at most 1 pole sage: Q.list() [Q_2(2^2), Q_2(2, 1^2), Q_2(1^4), Q_2(5, -1), Q_2(4, 1, -1), Q_2(3, 2, -1), Q_2(3, 1^2, -1), Q_2(2^2, 1, -1), Q_2(2, 1^3, -1), Q_2(1^5, -1)] sage: Q = QuadraticStrata(genus=4,nb_poles=3); Q Quadratic strata of genus 4 surfaces with 3 poles sage: Q.cardinality() 176 """ def __init__(self, genus, min_nb_poles=None, max_nb_poles=None): r""" INPUT: - ``genus`` - a non negative integer - ``min_nb_poles``, ``max_nb_poles`` - the number of poles TESTS:: sage: from surface_dynamics import * sage: s = QuadraticStrata(genus=3) sage: loads(dumps(s)) == s True sage: s = QuadraticStrata(genus=3,nb_poles=5) sage: loads(dumps(s)) == s True sage: s = QuadraticStrata(genus=3,min_nb_poles=2,max_nb_poles=6) sage: loads(dumps(s)) == s True """ self._genus = genus self._min_nb_poles = min_nb_poles self._max_nb_poles = max_nb_poles if max_nb_poles != Infinity: Parent.__init__(self, category=FiniteEnumeratedSets()) else: Parent.__init__(self,category=InfiniteEnumeratedSets()) def __eq__(self, other): r""" Equality test. """ r""" Equality test. TESTS:: sage: from surface_dynamics import * sage: QuadraticStrata(genus=1) == QuadraticStrata(genus=1) True sage: QuadraticStrata(genus=1) == QuadraticStrata(genus=2) False sage: QuadraticStrata(genus=1) == QuadraticStrata(genus=1,min_nb_poles=5) False sage: QuadraticStrata(genus=1) == QuadraticStrata(genus=1,max_nb_poles= 3) False """ return (type(self) == type(other) and self._genus == other._genus and self._min_nb_poles == other._min_nb_poles and self._max_nb_poles == other._max_nb_poles) def __ne__(self, other): return not self == other def __reduce__(self): r""" Support for pickling. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStrata(genus=1).__reduce__() (<class 'surface_dynamics.flat_surfaces.quadratic_strata.QuadraticStrata_g'>, (1, 0, +Infinity)) """ return (QuadraticStrata_g,(self._genus,self._min_nb_poles, self._max_nb_poles)) def __contains__(self, c): r""" Containance test TESTS:: sage: from surface_dynamics import * sage: a = QuadraticStrata(genus=3,max_nb_poles=4) sage: all(s in a for s in a) True """ if not isinstance(c, QuadraticStratum): return False return (c.genus() == self._genus and c.nb_poles() >= self._min_nb_poles and c.nb_poles() <= self._max_nb_poles) def _repr_base_(self): r""" Base string for the representation. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStrata(genus=4)._repr_base_() 'Quadratic strata of genus 4 surfaces' """ return "Quadratic strata of genus %d surfaces"%self._genus def __iter__(self): r""" TESTS:: sage: from surface_dynamics import * sage: QuadraticStrata(genus=2, nb_poles=0).list() [Q_2(2^2), Q_2(2, 1^2), Q_2(1^4)] sage: list(QuadraticStrata(genus=0, nb_poles=4)) [Q_0(-1^4)] """ from itertools import count from sage.combinat.partition import Partitions g = self._genus nb_poles = self._min_nb_poles while nb_poles <= self._max_nb_poles: s = 4*g-4+nb_poles if s == 0: if g == 0: yield QuadraticStratum({-1:4}) nb_poles += 1 continue for p in Partitions(s): Q = QuadraticStratum(list(p)+[-1]*nb_poles) if not Q.is_empty(): yield Q nb_poles += 1
[docs] def first(self): r""" Return the first element of this list of strata. EXAMPLES:: sage: from surface_dynamics import * sage: Q = QuadraticStrata(genus=4); Q Quadratic strata of genus 4 surfaces sage: Q.first() Q_4(12) sage: Q = QuadraticStrata(genus=3,nb_poles=1); Q Quadratic strata of genus 3 surfaces with 1 pole sage: Q.first() Q_3(9, -1) sage: Q = QuadraticStrata(genus=3,min_nb_poles=2); Q Quadratic strata of genus 3 surfaces with at least 2 poles sage: Q.first() Q_3(10, -1^2) """ p = self._min_nb_poles g = self._genus if g == 1: # Q(0) and Q(1,-1) are empty if p == 0 or p == 1: raise NotImplementedError("empty list") elif g == 2 and p == 0: # Q(4) and Q(3,1) are empty return QuadraticStratum(2,2) return QuadraticStratum([4*g-4+p]+[-1]*p)
an_element_ = first
[docs] def last(self): r""" Return the last element of this list of strata. EXAMPLES:: sage: from surface_dynamics import * sage: Q = QuadraticStrata(genus=2, nb_poles=0); Q Quadratic strata of genus 2 surfaces with no pole sage: Q.last() Q_2(1^4) sage: Q = QuadraticStrata(genus=0); Q Quadratic strata of genus 0 surfaces sage: Q.last() Traceback (most recent call last): ... NotImplementedError: infinite list TESTS:: sage: from surface_dynamics import * sage: Q = QuadraticStrata(genus=1,nb_poles=2) sage: Q.list()[-1] == Q.last() True sage: Q = QuadraticStrata(genus=1,max_nb_poles=2) sage: Q.list()[-1] == Q.last() True sage: Q = QuadraticStrata(genus=0,nb_poles=10) sage: Q.list()[-1] == Q.last() True """ if self._max_nb_poles == Infinity: raise NotImplementedError("infinite list") p = self._max_nb_poles g = self._genus if g == 0: if p < 4: raise NotImplementedError("empty list") if g == 1: # Q(0) and Q(1,-1) are empty if p == 0 or p == 1: raise NotImplementedError("empty list") return QuadraticStratum([1]*(4*g-4+p) + [-1]*p)
[docs] class QuadraticStrata_d(QuadraticStrata_class): r""" Strata with prescribed dimension. EXAMPLES:: sage: from surface_dynamics import * sage: for q in QuadraticStrata(dimension=5): print(q) Q_3(8) Q_2(2, 1^2) Q_2(4, 1, -1) Q_2(3, 2, -1) Q_2(6, -1^2) Q_1(2, 1, -1^3) Q_1(4, -1^4) Q_0(2, -1^6) sage: Q = QuadraticStrata(dimension=6,nb_poles=1); Q Quadratic strata of dimension 6 with 1 pole sage: for q in Q: print(q) Q_3(9, -1) Q_2(3, 1^2, -1) Q_2(2^2, 1, -1) """ def __init__(self, dimension, min_nb_poles, max_nb_poles, fake_zeros): r""" INPUT: - ``dimension`` - an integer greater than 1 - ``min_nb_poles``, ``max_nb_poles`` - integers - the min or max number of poles - ``fake_zeros`` - boolean - whether fake singularities are allowed TESTS:: sage: from surface_dynamics import * sage: s = QuadraticStrata(dimension=10,fake_zeros=True) sage: loads(dumps(s)) == s True sage: s = QuadraticStrata(dimension=10, min_nb_poles=2, max_nb_poles=5, fake_zeros=True) sage: loads(dumps(s)) == s True """ Parent.__init__(self, category=FiniteEnumeratedSets()) self._dimension = dimension self._min_nb_poles = min_nb_poles self._max_nb_poles = max_nb_poles self._fake_zeros = fake_zeros def __eq__(self, other): r""" Equality test. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStrata(dimension=4) == QuadraticStrata(dimension=4) True sage: QuadraticStrata(dimension=4) == QuadraticStrata(dimension=4, fake_zeros=True) False """ return (type(self) == type(other) and self._dimension == other._dimension and self._min_nb_poles == other._min_nb_poles and self._max_nb_poles == other._max_nb_poles and self._fake_zeros == other._fake_zeros) def __ne__(self, other): return not self == other def __reduce__(self): r""" Support for pickling. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStrata(dimension=12).__reduce__() (<class 'surface_dynamics.flat_surfaces.quadratic_strata.QuadraticStrata_d'>, (12, 0, +Infinity, False)) """ return (QuadraticStrata_d, (self._dimension,self._min_nb_poles,self._max_nb_poles, self._fake_zeros)) def __contains__(self, c): r""" Containment test. TESTS:: sage: from surface_dynamics import * sage: q = QuadraticStrata(dimension=7, fake_zeros=False) sage: QuadraticStratum(5, 2, 1) in q True sage: QuadraticStratum(7, 1, 0) in q False sage: q = QuadraticStrata(dimension=7, fake_zeros=True) sage: QuadraticStratum(5, 2, 1) in q True sage: QuadraticStratum(7, 1, 0) in q True """ return (isinstance(c, QuadraticStratum) and c.dimension() == self._dimension and c.nb_poles() >= self._min_nb_poles and c.nb_poles() <= self._max_nb_poles and (self._fake_zeros or not c.nb_fake_zeros())) z = c.zeros() def _repr_base_(self): r""" Base string for the representation. EXAMPLES:: sage: from surface_dynamics import * sage: QuadraticStrata(dimension=12)._repr_base_() 'Quadratic strata of dimension 12' """ return "Quadratic strata of dimension %d" %(self._dimension) def __iter__(self): r""" Iterator. TESTS:: sage: from surface_dynamics import * sage: for q in QuadraticStrata(dimension=6): print(q) Q_3(7, 1) Q_3(6, 2) Q_3(5, 3) Q_3(4^2) Q_2(1^4) Q_3(9, -1) Q_2(3, 1^2, -1) Q_2(2^2, 1, -1) Q_2(5, 1, -1^2) Q_2(4, 2, -1^2) Q_2(3^2, -1^2) Q_2(7, -1^3) Q_1(1^3, -1^3) Q_1(3, 1, -1^4) Q_1(2^2, -1^4) Q_1(5, -1^5) Q_0(1^2, -1^6) Q_0(3, -1^7) sage: for q in QuadraticStrata(dimension=4, fake_zeros=True): ....: print(q) Q_2(2^2) Q_2(5, -1) Q_1(1^2, -1^2) Q_1(3, -1^3) Q_0(1, -1^5) Q_1(2, 0, -1^2) Q_0(0^2, -1^4) """ if self._fake_zeros: for nb_fake_zeros in range(self._dimension - 1): d = self._dimension - nb_fake_zeros for Q in QuadraticStrata_d(d, self._min_nb_poles, self._max_nb_poles, False): yield QuadraticStratum(Q.zeros() + (0,) * nb_fake_zeros) return from sage.combinat.partition import Partitions d = self._dimension if d == 2: yield QuadraticStratum(-1,-1,-1,-1) else: m = max(0, self._min_nb_poles) M = min(2*d-2, self._max_nb_poles+1) for p in range(m,M): for z in range((d+p)%2,min(d+3-p,(2*d-p)//3+1),2): # d+z+p is 0 mod 2 # 2d-2z-2p >= -4 (or z <= d+2-p) for Z in Partitions(2*d-2*z-p, length=z): Q = QuadraticStratum(Z+[-1]*p) if not Q.is_empty(): yield Q
[docs] class QuadraticStrata_gd(QuadraticStrata_class): r""" Quadratic strata with presrcribed genus and dimension. """ def __init__(self, genus, dimension, min_nb_poles, max_nb_poles, fake_zeros): r""" INPUT: - ``genus`` - an integer - the genus of the surfaces - ``dimension`` - an integer - the dimension of strata - ``min_nb_poles``, ``max_nb_poles`` - integers - minimum and maximum number of poles - ``fake_zeros`` - boolean - whether fake zeros are allowed TESTS:: sage: from surface_dynamics import * sage: s = QuadraticStrata(genus=4, dimension=10) sage: loads(dumps(s)) == s True sage: s = QuadraticStrata(genus=4, dimension=8, min_nb_poles=2, max_nb_poles=3, fake_zeros=True) sage: loads(dumps(s)) == s True """ Parent.__init__(self, category=FiniteEnumeratedSets()) self._genus = genus self._dimension = dimension self._min_nb_poles = min_nb_poles self._max_nb_poles = max_nb_poles self._fake_zeros = fake_zeros def __eq__(self, other): r""" Equality test. """ return (type(self) == type(other) and self._genus == other._genus and self._dimension == other._dimension and self._min_nb_poles == other._min_nb_poles and self._max_nb_poles == other._max_nb_poles and self._fake_zeros == other._fake_zeros) def __ne__(self, other): return not self == other def __reduce__(self): r""" Pickling support. TESTS:: sage: from surface_dynamics import * sage: QuadraticStrata(genus=4,dimension=10).__reduce__() (<class 'surface_dynamics.flat_surfaces.quadratic_strata.QuadraticStrata_gd'>, (4, 10, 0, +Infinity, False)) """ return (QuadraticStrata_gd, (self._genus, self._dimension, self._min_nb_poles, self._max_nb_poles, self._fake_zeros)) def __contains__(self, c): r""" Containance test TESTS:: sage: from surface_dynamics import * sage: Q1 = QuadraticStrata(dimension=7, genus=2, fake_zeros=True) sage: Q2 = QuadraticStrata(dimension=7, genus=2, fake_zeros=False) sage: all(s in Q1 for s in Q1) True sage: all(s in Q2 for s in Q2) True sage: all(s in Q2 for s in Q1) False sage: q = QuadraticStratum({-1:4}) sage: q in QuadraticStrata(genus=0, dimension=2, fake_zeros=False) True sage: q in QuadraticStrata(genus=0, dimension=2, fake_zeros=True) True sage: q = QuadraticStratum({-1:4,0:1}) sage: q in QuadraticStrata(genus=0, dimension=3, fake_zeros=False) False sage: q in QuadraticStrata(genus=0, dimension=3, fake_zeros=True) True sage: Q = QuadraticStrata(dimension=6, genus=1, max_nb_poles=2, fake_zeros=False) sage: all(s in Q for s in Q) True """ return (isinstance(c, QuadraticStratum) and c.dimension() == self._dimension and c.genus() == self._genus and c.nb_poles() >= self._min_nb_poles and c.nb_poles() <= self._max_nb_poles and (self._fake_zeros or not c.nb_fake_zeros())) def _repr_base_(self): r""" TESTS:: sage: from surface_dynamics import * sage: QuadraticStrata(genus=2,dimension=4)._repr_base_() 'Quadratic strata of genus 2 surfaces and dimension 4' """ return "Quadratic strata of genus %d surfaces and dimension %d" %(self._genus, self._dimension) def __iter__(self): r""" Iterator. TESTS:: sage: from surface_dynamics import * sage: for q in QuadraticStrata(genus=1, dimension=6): print(q) Q_1(1^3, -1^3) Q_1(3, 1, -1^4) Q_1(2^2, -1^4) Q_1(5, -1^5) sage: QuadraticStrata(genus=0, dimension=2, nb_poles=5).list() [] sage: QuadraticStrata(genus=0, dimension=4, nb_poles=5).list() [Q_0(1, -1^5)] sage: QuadraticStrata(genus=0, dimension=4, fake_zeros=True).list() [Q_0(1, -1^5), Q_0(0^2, -1^4)] """ if self._fake_zeros: for n in range(self._dimension - 1): for q in QuadraticStrata_gd(self._genus, self._dimension - n, self._min_nb_poles, self._max_nb_poles, False): yield QuadraticStratum(q.zeros() + (0,)*n) return from sage.combinat.partition import Partitions d = self._dimension g = self._genus if d == 2: if g == 0 and self._min_nb_poles <= 4 <= self._max_nb_poles: yield QuadraticStratum(-1,-1,-1,-1) else: m = max(0,self._min_nb_poles) M = min(2*d-2,self._max_nb_poles+1) for p in range(m,M): z = d - p - 2*g + 2 for Z in Partitions(2*d-2*z-p,length=z): Q = QuadraticStratum(Z+[-1]*p) if not Q.is_empty(): yield Q