r"""
Databases for translation surfaces.
This file contain different databases (with different implementations) for
algorithms related to translation surfaces:
- structure of Strebel differentials for quadratic differentials in order to
differentiate
- database of separatrix and cylinder diagrams up to isomorphism
- database of volume of connected components of Abelian strata
"""
#*****************************************************************************
# Copyright (C) 2009 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
import os
import sage.misc.misc
from sage.env import SAGE_SHARE
from . import __path__ as SURFACE_DYNAMICS_DB_PATH
if len(SURFACE_DYNAMICS_DB_PATH) != 1:
raise RuntimeError("problem with setting paths")
SURFACE_DYNAMICS_DB_PATH = os.path.abspath(SURFACE_DYNAMICS_DB_PATH[0])
[docs]
def line_count(filename):
r"""
Returns the number of lines in the file whose name is filename.
"""
from sage.rings.integer import Integer
f = open(filename)
lines = 0
read_f = f.read # loop optimization
buf = read_f(0X100000)
while buf:
lines += buf.count('\n')
buf = read_f(0X100000) # 1024 x 1024
f.close()
return Integer(lines)
[docs]
class GenericRepertoryDatabase:
r"""
Database that consists of a list of files in a repertory.
"""
default_name = "generic_db"
def __init__(self, path=None, read_only=True):
r"""
INPUT:
- ``path`` - string (default is SAGE_TMP) - path to the database. If the
repertory does not exists, it is created.
- ``read_only`` - boolean
"""
if path is None:
try:
path = self.default_path
except AttributeError:
import tempfile
path = tempfile.TemporaryDirectory()
elif not isinstance(path, str):
raise TypeError('path must be a string')
elif not os.path.isdir(path):
if read_only:
raise ValueError("you must set read_only to `False` if the database does not already exist")
try:
os.makedirs(path)
except OSError:
raise ValueError("not able to create the database at {}".format(path))
self.path = path
self.read_only = read_only
def __eq__(self, other):
r"""
TESTS::
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: import tempfile
sage: tmp_dir1 = tempfile.TemporaryDirectory()
sage: tmp_dir2 = tempfile.TemporaryDirectory()
sage: C1 = CylinderDiagrams(tmp_dir1.name, read_only=False)
sage: C2 = CylinderDiagrams(tmp_dir1.name, read_only=False)
sage: C3 = CylinderDiagrams(tmp_dir2.name, read_only=False)
sage: C1 == C1
True
sage: C1 == C2
True
sage: C1 == C3
False
"""
return type(self) is type(other) and other.path == self.path
def __ne__(self, other):
r"""
TESTS::
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: import tempfile
sage: tmp_dir1 = tempfile.TemporaryDirectory()
sage: tmp_dir2 = tempfile.TemporaryDirectory()
sage: C1 = CylinderDiagrams(tmp_dir1.name, read_only=False)
sage: C2 = CylinderDiagrams(tmp_dir1.name, read_only=False)
sage: C3 = CylinderDiagrams(tmp_dir2.name, read_only=False)
sage: C1 != C1
False
sage: C1 != C2
False
sage: C1 != C3
True
"""
return not self == other
[docs]
def clean(self):
r"""
Clean the database.
EXAMPLES::
sage: from surface_dynamics import Stratum
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: import tempfile
sage: tmp_dir = tempfile.TemporaryDirectory()
sage: C = CylinderDiagrams(tmp_dir.name, read_only=False)
sage: C.update(Stratum([4], k=1))
sage: import os
sage: sorted(os.listdir(C.path))
['cyl_diags-4-hyp-1',
'cyl_diags-4-hyp-2',
'cyl_diags-4-hyp-3',
'cyl_diags-4-odd-1',
'cyl_diags-4-odd-2',
'cyl_diags-4-odd-3']
sage: C.clean()
sage: os.listdir(C.path)
[]
"""
assert not self.read_only
for filename in os.listdir(self.path):
os.remove(os.path.join(self.path,filename))
[docs]
class IrregularComponentTwins(GenericRepertoryDatabase):
r"""
Twin data of generalized permutation of irregular components of strata of
Abelian differentials.
"""
default_path = os.path.join(SURFACE_DYNAMICS_DB_PATH, "generalized_permutation_twins")
def __repr__(self):
r"""
String representation
TESTS::
sage: from surface_dynamics.databases.flat_surfaces import IrregularComponentTwins
sage: D = IrregularComponentTwins()
sage: D.__repr__()
'Database of twins of irregular components at ...'
"""
return "Database of twins of irregular components at %s" % self.path
[docs]
def filename(self, stratum):
r"""
Returns the name of the file for the given component.
EXAMPLES::
sage: from surface_dynamics import *
sage: from surface_dynamics.databases.flat_surfaces import IrregularComponentTwins
sage: D = IrregularComponentTwins()
sage: D.filename(Stratum([12], k=2))
'twins-12-irr'
sage: D.filename(Stratum([3,3,3,-1], k=2))
'twins-3_3_3_p-irr'
sage: D.filename(Stratum([2,2], k=2))
Traceback (most recent call last):
...
AssertionError: the stratum has no irregular component
"""
from surface_dynamics.flat_surfaces.quadratic_strata import QuadraticStratum
assert isinstance(stratum, QuadraticStratum)
assert stratum.has_regular_and_irregular_components(), "the stratum has no irregular component"
sig_pos = [z for z in stratum.signature() if z >= 0]
np = sum(z == -1 for z in stratum.signature())
return ('twins-' +
'_'.join(str(z) for z in sig_pos) +
'_p' * np +
'-irr')
[docs]
def has_stratum(self, stratum):
r"""
Test whether the component is in the database.
EXAMPLES::
sage: from surface_dynamics import *
sage: from surface_dynamics.databases.flat_surfaces import IrregularComponentTwins
sage: D = IrregularComponentTwins()
sage: D.has_stratum(Stratum([12], k=2))
True
"""
return os.path.isfile(os.path.join(self.path, self.filename(stratum)))
[docs]
def update(self, stratum):
r"""
Update the database with the irregular component of the given stratum.
The database should not be in read only mode.
"""
assert not self.read_only
from surface_dynamics.interval_exchanges.template import cylindric_canonical
p = stratum.irregular_component().permutation_representative()
res = set()
for q in p.rauzy_diagram(symmetric=True):
if q.is_cylindric():
res.add(cylindric_canonical(q))
res = sorted(res)
filename = os.path.join(self.path, self.filename(stratum))
with open(filename, 'w') as output:
for can in res:
output.write(str(can))
output.write("\n")
[docs]
def list_strata(self):
r"""
Returns the list of components for which the list of twins is stored.
EXAMPLES::
sage: from surface_dynamics.databases.flat_surfaces import IrregularComponentTwins
sage: G = IrregularComponentTwins()
sage: G.list_strata()
[Q_3(9, -1), Q_3(6, 3, -1), Q_4(12), Q_3(3^3, -1), Q_4(6^2), Q_4(9, 3), Q_4(6, 3^2), Q_4(3^4)]
"""
from surface_dynamics.flat_surfaces.quadratic_strata import Stratum
from sage.rings.integer import Integer
s = set()
for f in os.listdir(self.path):
if f.startswith('twins-'):
g = f[6:]
i = g.index('-')
comp = g[:i].replace('p','-1')
s.add(comp)
return sorted(Stratum(list(map(Integer,g.split('_'))), k=2) for g in s)
[docs]
def get(self, stratum):
r"""
Get the list of twins for the stratum of quadratic differentials ``stratum``.
EXAMPLES::
sage: from surface_dynamics import *
sage: from surface_dynamics.databases.flat_surfaces import IrregularComponentTwins
sage: D = IrregularComponentTwins()
sage: l = D.get(Stratum([6,3,-1], k=2))
sage: l[0]
((1, 2, 0, 4, 6, 7, 8, 9, 10, 11, 12, 13, 5, 3),)
sage: len(l)
32
"""
assert self.has_stratum(stratum)
f = open(os.path.join(self.path, self.filename(stratum)))
res = []
s = f.readline()
while s:
res.append(eval(s))
s = f.readline()
return res
[docs]
def count(self, stratum):
r"""
Returns the number of twins for that stratum.
EXAMPLES::
sage: from surface_dynamics import *
sage: from surface_dynamics.databases.flat_surfaces import IrregularComponentTwins
sage: D = IrregularComponentTwins()
sage: Q = Stratum([12], k=2)
sage: len(D.get(Q))
82
sage: D.count(Q)
82
"""
return line_count(os.path.join(self.path, self.filename(stratum)))
[docs]
class CylinderDiagrams(GenericRepertoryDatabase):
r"""
Database of cylinder diagrams.
The database consists of several files with the following name convention:
"stratum-component-ncyls". As an example, the list of 3-cylinder diagrams in
the odd component of H(2,2) is named "2_2-odd-3".
EXAMPLES::
sage: from surface_dynamics import Stratum
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: import os
sage: C = CylinderDiagrams()
sage: a = Stratum([3,1,1,1], k=1).unique_component()
sage: C.filename(a, 2)
'cyl_diags-3_1_1_1-c-2'
sage: os.path.isfile(os.path.join(C.path, C.filename(a, 2)))
True
sage: l = list(C.get_iterator(a, 2))
sage: l[0]
(0,9)-(0,5,7,8,6) (1,6,2,5,3,8,4,7)-(1,2,3,9,4)
sage: l[0].ncyls()
2
sage: l[0].stratum()
H_4(3, 1^3)
"""
default_path = os.path.join(SURFACE_DYNAMICS_DB_PATH, "cylinder_diagrams")
def __repr__(self):
r"""
String representation.
TESTS::
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: C = CylinderDiagrams(read_only=False)
sage: C # indirect doctest
Database of cylinder diagrams at ...
"""
return "Database of cylinder diagrams at %s"%self.path
[docs]
def filename(self, comp, ncyls):
r"""
Returns the name of the file for the given component ``comp`` and the
given of number of cylinders ``ncyls``.
EXAMPLES::
sage: from surface_dynamics import Stratum
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: C = CylinderDiagrams()
sage: C.filename(Stratum([4], k=1).odd_component(), 3)
'cyl_diags-4-odd-3'
sage: C.filename(Stratum([3,3], k=1).hyperelliptic_component(), 2)
'cyl_diags-3_3-hyp-2'
"""
return ('cyl_diags-' +
'_'.join(str(z) for z in comp.stratum().signature()) +
'-' + comp._name +
'-' + str(ncyls))
[docs]
def has_component(self, comp):
r"""
Test whether the database has the component ``comp``.
EXAMPLES::
sage: from surface_dynamics import Stratum
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: import tempfile
sage: tmp_dir = tempfile.TemporaryDirectory()
sage: C = CylinderDiagrams(tmp_dir.name, read_only=False)
sage: C.clean()
sage: a1 = Stratum([4], k=1).odd_component()
sage: a2 = Stratum([1,1,1,1], k=1).unique_component()
sage: C.has_component(a1)
False
sage: C.has_component(a2)
False
sage: C.update(Stratum([4], k=1))
sage: C.has_component(a1)
True
sage: C.has_component(a2)
False
sage: C.has_component(-19)
Traceback (most recent call last):
...
AssertionError: the argument must be a component of stratum of Abelian differentials
"""
from surface_dynamics.flat_surfaces.abelian_strata import AbelianStratumComponent
assert isinstance(comp, AbelianStratumComponent), "the argument must be a component of stratum of Abelian differentials"
return os.path.isfile(os.path.join(self.path,self.filename(comp, 1)))
[docs]
def has_stratum(self, stratum):
r"""
Test whether the database contains the data for a given stratum.
EXAMPLES::
sage: from surface_dynamics import Stratum
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: import os
sage: C = CylinderDiagrams(tmp_dir(), read_only=False)
sage: C.clean()
sage: a1 = Stratum([4], k=1)
sage: a2 = Stratum([1,1,1,1], k=1)
sage: C.has_stratum(a1)
False
sage: C.has_stratum(a2)
False
sage: C.update(Stratum([4], k=1))
sage: C.has_stratum(a1)
True
sage: C.has_stratum(a2)
False
sage: C.has_stratum(1)
Traceback (most recent call last):
...
AssertionError: the argument must be a stratum of Abelian differential
"""
from surface_dynamics.flat_surfaces.abelian_strata import AbelianStratum
assert isinstance(stratum, AbelianStratum), "the argument must be a stratum of Abelian differential"
return self.has_component(stratum.one_component())
def _files(self):
r"""
Iterator over the list of files.
TESTS::
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: C = CylinderDiagrams()
sage: C._files()
<generator object ...>
sage: it = C._files()
sage: next(it) # random
'cyl_diags-...'
sage: next(it) # random
'cyl_diags-...'
"""
for f in os.listdir(self.path):
if f.startswith('cyl_diags-'):
yield f
[docs]
def list_strata(self):
r"""
List available strata in that database.
EXAMPLES::
sage: from surface_dynamics import Stratum
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: import os
sage: C = CylinderDiagrams(tmp_dir(), read_only=False)
sage: C.clean()
sage: C.list_strata()
[]
sage: C.update(Stratum([1,1], k=1))
sage: C.list_strata()
[H_2(1^2)]
sage: C.update(Stratum([2], k=1))
sage: C.list_strata()
[H_2(2), H_2(1^2)]
"""
from surface_dynamics.flat_surfaces.abelian_strata import Stratum
from sage.rings.integer import Integer
s = set()
for f in self._files():
g = f[10:]
s.add(g[:g.index('-')])
return [Stratum(tuple(map(Integer, g.split('_'))), k=1) for g in sorted(s, reverse=True)]
[docs]
def get_iterator(self, comp, ncyls=None):
r"""
Returns an iterator over the list of cylinder diagrams for the component
``comp`` read from the database.
INPUT:
- ``comp`` - a component of stratum
- ``ncyls`` - number of cylinders
EXAMPLES::
sage: from surface_dynamics import Stratum
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: import os
sage: C = CylinderDiagrams(tmp_dir(), read_only=False)
sage: A = Stratum([2], k=1)
sage: a = A.unique_component()
sage: C.update(A)
sage: list(C.get_iterator(a)) == A.cylinder_diagrams()
True
sage: C.clean()
sage: C.get_iterator(a)
Traceback (most recent call last):
...
ValueError: not in the database
"""
from surface_dynamics.flat_surfaces.strata import Stratum, StratumComponent
if not isinstance(comp, StratumComponent):
raise TypeError("comp should be a component of stratum")
if ncyls is None:
from itertools import chain
g = comp.stratum().surface_genus()
s = len(comp.stratum().signature())
return chain(*(self.get_iterator(comp,i) for i in range(1,g+s)))
filename = os.path.join(self.path, self.filename(comp,ncyls))
if not os.path.isfile(filename):
raise ValueError("not in the database")
return self._one_file_iterator(filename)
def _one_file_iterator(self, filename):
f = open(filename)
from surface_dynamics.flat_surfaces.separatrix_diagram import CylinderDiagram
line = f.readline()
while line:
yield CylinderDiagram(line[:-1])
line = f.readline()
f.close()
def _check_symmetries(self, filename):
r"""
TESTS::
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: C = CylinderDiagrams()
sage: C._check_symmetries('cyl_diags-4_2-odd-5') # not tested -- to be fixed
sage: for filename in C._files(): # not tested -- very long time, ~1h
....: C._check_symmetries(filename) # not tested -- very long time, ~1h
"""
filename = os.path.join(self.path, filename)
for c in self._one_file_iterator(filename):
if c != c.canonical_label(inplace=False):
raise ValueError("in file {}, {} does not have canonical labels".format(
filename, c))
c1 = c.horizontal_symmetry()
c1.canonical_label(inplace=True)
if c1 < c:
raise ValueError("in file {}, {} is not minimal... {} is the good one!".format(
filename, c,c1))
c1 = c.vertical_symmetry()
c1.canonical_label(inplace=True)
if c1 < c:
raise ValueError("in file {}, {} is not minimal... {} is the good one!".format(
filename, c,c1))
c1 = c.inverse()
c1.canonical_label(inplace=True)
if c1 < c:
raise ValueError("in file {}, {} is not minimal... {} is the good one!".format(
filename,c,c1))
def _remove_symmetries(self, filename):
r"""
Use this at your own risk! It modifies the file of the database
inplace!!!
"""
filename = os.path.join(self.path, filename)
n = 0
s = set()
garbage = set()
for c in self._one_file_iterator(filename):
n += 1
if c in garbage:
continue
cc = [c]
c1 = c.horizontal_symmetry()
c1.canonical_label(inplace=True)
cc.append(c1)
c1 = c.vertical_symmetry()
c1.canonical_label(inplace=True)
cc.append(c1)
c1 = c.inverse()
c1.canonical_label(inplace=True)
cc.append(c1)
s.add(min(cc))
garbage.update(cc)
print("old cardinality:", n)
print("new cardinality:", len(s))
print("gain :", n-len(s))
f = open(filename, "w")
for c in sorted(s):
f.write(str(c) + "\n")
f.close()
[docs]
def update(self, stratum, verbose=False):
r"""
Compute once for all the given cylinder diagrams of the given
``stratum``.
Warning::
Depending on the dimension of the stratum, it may be very long!
EXAMPLES::
sage: from surface_dynamics import Stratum
sage: from surface_dynamics.databases.flat_surfaces import CylinderDiagrams
sage: C = CylinderDiagrams(tmp_dir(), read_only=False)
sage: C.update(Stratum([4], k=1), verbose=True) # random
computation for H_3(4)
ncyls = 1
1 cyl. diags for H_3(4)^hyp
2 cyl. diags for H_3(4)^odd
ncyls = 2
2 cyl. diags for H_3(4)^hyp
4 cyl. diags for H_3(4)^odd
ncyls = 3
2 cyl. diags for H_3(4)^hyp
4 cyl. diags for H_3(4)^odd
sage: sorted(os.listdir(C.path))
['cyl_diags-4-hyp-1',
'cyl_diags-4-hyp-2',
'cyl_diags-4-hyp-3',
'cyl_diags-4-odd-1',
'cyl_diags-4-odd-2',
'cyl_diags-4-odd-3']
"""
import sys
if verbose:
print("computation for %s"%stratum)
sys.stdout.flush()
for ncyls in range(1, stratum.surface_genus() + len(stratum.signature())):
if verbose:
print(" ncyls = %d"%ncyls)
sys.stdout.flush()
d = stratum.cylinder_diagrams_by_component(ncyls, force_computation = True)
for c in d:
if verbose:
print(" %d cyl. diags for %s"%(len(d[c]),c))
f = open(os.path.join(self.path,self.filename(c,ncyls)), "w")
for cyl in sorted(d[c]):
f.write(str(cyl) + "\n")
f.close()
[docs]
def count(self, comp, ncyls=None):
r"""
Returns the number of cylinder diagrams for a stratum or a component of
stratum with given number of cylinders.
"""
from surface_dynamics.flat_surfaces.abelian_strata import AbelianStratum
if isinstance(comp, AbelianStratum):
return sum(self.count(cc, ncyls) for cc in comp.components())
if ncyls is None:
g = comp.stratum().surface_genus()
s = len(comp.stratum().signature())
return sum((self.count(comp,i) for i in range(1,g+s)))
return line_count(os.path.join(self.path, self.filename(comp, ncyls)))