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