Esempio n. 1
0
    def ambient_vector_space(self):
        """
        Return the ambient vector space.

        .. SEEALSO::

            :meth:`ambient_module`

        OUTPUT:

        The vector space (over the fraction field of the base ring)
        where the linear expressions live.

        EXAMPLES::

            sage: from sage.geometry.linear_expression import LinearExpressionModule
            sage: L = LinearExpressionModule(QQ, ('x', 'y', 'z'))
            sage: L.ambient_vector_space()
            Vector space of dimension 3 over Rational Field
            sage: M = LinearExpressionModule(ZZ, ('r', 's'))
            sage: M.ambient_module()
            Ambient free module of rank 2 over the principal ideal domain Integer Ring
            sage: M.ambient_vector_space()
            Vector space of dimension 2 over Rational Field
        """
        from sage.modules.all import VectorSpace
        field = self.base_ring().fraction_field()
        return VectorSpace(field, self.ngens())
Esempio n. 2
0
    def fiber(self):
        r"""
        Return the generic fiber of the vector bundle.

        OUTPUT:

        A vector space over :meth:`base_ring`.

        EXAMPLES::

            sage: T_P2 = toric_varieties.P2().sheaves.tangent_bundle()
            sage: T_P2.fiber()
            Vector space of dimension 2 over Rational Field
        """
        from sage.modules.all import VectorSpace
        return VectorSpace(self.base_ring(), self.rank())
Esempio n. 3
0
    def vertices(self):
        """
        EXAMPLES::

            sage: P = polymake.convex_hull([[1,0,0,0], [1,0,0,1], [1,0,1,0], [1,0,1,1],  [1,1,0,0], [1,1,0,1], [1,1,1,0], [1,1,1,1]])     # optional - polymake
            sage: P.vertices()                            # optional - polymake
            [(1, 0, 0, 0), (1, 0, 0, 1), (1, 0, 1, 0), (1, 0, 1, 1), (1, 1, 0, 0), (1, 1, 0, 1), (1, 1, 1, 0), (1, 1, 1, 1)]
        """
        try:
            return self.__vertices
        except AttributeError:
            pass
        s = self.cmd('VERTICES')
        s = s.rstrip().split('\n')[1:]
        if len(s) == 0:
            ans = Sequence([], immutable=True)
        else:
            n = len(s[0].split())
            V = VectorSpace(QQ, n)
            ans = Sequence((V(x.split()) for x in s), immutable=True)
        self.__vertices = ans
        return ans
Esempio n. 4
0
 def facets(self):
     """
     EXAMPLES::
     
         sage: P = polymake.convex_hull([[1,0,0,0], [1,0,0,1], [1,0,1,0], [1,0,1,1],  [1,1,0,0], [1,1,0,1], [1,1,1,0], [1,1,1,1]])   # optional: needs polymake
         sage: P.facets()                            # optional
         [(0, 0, 0, 1), (0, 1, 0, 0), (0, 0, 1, 0), (1, 0, 0, -1), (1, 0, -1, 0), (1, -1, 0, 0)]
     """
     try:
         return self.__facets
     except AttributeError:
         pass
     s = self.cmd('FACETS')
     s = s.rstrip().split('\n')[1:]
     if len(s) == 0:
         ans = Sequence([], immutable=True)
     else:
         n = len(s[0].split())
         V = VectorSpace(QQ, n)
         ans = Sequence((V(x.split()) for x in s), immutable=True)
     self.__facets = ans
     return ans
    def right_angle_triangle(w, h):
        r"""
        TESTS::

            sage: from flatsurf import *
            sage: R = similarity_surfaces.right_angle_triangle(2, 3)
            sage: R
            ConeSurface built from 2 polygons
            sage: TestSuite(R).run()
        """
        from sage.modules.free_module_element import vector

        F = Sequence([w, h]).universe()

        if not F.is_field():
            F = F.fraction_field()
        V = VectorSpace(F, 2)
        P = ConvexPolygons(F)
        s = Surface_list(base_ring=F)
        s.add_polygon(P([V((w, 0)), V((-w, h)), V((0, -h))]))  # gets label 0
        s.add_polygon(P([V((0, h)), V((-w, -h)), V((w, 0))]))  # gets label 1
        s.change_polygon_gluings(0, [(1, 2), (1, 1), (1, 0)])
        s.set_immutable()
        return ConeSurface(s)
Esempio n. 6
0
    def __call__(self, program, complex, subcomplex=None, **kwds):
        """
        Call a CHomP program to compute the homology of a chain
        complex, simplicial complex, or cubical complex.

        See :class:`CHomP` for full documentation.

        EXAMPLES::

            sage: from sage.interfaces.chomp import CHomP
            sage: T = cubical_complexes.Torus()
            sage: CHomP()('homcubes', T) # indirect doctest, optional - CHomP
            {0: 0, 1: Z x Z, 2: Z}
        """
        from sage.misc.temporary_file import tmp_filename
        from sage.homology.all import CubicalComplex, cubical_complexes
        from sage.homology.all import SimplicialComplex, Simplex
        from sage.homology.chain_complex import HomologyGroup
        from subprocess import Popen, PIPE
        from sage.rings.all import QQ, ZZ
        from sage.modules.all import VectorSpace, vector
        from sage.combinat.free_module import CombinatorialFreeModule

        if not have_chomp(program):
            raise OSError("Program %s not found" % program)

        verbose = kwds.get('verbose', False)
        generators = kwds.get('generators', False)
        extra_opts = kwds.get('extra_opts', '')
        base_ring = kwds.get('base_ring', ZZ)

        if extra_opts:
            extra_opts = extra_opts.split()
        else:
            extra_opts = []

        # type of complex:
        cubical = False
        simplicial = False
        chain = False
        # CHomP seems to have problems with cubical complexes if the
        # first interval in the first cube defining the complex is
        # degenerate.  So replace the complex X with [0,1] x X.
        if isinstance(complex, CubicalComplex):
            cubical = True
            edge = cubical_complexes.Cube(1)
            original_complex = complex
            complex = edge.product(complex)
            if verbose:
                print("Cubical complex")
        elif isinstance(complex, SimplicialComplex):
            simplicial = True
            if verbose:
                print("Simplicial complex")
        else:
            chain = True
            base_ring = kwds.get('base_ring', complex.base_ring())
            if verbose:
                print("Chain complex over %s" % base_ring)

        if base_ring == QQ:
            raise ValueError(
                "CHomP doesn't compute over the rationals, only over Z or F_p."
            )
        if base_ring.is_prime_field():
            p = base_ring.characteristic()
            extra_opts.append('-p%s' % p)
            mod_p = True
        else:
            mod_p = False

        #
        #    complex
        #
        try:
            data = complex._chomp_repr_()
        except AttributeError:
            raise AttributeError(
                "Complex cannot be converted to use with CHomP.")

        datafile = tmp_filename()
        with open(datafile, 'w') as f:
            f.write(data)

        #
        #    subcomplex
        #
        if subcomplex is None:
            if cubical:
                subcomplex = CubicalComplex([complex.n_cells(0)[0]])
            elif simplicial:
                m = re.search(r'\(([^,]*),', data)
                v = int(m.group(1))
                subcomplex = SimplicialComplex([[v]])
        else:
            # replace subcomplex with [0,1] x subcomplex.
            if cubical:
                subcomplex = edge.product(subcomplex)
        #
        #    generators
        #
        if generators:
            genfile = tmp_filename()
            extra_opts.append('-g%s' % genfile)

        #
        #    call program
        #
        if subcomplex is not None:
            try:
                sub = subcomplex._chomp_repr_()
            except AttributeError:
                raise AttributeError(
                    "Subcomplex cannot be converted to use with CHomP.")
            subfile = tmp_filename()
            with open(subfile, 'w') as f:
                f.write(sub)
        else:
            subfile = ''
        if verbose:
            print("Popen called with arguments", end="")
            print([program, datafile, subfile] + extra_opts)
            print("")
            print("CHomP output:")
            print("")
        # output = Popen([program, datafile, subfile, extra_opts],
        cmd = [program, datafile]
        if subfile:
            cmd.append(subfile)
        if extra_opts:
            cmd.extend(extra_opts)
        output = Popen(cmd, stdout=PIPE).communicate()[0]
        if verbose:
            print(output)
            print("End of CHomP output")
            print("")
        if generators:
            with open(genfile, 'r') as f:
                gens = f.read()
            if verbose:
                print("Generators:")
                print(gens)
        #
        #    process output
        #
        if output.find('ERROR') != -1:
            raise RuntimeError('error inside CHomP')
        # output contains substrings of one of the forms
        # "H_1 = Z", "H_1 = Z_2 + Z", "H_1 = Z_2 + Z^2",
        # "H_1 = Z + Z_2 + Z"
        if output.find('trivial') != -1:
            if mod_p:
                return {0: VectorSpace(base_ring, 0)}
            else:
                return {0: HomologyGroup(0, ZZ)}
        d = {}
        h = re.compile("^H_([0-9]*) = (.*)$", re.M)
        tors = re.compile("Z_([0-9]*)")
        #
        #    homology groups
        #
        for m in h.finditer(output):
            if verbose:
                print(m.groups())
            # dim is the dimension of the homology group
            dim = int(m.group(1))
            # hom_str is the right side of the equation "H_n = Z^r + Z_k + ..."
            hom_str = m.group(2)
            # need to read off number of summands and their invariants
            if hom_str.find("0") == 0:
                if mod_p:
                    hom = VectorSpace(base_ring, 0)
                else:
                    hom = HomologyGroup(0, ZZ)
            else:
                rk = 0
                if hom_str.find("^") != -1:
                    rk_srch = re.search(r'\^([0-9]*)\s?', hom_str)
                    rk = int(rk_srch.group(1))
                rk += len(re.findall(r"(Z$)|(Z\s)", hom_str))
                if mod_p:
                    rk = rk if rk != 0 else 1
                    if verbose:
                        print("dimension = %s, rank of homology = %s" %
                              (dim, rk))
                    hom = VectorSpace(base_ring, rk)
                else:
                    n = rk
                    invts = []
                    for t in tors.finditer(hom_str):
                        n += 1
                        invts.append(int(t.group(1)))
                    for i in range(rk):
                        invts.append(0)
                    if verbose:
                        print(
                            "dimension = %s, number of factors = %s, invariants = %s"
                            % (dim, n, invts))
                    hom = HomologyGroup(n, ZZ, invts)

            #
            #    generators
            #
            if generators:
                if cubical:
                    g = process_generators_cubical(gens, dim)
                    if verbose:
                        print("raw generators: %s" % g)
                    if g:
                        module = CombinatorialFreeModule(
                            base_ring,
                            original_complex.n_cells(dim),
                            prefix="",
                            bracket=True)
                        basis = module.basis()
                        output = []
                        for x in g:
                            v = module(0)
                            for term in x:
                                v += term[0] * basis[term[1]]
                            output.append(v)
                        g = output
                elif simplicial:
                    g = process_generators_simplicial(gens, dim, complex)
                    if verbose:
                        print("raw generators: %s" % gens)
                    if g:
                        module = CombinatorialFreeModule(base_ring,
                                                         complex.n_cells(dim),
                                                         prefix="",
                                                         bracket=False)
                        basis = module.basis()
                        output = []
                        for x in g:
                            v = module(0)
                            for term in x:
                                if complex._is_numeric():
                                    v += term[0] * basis[term[1]]
                                else:
                                    translate = complex._translation_from_numeric(
                                    )
                                    simplex = Simplex(
                                        [translate[a] for a in term[1]])
                                    v += term[0] * basis[simplex]
                            output.append(v)
                        g = output
                elif chain:
                    g = process_generators_chain(gens, dim, base_ring)
                    if verbose:
                        print("raw generators: %s" % gens)
                if g:
                    if not mod_p:
                        # sort generators to match up with corresponding invariant
                        g = [
                            _[1]
                            for _ in sorted(zip(invts, g), key=lambda x: x[0])
                        ]
                    d[dim] = (hom, g)
                else:
                    d[dim] = hom
            else:
                d[dim] = hom

        if chain:
            new_d = {}
            diff = complex.differential()
            if len(diff) == 0:
                return {}
            bottom = min(diff)
            top = max(diff)
            for dim in d:
                if complex._degree_of_differential == -1:  # chain complex
                    new_dim = bottom + dim
                else:  # cochain complex
                    new_dim = top - dim
                if isinstance(d[dim], tuple):
                    # generators included.
                    group = d[dim][0]
                    gens = d[dim][1]
                    new_gens = []
                    dimension = complex.differential(new_dim).ncols()
                    # make sure that each vector is embedded in the
                    # correct ambient space: pad with a zero if
                    # necessary.
                    for v in gens:
                        v_dict = v.dict()
                        if dimension - 1 not in v.dict():
                            v_dict[dimension - 1] = 0
                            new_gens.append(vector(base_ring, v_dict))
                        else:
                            new_gens.append(v)
                    new_d[new_dim] = (group, new_gens)
                else:
                    new_d[new_dim] = d[dim]
            d = new_d
        return d
Esempio n. 7
0
    def homology(self, dim=None, **kwds):
        r"""
        The reduced homology of this cell complex.

        :param dim: If None, then return the homology in every
           dimension.  If ``dim`` is an integer or list, return the
           homology in the given dimensions.  (Actually, if ``dim`` is
           a list, return the homology in the range from ``min(dim)``
           to ``max(dim)``.)
        :type dim: integer or list of integers or None; optional,
           default None
        :param base_ring: commutative ring, must be ZZ or a field.
        :type base_ring: optional, default ZZ
        :param subcomplex: a subcomplex of this simplicial complex.
           Compute homology relative to this subcomplex.
        :type subcomplex: optional, default empty
        :param generators: If ``True``, return generators for the homology
           groups along with the groups.  NOTE: Since :trac:`6100`, the result
           may not be what you expect when not using CHomP since its return
           is in terms of the chain complex.
        :type generators: boolean; optional, default False
        :param cohomology: If True, compute cohomology rather than homology.
        :type cohomology: boolean; optional, default False
        :param algorithm: The options are 'auto', 'dhsw', 'pari' or 'no_chomp'.
           See below for a description of what they mean.
        :type algorithm: string; optional, default 'auto'
        :param verbose: If True, print some messages as the homology is
           computed.
        :type verbose: boolean; optional, default False

        .. note::

            The keyword arguments to this function get passed on to
            :meth:``chain_complex`` and its homology.

        ALGORITHM:

        If ``algorithm`` is set to 'auto' (the default), then use
        CHomP if available.  (CHomP is available at the web page
        http://chomp.rutgers.edu/.  It is also an experimental package
        for Sage.)

        CHomP computes homology, not cohomology, and only works over
        the integers or finite prime fields.  Therefore if any of
        these conditions fails, or if CHomP is not present, or if
        ``algorithm`` is set to 'no_chomp', go to plan B: if ``self``
        has a ``_homology`` method -- each simplicial complex has
        this, for example -- then call that.  Such a method implements
        specialized algorithms for the particular type of cell
        complex.

        Otherwise, move on to plan C: compute the chain complex of
        ``self`` and compute its homology groups.  To do this: over a
        field, just compute ranks and nullities, thus obtaining
        dimensions of the homology groups as vector spaces.  Over the
        integers, compute Smith normal form of the boundary matrices
        defining the chain complex according to the value of
        ``algorithm``.  If ``algorithm`` is 'auto' or 'no_chomp', then
        for each relatively small matrix, use the standard Sage
        method, which calls the Pari package.  For any large matrix,
        reduce it using the Dumas, Heckenbach, Saunders, and Welker
        elimination algorithm: see
        :func:`sage.homology.chain_complex.dhsw_snf` for details.

        Finally, ``algorithm`` may also be 'pari' or 'dhsw', which
        forces the named algorithm to be used regardless of the size
        of the matrices and regardless of whether CHomP is available.

        As of this writing, CHomP is by far the fastest option,
        followed by the 'auto' or 'no_chomp' setting of using the
        Dumas, Heckenbach, Saunders, and Welker elimination algorithm
        for large matrices and Pari for small ones.

        EXAMPLES::

            sage: P = delta_complexes.RealProjectivePlane()
            sage: P.homology()
            {0: 0, 1: C2, 2: 0}
            sage: P.homology(base_ring=GF(2))
            {0: Vector space of dimension 0 over Finite Field of size 2,
             1: Vector space of dimension 1 over Finite Field of size 2,
             2: Vector space of dimension 1 over Finite Field of size 2}
            sage: S7 = delta_complexes.Sphere(7)
            sage: S7.homology(7)
            Z
            sage: cubical_complexes.KleinBottle().homology(1, base_ring=GF(2))
            Vector space of dimension 2 over Finite Field of size 2

        If CHomP is installed, Sage can compute generators of homology
        groups::

            sage: S2 = simplicial_complexes.Sphere(2)
            sage: S2.homology(dim=2, generators=True, base_ring=GF(2))  # optional - CHomP
            (Vector space of dimension 1 over Finite Field of size 2, [(0, 1, 2) + (0, 1, 3) + (0, 2, 3) + (1, 2, 3)])

        When generators are computed, Sage returns a pair for each
        dimension: the group and the list of generators.  For
        simplicial complexes, each generator is represented as a
        linear combination of simplices, as above, and for cubical
        complexes, each generator is a linear combination of cubes::

            sage: S2_cub = cubical_complexes.Sphere(2)
            sage: S2_cub.homology(dim=2, generators=True)  # optional - CHomP
            (Z, [-[[0,1] x [0,1] x [0,0]] + [[0,1] x [0,1] x [1,1]] - [[0,0] x [0,1] x [0,1]] - [[0,1] x [1,1] x [0,1]] + [[0,1] x [0,0] x [0,1]] + [[1,1] x [0,1] x [0,1]]])
        """
        from sage.interfaces.chomp import have_chomp, homcubes, homsimpl
        from sage.homology.cubical_complex import CubicalComplex
        from sage.homology.simplicial_complex import SimplicialComplex
        from sage.modules.all import VectorSpace
        from sage.homology.chain_complex import HomologyGroup

        base_ring = kwds.get('base_ring', ZZ)
        cohomology = kwds.get('cohomology', False)
        subcomplex = kwds.get('subcomplex', None)
        verbose = kwds.get('verbose', False)
        algorithm = kwds.get('algorithm', 'auto')

        if dim is not None:
            if isinstance(dim, (list, tuple)):
                low = min(dim) - 1
                high = max(dim) + 2
            else:
                low = dim - 1
                high = dim + 2
            dims = range(low, high)
        else:
            dims = None

        # try to use CHomP if computing homology (not cohomology) and
        # working over Z or F_p, p a prime.
        if (algorithm == 'auto' and cohomology is False
                and (base_ring == ZZ or
                     (base_ring.is_prime_field() and base_ring != QQ))):
            # homcubes, homsimpl seems fastest if all of homology is computed.
            H = None
            if isinstance(self, CubicalComplex):
                if have_chomp('homcubes'):
                    if 'subcomplex' in kwds:
                        del kwds['subcomplex']
                    H = homcubes(self, subcomplex, **kwds)
            elif isinstance(self, SimplicialComplex):
                if have_chomp('homsimpl'):
                    if 'subcomplex' in kwds:
                        del kwds['subcomplex']
                    H = homsimpl(self, subcomplex, **kwds)
            # now pick off the requested dimensions
            if H:
                answer = {}
                if not dims:
                    for d in range(self.dimension() + 1):
                        if base_ring == ZZ:
                            answer[d] = H.get(d, HomologyGroup(0))
                        else:
                            answer[d] = H.get(d, VectorSpace(base_ring, 0))
                else:
                    for d in dims:
                        if base_ring == ZZ:
                            answer[d] = H.get(d, HomologyGroup(0))
                        else:
                            answer[d] = H.get(d, VectorSpace(base_ring, 0))
                if dim is not None:
                    if not isinstance(dim, (list, tuple)):
                        if base_ring == ZZ:
                            answer = answer.get(dim, HomologyGroup(0))
                        else:
                            answer = answer.get(dim, VectorSpace(base_ring, 0))
                return answer

        # Derived classes can implement specialized algorithms using a
        # _homology_ method.  See SimplicialComplex for one example.
        if hasattr(self, '_homology_'):
            return self._homology_(dim, **kwds)

        C = self.chain_complex(cochain=cohomology,
                               augmented=True,
                               dimensions=dims,
                               **kwds)
        if 'subcomplex' in kwds:
            del kwds['subcomplex']
        answer = C.homology(**kwds)
        if isinstance(answer, dict):
            if cohomology:
                too_big = self.dimension() + 1
                if (not ((isinstance(dim, (list, tuple)) and too_big in dim)
                         or too_big == dim) and too_big in answer):
                    del answer[too_big]
            if -2 in answer:
                del answer[-2]
            if -1 in answer:
                del answer[-1]
            for d in range(self.dimension() + 1):
                if d not in answer:
                    if base_ring == ZZ:
                        answer[d] = HomologyGroup(0)
                    else:
                        answer[d] = VectorSpace(base_ring, 0)

            if dim is not None:
                if isinstance(dim, (list, tuple)):
                    temp = {}
                    for n in dim:
                        temp[n] = answer[n]
                    answer = temp
                else:  # just a single dimension
                    if base_ring == ZZ:
                        answer = answer.get(dim, HomologyGroup(0))
                    else:
                        answer = answer.get(dim, VectorSpace(base_ring, 0))
        return answer
    def from_flipper(h):
        r"""
        Build a (half-)translation surface from a flipper pseudo-Anosov.

        EXAMPLES::

            sage: from flatsurf import *
            sage: import flipper                             # optional - flipper

        A torus example::

            sage: t1 = (0r,1r,2r)                            # optional - flipper
            sage: t2 = (~0r,~1r,~2r)                         # optional - flipper
            sage: T = flipper.create_triangulation([t1,t2])  # optional - flipper
            sage: L1 = T.lamination([1r,0r,1r])              # optional - flipper
            sage: L2 = T.lamination([0r,1r,1r])              # optional - flipper
            sage: h1 = L1.encode_twist()                     # optional - flipper
            sage: h2 = L2.encode_twist()                     # optional - flipper
            sage: h = h1*h2^(-1r)                            # optional - flipper
            sage: f = h.flat_structure()                     # optional - flipper
            sage: ts = translation_surfaces.from_flipper(h)  # optional - flipper
            sage: ts                                         # optional - flipper
            HalfTranslationSurface built from 2 polygons
            sage: TestSuite(ts).run()                        # optional - flipper

        A non-orientable example::

            sage: T = flipper.load('SB_4')                   # optional - flipper
            sage: h = T.mapping_class('s_0S_1s_2S_3s_1S_2')  # optional - flipper
            sage: h.is_pseudo_anosov()                       # optional - flipper
            True
            sage: S = translation_surfaces.from_flipper(h)   # optional - flipper
            sage: TestSuite(S).run()                         # optional - flipper
            sage: S.num_polygons()                           # optional - flipper
            4
            sage: from flatsurf.geometry.similarity_surface_generators import flipper_nf_element_to_sage
            sage: a = flipper_nf_element_to_sage(h.dilatation())  # optional - flipper
        """
        from .surface import surface_list_from_polygons_and_gluings

        f = h.flat_structure()

        x = next(itervalues(f.edge_vectors)).x
        K = flipper_nf_to_sage(x.field)
        V = VectorSpace(K, 2)
        edge_vectors = {
            i: V((flipper_nf_element_to_sage(e.x, K),
                  flipper_nf_element_to_sage(e.y, K)))
            for i, e in iteritems(f.edge_vectors)
        }

        to_polygon_number = {
            k: (i, j)
            for i, t in enumerate(f.triangulation) for j, k in enumerate(t)
        }

        C = ConvexPolygons(K)

        polys = []
        adjacencies = {}
        for i, t in enumerate(f.triangulation):
            for j, k in enumerate(t):
                adjacencies[(i, j)] = to_polygon_number[~k]
            try:
                poly = C([edge_vectors[i] for i in tuple(t)])
            except ValueError:
                raise ValueError("t = {}, edges = {}".format(
                    t, [edge_vectors[i].n(digits=6) for i in t]))
            polys.append(poly)

        return HalfTranslationSurface(
            surface_list_from_polygons_and_gluings(polys, adjacencies))
    def arnoux_yoccoz(genus):
        r"""
        Construct the Arnoux-Yoccoz surface of genus 3 or greater.

        This presentation of the surface follows Section 2.3 of
        Joshua P. Bowman's paper "The Complete Family of Arnoux-Yoccoz
        Surfaces."

        EXAMPLES::

            sage: from flatsurf import *
            sage: s = translation_surfaces.arnoux_yoccoz(4)
            sage: TestSuite(s).run()
            sage: s.is_delaunay_decomposed()
            True
            sage: s = s.canonicalize()
            sage: field=s.base_ring()
            sage: a = field.gen()
            sage: from sage.matrix.constructor import Matrix
            sage: m = Matrix([[a,0],[0,~a]])
            sage: ss = m*s
            sage: ss = ss.canonicalize()
            sage: s.cmp(ss) == 0
            True

        The Arnoux-Yoccoz pseudo-Anosov are known to have (minimal) invariant
        foliations with SAF=0::

            sage: S3 = translation_surfaces.arnoux_yoccoz(3)
            sage: Jxx, Jyy, Jxy = S3.j_invariant()
            sage: Jxx.is_zero() and Jyy.is_zero()
            True
            sage: Jxy
            [ 0  2  0]
            [ 2 -2  0]
            [ 0  0  2]

            sage: S4 = translation_surfaces.arnoux_yoccoz(4)
            sage: Jxx, Jyy, Jxy = S4.j_invariant()
            sage: Jxx.is_zero() and Jyy.is_zero()
            True
            sage: Jxy
            [ 0  2  0  0]
            [ 2 -2  0  0]
            [ 0  0  2  2]
            [ 0  0  2  0]
        """
        g = ZZ(genus)
        assert g >= 3
        x = polygen(AA)
        p = sum([x**i for i in range(1, g + 1)]) - 1
        cp = AA.common_polynomial(p)
        alpha_AA = AA.polynomial_root(cp, RIF(1 / 2, 1))
        field = NumberField(alpha_AA.minpoly(), 'alpha', embedding=alpha_AA)
        a = field.gen()
        V = VectorSpace(field, 2)
        p = [None for i in range(g + 1)]
        q = [None for i in range(g + 1)]
        p[0] = V(((1 - a**g) / 2, a**2 / (1 - a)))
        q[0] = V((-a**g / 2, a))
        p[1] = V((-(a**(g - 1) + a**g) / 2, (a - a**2 + a**3) / (1 - a)))
        p[g] = V((1 + (a - a**g) / 2, (3 * a - 1 - a**2) / (1 - a)))
        for i in range(2, g):
            p[i] = V(((a - a**i) / (1 - a), a / (1 - a)))
        for i in range(1, g + 1):
            q[i] = V(((2 * a - a**i - a**(i + 1)) / (2 * (1 - a)),
                      (a - a**(g - i + 2)) / (1 - a)))
        P = ConvexPolygons(field)
        s = Surface_list(field)
        T = [None] * (2 * g + 1)
        Tp = [None] * (2 * g + 1)
        from sage.matrix.constructor import Matrix
        m = Matrix([[1, 0], [0, -1]])
        for i in range(1, g + 1):
            # T_i is (P_0,Q_i,Q_{i-1})
            T[i] = s.add_polygon(
                P(edges=[q[i] - p[0], q[i - 1] - q[i], p[0] - q[i - 1]]))
            # T_{g+i} is (P_i,Q_{i-1},Q_{i})
            T[g + i] = s.add_polygon(
                P(edges=[q[i - 1] - p[i], q[i] - q[i - 1], p[i] - q[i]]))
            # T'_i is (P'_0,Q'_{i-1},Q'_i)
            Tp[i] = s.add_polygon(m * s.polygon(T[i]))
            # T'_{g+i} is (P'_i,Q'_i, Q'_{i-1})
            Tp[g + i] = s.add_polygon(m * s.polygon(T[g + i]))
        for i in range(1, g):
            s.change_edge_gluing(T[i], 0, T[i + 1], 2)
            s.change_edge_gluing(Tp[i], 2, Tp[i + 1], 0)
        for i in range(1, g + 1):
            s.change_edge_gluing(T[i], 1, T[g + i], 1)
            s.change_edge_gluing(Tp[i], 1, Tp[g + i], 1)
        #P 0 Q 0 is paired with P' 0 Q' 0, ...
        s.change_edge_gluing(T[1], 2, Tp[g], 2)
        s.change_edge_gluing(Tp[1], 0, T[g], 0)
        # P1Q1 is paired with P'_g Q_{g-1}
        s.change_edge_gluing(T[g + 1], 2, Tp[2 * g], 2)
        s.change_edge_gluing(Tp[g + 1], 0, T[2 * g], 0)
        # P1Q0 is paired with P_{g-1} Q_{g-1}
        s.change_edge_gluing(T[g + 1], 0, T[2 * g - 1], 2)
        s.change_edge_gluing(Tp[g + 1], 2, Tp[2 * g - 1], 0)
        # PgQg is paired with Q1P2
        s.change_edge_gluing(T[2 * g], 2, T[g + 2], 0)
        s.change_edge_gluing(Tp[2 * g], 0, Tp[g + 2], 2)
        for i in range(2, g - 1):
            # PiQi is paired with Q'_i P'_{i+1}
            s.change_edge_gluing(T[g + i], 2, Tp[g + i + 1], 2)
            s.change_edge_gluing(Tp[g + i], 0, T[g + i + 1], 0)
        s.set_immutable()
        return TranslationSurface(s)