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 = Stratum([2,2], k=2)
    sage: a
    Q_2(2^2)
    sage: a.surface_genus()
    2

::

    sage: a = Stratum([4,3,2,2,1], k=2)
    sage: a
    Q_4(4, 3, 2^2, 1)
    sage: a.surface_genus()
    4

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

    sage: a1 = Stratum([7,5,3,1], k=2)
    sage: a1
    Q_5(7, 5, 3, 1)
    sage: a2 = Stratum([3,1,7,5], k=2)
    sage: a2
    Q_5(7, 5, 3, 1)
    sage: a1 == a2
    True

List the connected components of a stratum::

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

::

    sage: a = Stratum([12], k=2)
    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 = Stratum([1, 1, 1, 1], k=2)
    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] def DeprecatedQuadraticStratumConstructor(*l, **kwds): r""" TESTS:: sage: from surface_dynamics import * sage: QuadraticStratum(-1,-1,-1,-1) is QuadraticStratum({-1:4}) doctest:warning ... UserWarning: QuadraticStratum has changed its arguments in order to handle meromorphic and higher-order differentials; use Stratum instead True sage: QuadraticStratum(-1,-1,-1,-1) is Stratum((-1,-1,-1,-1), 2) True """ import warnings warnings.warn('QuadraticStratum has changed its arguments in order to handle meromorphic and higher-order differentials; use Stratum instead') 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)), []) if genus is not None: g = sum(l) + 4 if 4 * genus > g: raise ValueError elif 4 * genus < g: l += (-1,) * (g - 4*genus) l = tuple(sorted(map(Integer, l), reverse=True)) return Stratum(l, 2)
[docs] class QuadraticStratum(Stratum): r""" Stratum of quadratic differentials. EXAMPLES:: sage: from surface_dynamics import * sage: Q = Stratum([15,-1,-1,-1], k=2) sage: Q Q_4(15, -1^3) sage: Q.components() (Q_4(15, -1^3)^c,) sage: Q = Stratum([6,6], k=2) sage: 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}"
[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 = Stratum({4:1,-1:4}, k=2) sage: 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 = Stratum({4:1, -1:8}, k=2) sage: q Q_0(4, -1^8) sage: q.dimension() 7 sage: q.orientation_cover().dimension() 7 sage: q = Stratum({3:1, 1:1, -1:8}, k=2) sage: 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: Stratum({-1:4}, k=2).orientation_cover() H_1(0) """ l = [] for z in self.signature(): 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 Stratum(l, k=1)
[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: Stratum([1,3], k=2).spin() 0 sage: Stratum({1:1,3:1,-1:4}, k=2).spin() 1 sage: Stratum([2,2], k=2).spin() is None True """ k = [0,0,0,0] for z in self.signature(): zz = z % 4 if zz == 2: return None k[zz] += 1 return (abs(k[1] - k[3]) // 4) % 2
# TODO: redesign this
[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: Stratum([2,2], k=2).has_hyperelliptic_component() True sage: Stratum([3,1], k=2).has_hyperelliptic_component() False """ return HQSC(self) in self.connected_components() or GTHQSC(self) in self.connected_components()
[docs] def hyperelliptic_component(self): r""" Returns the hyperelliptic component of self (or raise a ValueError). EXAMPLES:: sage: from surface_dynamics import * sage: Stratum([2,2], k=2).hyperelliptic_component() Q_2(2^2)^hyp sage: Stratum([3,1], k=2).hyperelliptic_component() Traceback (most recent call last): ... ValueError: the stratum has no hyperelliptic component """ if HQSC(self) in self.connected_components(): return HQSC(self) if GTHQSC(self) in self.connected_components(): 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: Stratum([10,10], k=2).has_non_hyperelliptic_component() True sage: Stratum([6,6], k=2).has_non_hyperelliptic_component() False """ return NQSC(self) in self.connected_components() or GTNQSC(self) in self.connected_components()
[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: Stratum([10,10], k=2).non_hyperelliptic_component() Q_6(10^2)^nonhyp sage: Stratum([2,2], k=2).non_hyperelliptic_component() Traceback (most recent call last): ... ValueError: no non hyperelliptic component """ ccs = self.connected_components() if NQSC(self) in ccs: return NQSC(self) elif GTNQSC(self) in ccs: 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: Stratum([9,-1], k=2).has_regular_and_irregular_components() True sage: Stratum([11,1], k=2).has_regular_and_irregular_components() False """ return REQSC(self) in self.connected_components()
[docs] def regular_component(self): r""" Returns the regular component of that stratum or raise a ValueError. EXAMPLES:: sage: from surface_dynamics import * sage: Stratum([12], k=2).regular_component() Q_4(12)^reg sage: Stratum([2,2,2,2], k=2).regular_component() Traceback (most recent call last): ... ValueError: no regular component for this stratum """ if REQSC(self) in self.connected_components(): 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: Stratum([3,3,3,-1], k=2).irregular_component() Q_3(3^3, -1)^irr sage: Stratum([2,2], k=2).irregular_component() Traceback (most recent call last): ... ValueError: no irregular component for this stratum """ if IEQSC(self) in self.connected_components(): 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 = Stratum([4,4], k=2) 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 = Stratum({5:1, -1:9}, k=2).unique_component() sage: cc.orientation_cover_component() H_4(6)^hyp sage: cc = Stratum({5:1, -1:5}, k=2).unique_component() sage: cc.orientation_cover_component() H_4(6)^odd sage: cc = Stratum({5:1, -1:1}, k=2).unique_component() sage: cc.orientation_cover_component() H_4(6)^even sage: cc = Stratum({1: 2, -1: 6}, k=2).unique_component() sage: cc.orientation_cover_component() H_3(2^2)^odd sage: cc = Stratum({4:1, -1:8}, k=2).unique_component() sage: cc.orientation_cover_component() H_3(2^2)^hyp sage: cc = Stratum({1:2, -1:2}, k=2).unique_component() sage: cc.orientation_cover_component() H_3(2^2)^hyp """ stratum = self.stratum() zeros = [d for d in stratum.signature() if d > 0] 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.surface_genus() == 0: return astratum.hyperelliptic_component() # 2) other possibility for the hyperelliptic components (low genus # special case for Q(1,1,-1,-1)) nb_poles = self._stratum.signature().count(-1) if zeros == [1,1] and 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 = Stratum({4:1,-1:4}, k=2) sage: 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 = Stratum([6,6], k=2) 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 = Stratum({1:1,-1:5}, k=2) 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 = Stratum([5,-1], k=2) sage: c = Q.unique_component().one_cylinder_diagram() sage: c (0,1,1,2)-(3,0,3,2) sage: c.stratum() == Q True sage: Stratum({-1:4}, k=2).unique_component().one_cylinder_diagram() (0,0)-(1,1) sage: Stratum({-1:4,0:1}, k=2).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().surface_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 = Stratum([3,3,3,-1], k=2).regular_component() sage: R.lyapunov_exponents_H_plus(nb_iterations=2**21) # long time # abs tol .05 [0.596, 0.405, 0.202] sage: sum(_) # long time # abs tol .05 1.2 sage: R = Stratum([2,2,2,2], k=2).unique_component() sage: R.lyapunov_exponents_H_plus(nb_iterations=2**21) # long time # abs tol .05 [0.651, 0.469, 0.243] sage: sum(_) # long time # 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 = Stratum({1:3, -1:3}, k=2).unique_component() sage: Q.lyapunov_exponents_H_minus(nb_iterations=2**21) # long time # abs tol .05 [1.000, 0.369, 0.176] sage: R = Stratum([3,3,3,-1], k=2).regular_component() sage: R.lyapunov_exponents_H_minus(nb_iterations=2**21) # long time # 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: Stratum([-1,-1,-1,-1], k=2).permutation_representative() 0 1 1 2 2 0 sage: Stratum({1:1, -1:5}, k=2).permutation_representative() 0 1 1 2 2 3 3 4 4 0 sage: Stratum({2:1, -1:6}, k=2).permutation_representative() 0 1 1 2 2 3 3 4 4 5 5 0 sage: Stratum({1:2, -1:6}, k=2).permutation_representative() 0 1 1 2 2 3 4 4 5 5 3 6 6 0 sage: Stratum({2:1, 1:1, -1:7}, k=2).permutation_representative() 0 1 1 2 2 3 3 4 5 5 6 6 4 7 7 0 """ p = self._stratum.signature().count(-1) f = self._stratum.signature().count(0) z = tuple(m for m in self._stratum.signature() if m > 0) 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: Stratum({2: 1, -1: 2}, k=2).permutation_representative() 0 1 2 2 1 3 3 0 sage: Stratum({3: 1, -1: 3}, k=2).permutation_representative() 0 1 2 2 3 3 1 4 4 0 sage: Stratum({1: 2, -1: 2}, k=2).permutation_representative() 0 1 2 3 3 2 1 4 4 0 sage: Q = Stratum({2: 1, 1: 1, -1: 3}, k=2) 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: Stratum({1: 1, -1: 1}, k=2).permutation_representative() Traceback (most recent call last): ... EmptySetError: The stratum is empty sage: Q = Stratum([2,-1,-1,0], k=2) 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.signature().count(-1) f = self._stratum.signature().count(0) z = tuple(m for m in self._stratum.signature() if m > 0) 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 = Stratum({1: 4}, k=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 = Stratum([2,1,1], k=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 = Stratum([2,2], k=2) sage: H = Q.hyperelliptic_component() sage: H.permutation_representative() 0 1 2 1 3 4 3 4 2 0 """ p = self._stratum.signature().count(-1) f = self._stratum.signature().count(0) z = tuple(m for m in self._stratum.signature() if m > 0) 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 = Stratum({6: 1, -1: 2}, k=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 = Stratum({3: 2, -1: 2}, k=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.signature().count(-1) f = self._stratum.signature().count(0) z = tuple(m for m in self._stratum.signature() if m > 0) 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 = Stratum({6:1, -1:2}, k=2).hyperelliptic_component() sage: cc.permutation_representative() 0 1 2 3 4 1 5 4 3 2 5 0 sage: cc = Stratum({3: 2, -1: 2}, k=2).hyperelliptic_component() sage: cc.permutation_representative() 0 1 2 3 4 5 1 6 5 4 3 2 6 0 sage: cc = Stratum([10,10], k=2).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.signature().count(-1) f = self._stratum.signature().count(0) z = tuple(m for m in self._stratum.signature() if m != 0) 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 = Stratum([6,-1,-1], k=2).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 = Stratum({3: 2, -1: 2}, k=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 = Stratum([8], k=2).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 = Stratum([4,4], k=2).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 = Stratum({12:1,-1:4}, k=2).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.signature().count(-1) f = self._stratum.signature().count(0) z = tuple(m for m in self._stratum.signature() if m > 0) 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 = Stratum([9,-1], k=2).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 = Stratum([6,3,-1], k=2).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 = Stratum([12], k=2).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 = sum(d < 0 for d in self._stratum.signature()) f = sum(d == 0 for d in self._stratum.signature()) z = [d for d in self._stratum.signature() if d] 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 = Stratum([9,-1], k=2).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 = Stratum([6,3,-1], k=2).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 = Stratum([12], k=2).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 = sum(d < 0 for d in self._stratum.signature()) f = sum(d == 0 for d in self._stratum.signature()) z = [d for d in self._stratum.signature() if d] 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 p = sum(z < 0 for z in c.signature()) return (c.surface_genus() == self._genus and p >= self._min_nb_poles and p <= 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 Stratum({-1:4}, k=2) nb_poles += 1 continue for p in Partitions(s): Q = Stratum(list(p)+[-1]*nb_poles, k=2) 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 Stratum([2,2], k=2) return Stratum([4*g-4+p]+[-1]*p, k=2)
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 Stratum([1]*(4*g-4+p) + [-1]*p, k=2)
[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: Stratum([5, 2, 1], k=2) in q True sage: Stratum([7, 1, 0], k=2) in q False sage: q = QuadraticStrata(dimension=7, fake_zeros=True) sage: Stratum([5, 2, 1], k=2) in q True sage: Stratum([7, 1, 0], k=2) in q True """ if not isinstance(c, Stratum) or c._k != 2: return False p = sum(d < 0 for d in c.signature()) f = sum(d == 0 for d in c.signature()) return (c.dimension() == self._dimension and p >= self._min_nb_poles and p <= self._max_nb_poles and (self._fake_zeros or not f)) 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 Stratum(Q.signature() + (0,) * nb_fake_zeros, k=2) return from sage.combinat.partition import Partitions d = self._dimension if d == 2: yield Stratum([-1,-1,-1,-1], k=2) 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 = Stratum(Z+[-1]*p, k=2) 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 Stratum, QuadraticStrata 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 = Stratum({-1:4}, k=2) 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 = Stratum({-1:4,0:1}, k=2) 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 """ if not isinstance(c, Stratum) or c._k != 2: return False np = sum(z < 0 for z in c.signature()) nf = sum(z == 0 for z in c.signature()) return (c.dimension() == self._dimension and c.surface_genus() == self._genus and np >= self._min_nb_poles and np <= self._max_nb_poles and (self._fake_zeros or not nf)) 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 QuadraticStrata 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 Stratum(q.signature() + (0,)*n, k=2) 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 Stratum((-1,-1,-1,-1), k=2) 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 = Stratum(Z+[-1]*p, k=2) if not Q.is_empty(): yield Q