r"""
Strata of differential on Riemann surfaces
This file gather common code used in
:mod:`~surface_dynamics.flat_surfaces.abelian_strata` and
:mod:`~surface_dynamics.flat_surfaces.quadratic_strata`.
"""
#*****************************************************************************
# Copyright (C) 2009-2019 Vincent Delecroix <20100.delecroix@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, division
from six.moves import range, map, filter, zip
from functools import total_ordering
from sage.structure.unique_representation import UniqueRepresentation
from sage.structure.sage_object import SageObject
from sage.structure.parent import Parent
[docs]
def list_to_exp_list(l):
r"""
Convert list into exponential notation.
EXAMPLES::
sage: from surface_dynamics import *
sage: from surface_dynamics.flat_surfaces.strata import list_to_exp_list
sage: l = [0,0,2,2,3,2,0,0,0]
sage: list_to_exp_list(l)
[(0, 2), (2, 2), (3, 1), (2, 1), (0, 3)]
"""
d = []
i = 0
while i < len(l):
j = i
while j < len(l) and l[j] == l[i]:
j += 1
d.append((l[i],j-i))
i = j
return d
#
# Stratum, stratum component
#
[docs]
class Stratum(UniqueRepresentation, SageObject):
r"""
Generic class for stratum of flat surfaces.
Assumes there are
- a method ``.zeros()`` which returns the list of all zeros
- a method ``.nb_zeros()`` which returns the number of zeros with an option
fake_zeros which could be true or false
- a method ``.nb_fake_zeros()`` which returns the number of fake zeros (or
marked points)
- a method ``.dimension()`` which returns the dimension of the stratum
- an attribute ``._cc`` which is a list of classes associated to the
connected components of self
There may be
- an attribute ``._name`` which corresponds to the beginning of the string
representation (default is the empty string)
- an attribute ``._latex_name`` which corresponds to the beginning of the latex
string representation (uses ``_name`` by default)
TESTS::
sage: from surface_dynamics import *
sage: A = AbelianStratum(2,2)
sage: B = AbelianStratum(1,1)
sage: hash(A) == hash(B)
False
"""
_name = ''
_latex_name = ''
#
# String representation
#
def _flat_zero_str(self):
r"""
String representation of the zeros.
EXAMPLES::
sage: from surface_dynamics import *
sage: a = AbelianStratum({2:3})
sage: a._flat_zero_str()
'2, 2, 2'
"""
return ', '.join(map(str,self.zeros()))
def _exp_zero_str(self):
r"""
String representation with exponential notation
EXAMPLES::
sage: from surface_dynamics import *
sage: a = AbelianStratum(2,2,2)
sage: a._exp_zero_str()
'2^3'
"""
return ', '.join('%d^%d' %(i,e) if e != 1 else '%d' %i for (i,e) in list_to_exp_list(self.zeros()))
# this attribute can be switched between _flat_zero_str and _exp_zero_str
_zero_str = _exp_zero_str
def _repr_(self):
"""
TESTS::
sage: from surface_dynamics import *
sage: repr(AbelianStratum(1,1)) # indirect doctest
'H_2(1^2)'
sage: repr(QuadraticStratum(1,1,1,1)) # indirect doctest
'Q_2(1^4)'
"""
return self._name + "_" + str(self.genus()) + "(" + self._zero_str() + ")"
def _latex_(self):
r"""
Latex string representation
EXAMPLES::
sage: from surface_dynamics import *
sage: AbelianStratum(0)._latex_()
'\\mathcal{H}_1(0)'
sage: QuadraticStratum({-1:4})._latex_()
'\\mathcal{Q}_0(-1^4)'
"""
return self._latex_name + '_' + str(self.genus()) + "(" + self._zero_str() + ")"
#
# Equality and comparisons
#
def __lt__(self, other):
r"""
Comparison
ALGORITHM:
First compare the class, then the dimension and then the list of zeros.
TESTS::
sage: from surface_dynamics import *
sage: AbelianStratum(0) == AbelianStratum(0)
True
sage: QuadraticStratum(5,-1) == QuadraticStratum(5,-1)
True
sage: QuadraticStratum(5,-1) != QuadraticStratum(5,-1)
False
sage: AbelianStratum(12) == QuadraticStratum(12)
False
sage: QuadraticStratum(12,0,0) == QuadraticStratum(12,0)
False
sage: AbelianStratum(2,0) == AbelianStratum(2)
False
sage: AbelianStratum(2,0) != AbelianStratum(2)
True
sage: AbelianStratum(1,1) < AbelianStratum(1,1,0)
True
sage: AbelianStratum(1,1,0) < AbelianStratum(1,1)
False
sage: AbelianStratum(1,1,0) < AbelianStratum(1,1,0,0)
True
sage: AbelianStratum(2) < AbelianStratum(1,1)
True
sage: AbelianStratum(4,0) > AbelianStratum(1,1,1,1)
False
sage: AbelianStratum(4,0,0,0) > AbelianStratum(1,1,1,1)
True
::
sage: QuadraticStratum(2,2) < QuadraticStratum(2,2,0)
True
sage: QuadraticStratum(2,2,0) < QuadraticStratum(2,2)
False
sage: QuadraticStratum(2,2,0) < QuadraticStratum(2,2,0,0)
True
sage: QuadraticStratum(4) < QuadraticStratum(2,2)
True
sage: QuadraticStratum(4,0) > QuadraticStratum(1,1,1,1)
False
sage: Q1 = QuadraticStratum(4,0,0,0)
sage: Q2 = QuadraticStratum(1,1,1,1)
sage: Q1 > Q2
True
sage: Q1 >= Q2
True
sage: Q1 < Q2
False
sage: Q1 <= Q2
False
"""
if not isinstance(self, Stratum) or not isinstance(other, Stratum):
raise TypeError
if type(self) is type(other):
# compare the dimension
if self.dimension() < other.dimension():
return True
elif self.dimension() > other.dimension():
return False
# compare the list of zeros
if self.zeros() < other.zeros():
return True
elif self.zeros() > other.zeros():
return False
# equality
return False
else:
sname = type(self).__name__
oname = type(other).__name__
if sname < oname:
return True
elif sname > oname:
return False
return False
def __ne__(self, other):
return not self == other
def __le__(self, other):
return self == other or self < other
def __ge__(self, other):
return not self < other
def __gt__(self, other):
return not self <= other
#
# Connected components
#
[docs]
def is_connected(self):
r"""
Test if the strata is connected.
EXAMPLES::
sage: from surface_dynamics import *
sage: AbelianStratum([2]).is_connected()
True
sage: AbelianStratum([2,2]).is_connected()
False
sage: QuadraticStratum([-1,-1,-1,-1]).is_connected()
True
sage: QuadraticStratum([12]).is_connected()
False
"""
return len(self._cc) <= 1
[docs]
def permutation_representative(self, *args, **kwds):
r"""
Return a permutation of interval exchanges associated to this stratum.
EXAMPLES::
sage: from surface_dynamics import *
Examples from Abelian differentials::
sage: a = AbelianStratum([3,2,1,0,0])
sage: p = a.permutation_representative()
sage: p.stratum()
H_4(3, 2, 1, 0^2)
sage: a = AbelianStratum([2, 2, 2])
sage: p = a.permutation_representative()
sage: p.stratum()
H_4(2^3)
Examples from quadratic differentials::
sage: a = QuadraticStratum([6,-1,-1])
sage: p = a.permutation_representative()
sage: p.stratum()
Q_2(6, -1^2)
sage: a = QuadraticStratum([-1,-1,-1,-1,0,0])
sage: p = a.permutation_representative()
sage: p.stratum()
Q_0(0^2, -1^4)
"""
return self.one_component().permutation_representative(*args,**kwds)
[docs]
def is_empty(self):
r"""
Return True if the stratum is empty
EXAMPLES::
sage: from surface_dynamics import *
sage: AbelianStratum(2).is_empty()
False
sage: QuadraticStratum(1,-1).is_empty()
True
"""
return len(self._cc) == 0
[docs]
def number_of_components(self):
r"""
Returns the number of connected components of self
EXAMPLES::
sage: from surface_dynamics import *
sage: AbelianStratum(2).number_of_components()
1
sage: AbelianStratum(4).number_of_components()
2
sage: AbelianStratum(3,3).number_of_components()
2
"""
return len(self._cc)
[docs]
def one_component(self):
r"""
Returns a connected component of this stratum.
EXAMPLES::
sage: from surface_dynamics import *
sage: AbelianStratum(2).one_component()
H_2(2)^hyp
"""
if self.components():
return self.components()[-1]
from sage.categories.sets_cat import EmptySetError
raise EmptySetError("The stratum is empty")
[docs]
def unique_component(self):
r"""
Returns the unique component of self or raise a ValueError.
EXAMPLES::
sage: from surface_dynamics import *
sage: a = AbelianStratum(1,1); a
H_2(1^2)
sage: a.unique_component()
H_2(1^2)^hyp
sage: a = AbelianStratum(3,2,1); a
H_4(3, 2, 1)
sage: a.unique_component()
H_4(3, 2, 1)^c
sage: QuadraticStratum({1:1, -1:5}).unique_component()
Q_0(1, -1^5)^c
sage: QuadraticStratum(3,2,-1).unique_component()
Q_2(3, 2, -1)^nonhyp
sage: QuadraticStratum(12).unique_component()
Traceback (most recent call last):
...
ValueError: several components for this stratum
"""
if len(self._cc) != 1:
raise ValueError("several components for this stratum")
return self._cc[0](self)
[docs]
def random_component(self):
r"""
Returns a random connected component of this stratum.
EXAMPLES::
sage: from surface_dynamics import *
sage: Q = QuadraticStratum(6,6)
sage: Q.random_component() # random
Q_4(6^2)^hyp
sage: Q.random_component() # random
Q_4(6^2)^reg
"""
if self.components():
from sage.misc.prandom import choice
return choice(self.components())
from sage.categories.sets_cat import EmptySetError
raise EmptySetError("The stratum is empty")
[docs]
def components(self):
"""
Lists the connected components of the Stratum.
OUTPUT:
list -- a list of connected components of stratum
EXAMPLES::
sage: from surface_dynamics import *
Some abelian strata::
sage: AbelianStratum(0).components()
[H_1(0)^hyp]
sage: AbelianStratum(2).components()
[H_2(2)^hyp]
sage: AbelianStratum(4).components()
[H_3(4)^hyp, H_3(4)^odd]
sage: AbelianStratum(2,2).components()
[H_3(2^2)^hyp, H_3(2^2)^odd]
sage: AbelianStratum(1,1,1,1).components()
[H_3(1^4)^c]
Some quadratic strata::
sage: QuadraticStratum(12).components()
[Q_4(12)^reg, Q_4(12)^irr]
sage: QuadraticStratum(6,-1,-1).components()
[Q_2(6, -1^2)^hyp, Q_2(6, -1^2)^nonhyp]
"""
return list(map(lambda x: x(self), self._cc))
[docs]
def masur_veech_volume(self, rational=False, method=None):
r"""
Return the Masur-Veech volume of this stratum.
INPUT:
- ``rational`` (optional, boolean) - if ``False`` (default) return the Masur-Veech volume
and if ``True`` return the Masur-Veech volume divided by `\zeta(2g)`.
- ``method`` (optional string) - the method to use to compute the volume either, see
:func:`~surface_dynamics.flat_surfaces.masur_veech_volumes.masur_veech_volume`
EXAMPLES::
sage: from surface_dynamics import AbelianStratum
sage: AbelianStratum(2).masur_veech_volume()
1/120*pi^4
sage: AbelianStratum(1,1,1,1).masur_veech_volume()
1/4860*pi^6
sage: AbelianStratum(20).masur_veech_volume()
1604064377302075061983/792184445986404135075840000000000*pi^22
"""
from .masur_veech_volumes import masur_veech_volume
return masur_veech_volume(self, rational, method)
[docs]
class StratumComponent(SageObject):
r"""
Generic class for connected component of a stratum of flat surfaces.
Assumes there are implemented
- a method .permutation_representative()
There may be
- an attribute ._name
- an attribute ._latex_name
"""
_name = ''
_latex_name = ''
def __init__(self, stratum):
r"""
TESTS::
sage: from surface_dynamics import *
sage: a = AbelianStratum(4,4).one_component()
sage: a == loads(dumps(a))
True
sage: q = QuadraticStratum(5,5,-1,-1).one_component()
sage: q == loads(dumps(q))
True
"""
self._stratum = stratum
def __reduce__(self):
r"""
Reduce method for pickling
TESTS::
sage: from surface_dynamics import *
Tests for Abelian strata::
sage: a = AbelianStratum(2,2)
sage: all(loads(dumps(cc)) == cc for cc in a.components())
True
sage: a = AbelianStratum(3,3)
sage: all(loads(dumps(cc)) == cc for cc in a.components())
True
sage: a = AbelianStratum(6)
sage: all(loads(dumps(cc)) == cc for cc in a.components())
True
sage: a = AbelianStratum(1,1,1,1)
sage: all(loads(dumps(cc)) == cc for cc in a.components())
True
Tests for quadratic strata::
sage: q = QuadraticStratum(-1,-1,-1,-1)
sage: all(loads(dumps(cc)) == cc for cc in q.components())
True
sage: q = QuadraticStratum(12)
sage: all(loads(dumps(cc)) == cc for cc in q.components())
True
"""
return (self.__class__, (self._stratum,))
def _repr_(self):
r"""
String representation
EXAMPLES::
sage: from surface_dynamics import *
sage: a_hyp = AbelianStratum(4).hyperelliptic_component()
sage: a_hyp._repr_()
'H_3(4)^hyp'
sage: a_odd = AbelianStratum(4).odd_component()
sage: a_odd._repr_()
'H_3(4)^odd'
"""
return str(self._stratum) + "^" + self._name
def __hash__(self):
r"""
TESTS::
sage: from surface_dynamics import *
sage: A4hyp = AbelianStratum(4).hyperelliptic_component()
sage: A4odd = AbelianStratum(4).odd_component()
sage: hash(A4hyp) != hash(A4odd)
True
sage: A22hyp = AbelianStratum(2,2).hyperelliptic_component()
sage: hash(A22hyp) != hash(A4hyp)
True
"""
return hash(self._stratum) ^ hash(self._name)
[docs]
def stratum(self):
r"""
Return the stratum associated to self
EXAMPLES::
sage: from surface_dynamics import *
sage: a = AbelianStratum(4,4)
sage: all([c.stratum() == a for c in a.components()])
True
"""
return self._stratum
[docs]
def genus(self):
r"""
Return genus of the corresponding stratum
EXAMPLES::
sage: from surface_dynamics import *
sage: a = AbelianStratum(4,4)
sage: a.one_component().genus()
5
"""
return self._stratum.genus()
[docs]
def dimension(self):
r"""
Return the (complex) dimension of this GL(2,R)-invariant orbifold.
EXAMPLES::
sage: from surface_dynamics import AbelianStratum, QuadraticStratum
sage: AbelianStratum(4).odd_component().dimension()
6
sage: QuadraticStratum(12).regular_component().dimension()
7
"""
return self._stratum.dimension()
[docs]
def rank(self):
r"""
Return the rank of this GL(2,R)-invariant orbifold.
EXAMPLES::
sage: from surface_dynamics import AbelianStratum, QuadraticStratum
sage: AbelianStratum(4).odd_component().rank()
3
sage: QuadraticStratum(12).regular_component().rank()
3
"""
return self._stratum.rank()
def __eq__(self,other):
r"""
Equality test
EXAMPLES::
sage: from surface_dynamics import *
sage: c_hyp = AbelianStratum(6).hyperelliptic_component()
sage: c_odd = AbelianStratum(6).odd_component()
sage: c_hyp == c_hyp
True
sage: c_hyp == c_odd
False
"""
if not isinstance(self, StratumComponent) or not isinstance(other, StratumComponent):
return NotImplemented
return type(self) == type(other) and self._stratum == other._stratum
def __lt__(self, other):
r"""
Comparison
TESTS::
sage: from surface_dynamics import *
sage: a1 = AbelianStratum(1,1,1,1)
sage: c1 = a1.components()[0]
sage: a2 = AbelianStratum(3,1)
sage: c2 = a2.components()[0]
sage: c1 == c1
True
sage: c1 == c2
False
sage: a1 = AbelianStratum(1,1,1,1)
sage: c1 = a1.components()[0]
sage: a2 = AbelianStratum(2, 2)
sage: c2_hyp, c2_odd = a2.components()
sage: c1 != c1
False
sage: c1 != c2_hyp
True
sage: c2_hyp != c2_odd
True
"""
if not isinstance(self, StratumComponent) or not isinstance(other, StratumComponent):
return NotImplemented
if self._stratum < other._stratum:
return True
elif self._stratum > other._stratum:
return False
if type(self) < type(other):
return True
elif type(self) > type(other):
return False
[docs]
def masur_veech_volume(self, rational=False, method=None):
r"""
Return the Masur-Veech volume of this stratum component.
INPUT:
- ``rational`` (optional, boolean) - if ``False`` (default) return the Masur-Veech volume
and if ``True`` return the Masur-Veech volume divided by `\zeta(2g)`.
- ``method`` (optional string) - the method to use to compute the volume either, see
:func:`~surface_dynamics.flat_surfaces.masur_veech_volumes.masur_veech_volume`
EXAMPLES::
sage: from surface_dynamics import AbelianStratum
sage: AbelianStratum(4).hyperelliptic_component().masur_veech_volume()
1/6720*pi^6
sage: AbelianStratum(6).even_component().masur_veech_volume()
32/1913625*pi^8
"""
from .masur_veech_volumes import masur_veech_volume
return masur_veech_volume(self, rational, method)
#
# Strata (family of strata)
#
[docs]
class Strata(Parent):
r"""
Strata of Abelian or Quadratic differentials.
"""
pass