Example #1
0
def as_embedded_number_field_elements(algs):
    try:
        nf, elts, _ = number_field_elements_from_algebraics(algs, embedded=True)
    except NotImplementedError: # compatibility with Sage <= 9.3
        nf, elts, emb = number_field_elements_from_algebraics(algs)
        if nf is not QQ:
            nf = NumberField(nf.polynomial(), nf.variable_name(),
                        embedding=emb(nf.gen()))
            elts = [elt.polynomial()(nf.gen()) for elt in elts]
            nf, hom = good_number_field(nf)
            elts = [hom(elt) for elt in elts]
        assert [QQbar.coerce(elt) == alg for alg, elt in zip(algs, elts)]
    return nf, elts
Example #2
0
def number_field_elements_from_algebraics(elts, name='a'):
    r"""
    The native Sage function ``number_field_elements_from_algebraics`` currently
    returns number field *without* embedding. This function return field with
    embedding!

    EXAMPLES::

        sage: from flatsurf.geometry.subfield import number_field_elements_from_algebraics
        sage: z = QQbar.zeta(5)
        sage: c = z.real()
        sage: s = z.imag()
        sage: number_field_elements_from_algebraics((c,s))
        (Number Field in a with defining polynomial y^4 - 5*y^2 + 5 with a = 1.902113032590308?,
         [1/2*a^2 - 3/2, 1/2*a])

         sage: number_field_elements_from_algebraics([AA(1), AA(2/3)])
         (Rational Field, [1, 2/3])
    """
    # case when all elements are rationals
    if all(x in QQ for x in elts):
        return QQ, [QQ(x) for x in elts]

    # general case
    from sage.rings.qqbar import number_field_elements_from_algebraics
    field, elts, phi = number_field_elements_from_algebraics(elts,
                                                             minimal=True)

    polys = [x.polynomial() for x in elts]
    K = NumberField(field.polynomial(), name, embedding=AA(phi(field.gen())))
    gen = K.gen()

    return K, [x.polynomial()(gen) for x in elts]
Example #3
0
    def regular_ngon(n):
        r"""
        Return a regular n-gon.

        EXAMPLES::

            sage: from flatsurf.geometry.polygon import polygons

            sage: p = polygons.regular_ngon(17)
            sage: p
            Polygon: (0, 0), (1, 0), ..., (-1/2*a^14 + 15/2*a^12 - 45*a^10 + 275/2*a^8 - 225*a^6 + 189*a^4 - 70*a^2 + 15/2, 1/2*a)
        """
        # The code below crashes for n=4!
        if n==4:
            return polygons.square(QQ(1))
        
        from sage.rings.qqbar import QQbar

        c = QQbar.zeta(n).real()
        s = QQbar.zeta(n).imag()

        field, (c,s) = number_field_elements_from_algebraics((c,s))

        cn = field.one()
        sn = field.zero()
        edges = [(cn,sn)]
        for _ in range(n-1):
            cn,sn = c*cn - s*sn, c*sn + s*cn
            edges.append((cn,sn))

        return Polygons(field)(edges=edges)
Example #4
0
def number_field_elements_from_algebraics(elts, name='a'):
    r"""
    The native Sage function ``number_field_elements_from_algebraics`` currently
    returns number field *without* embedding. This function return field with
    embedding!

    EXAMPLES::

        sage: from flatsurf.geometry.polygon import number_field_elements_from_algebraics
        sage: z = QQbar.zeta(5)
        sage: c = z.real()
        sage: s = z.imag()
        sage: number_field_elements_from_algebraics((c,s))
        (Number Field in a with defining polynomial y^4 - 5*y^2 + 5,
         [1/2*a^2 - 3/2, 1/2*a])
    """
    from sage.rings.qqbar import number_field_elements_from_algebraics
    from sage.rings.number_field.number_field import NumberField
    field,elts,phi = number_field_elements_from_algebraics(elts, minimal=True)

    polys = [x.polynomial() for x in elts]
    K = NumberField(field.polynomial(), name, embedding=AA(phi(field.gen())))
    gen = K.gen()

    return K, [x.polynomial()(gen) for x in elts]
Example #5
0
def to_pyflatsurf(S):
    r"""
    Given S a translation surface from sage-flatsurf return a
    flatsurf::FlatTriangulation from libflatsurf/pyflatsurf.
    """
    if not isinstance(S, TranslationSurface):
        raise TypeError("S must be a translation surface")
    if not S.is_finite():
        raise ValueError("the surface S must be finite")

    S = S.triangulate()

    # populate half edges and vectors
    n = sum(S.polygon(lab).num_edges() for lab in S.label_iterator())
    half_edge_labels = {}  # map: (face lab, edge num) in faltsurf -> integer
    vec = []  # vectors
    k = 1  # half edge label in {1, ..., n}
    for t0, t1 in S.edge_iterator(gluings=True):
        if t0 in half_edge_labels:
            continue

        half_edge_labels[t0] = k
        half_edge_labels[t1] = -k

        f0, e0 = t0
        p = S.polygon(f0)
        vec.append(p.edge(e0))

        k += 1

    # compute vertex and face permutations
    vp = [None] * (n + 1)  # vertex permutation
    fp = [None] * (n + 1)  # face permutation
    for t in S.edge_iterator(gluings=False):
        e = half_edge_labels[t]
        j = (t[1] + 1) % S.polygon(t[0]).num_edges()
        fp[e] = half_edge_labels[(t[0], j)]
        vp[fp[e]] = -e

    # convert the vp permutation into cycles
    verts = _cycle_decomposition(vp)

    # find a finite SageMath base ring that contains all the coordinates
    base_ring = S.base_ring()
    if base_ring is AA:
        from sage.rings.qqbar import number_field_elements_from_algebraics
        from itertools import chain
        base_ring = number_field_elements_from_algebraics(list(
            chain(*[list(v) for v in vec])),
                                                          embedded=True)[0]

    V = Vectors(base_ring)
    vec = [V(v).vector for v in vec]

    _check_data(vp, fp, vec)

    return make_surface(verts, vec)
Example #6
0
    def _number_field_from_algebraics(self):
        r"""
        Given a projective point defined over ``QQbar``, return the same point, but defined
        over a number field.

        This is only implemented for points of projective space.

        OUTPUT: scheme point

        EXAMPLES::

            sage: R.<x> = PolynomialRing(QQ)
            sage: P.<x,y> = ProjectiveSpace(QQbar,1)
            sage: Q = P([-1/2*QQbar(sqrt(2)) + QQbar(I), 1])
            sage: S = Q._number_field_from_algebraics(); S
            (1/2*a^3 + a^2 - 1/2*a : 1)
            sage: S.codomain()
            Projective Space of dimension 1 over Number Field in a with defining polynomial y^4 + 1 with a = 0.7071067811865475? + 0.7071067811865475?*I

        The following was fixed in :trac:`23808`::

            sage: R.<x> = PolynomialRing(QQ)
            sage: P.<x,y> = ProjectiveSpace(QQbar,1)
            sage: Q = P([-1/2*QQbar(sqrt(2)) + QQbar(I), 1]);Q
            (-0.7071067811865475? + 1*I : 1)
            sage: S = Q._number_field_from_algebraics(); S
            (1/2*a^3 + a^2 - 1/2*a : 1)
            sage: T = S.change_ring(QQbar) # Used to fail
            sage: T
            (-0.7071067811865475? + 1.000000000000000?*I : 1)
            sage: Q[0] == T[0]
            True
        """
        from sage.schemes.projective.projective_space import is_ProjectiveSpace
        if not is_ProjectiveSpace(self.codomain()):
            raise NotImplementedError("not implemented for subschemes")

        # Trac #23808: Keep the embedding info associated with the number field K
        # used below, instead of in the separate embedding map phi which is
        # forgotten.
        K_pre, P, phi = number_field_elements_from_algebraics(list(self))
        if K_pre is QQ:
            K = QQ
        else:
            from sage.rings.number_field.number_field import NumberField
            K = NumberField(K_pre.polynomial(),
                            embedding=phi(K_pre.gen()),
                            name='a')
            psi = K_pre.hom([K.gen()], K)  # Identification of K_pre with K
            P = [psi(p) for p in P]  # The elements of P were elements of K_pre
        from sage.schemes.projective.projective_space import ProjectiveSpace
        PS = ProjectiveSpace(K, self.codomain().dimension_relative(), 'z')
        return PS(P)
Example #7
0
def as_embedded_number_field_elements(algs):
    # number_field_elements_from_algebraics() now takes an embedded=...
    # argument, but doesn't yet support all cases we need
    nf, elts, emb = number_field_elements_from_algebraics(algs)
    if nf is not QQ:
        nf = NumberField(nf.polynomial(), nf.variable_name(),
                    embedding=emb(nf.gen()))
        elts = [elt.polynomial()(nf.gen()) for elt in elts]
        nf, hom = good_number_field(nf)
        elts = [hom(elt) for elt in elts]
    # assert [QQbar.coerce(elt) == alg for alg, elt in zip(algs, elts)]
    return nf, elts
Example #8
0
    def right_triangle(angle,leg0=None, leg1=None, hypotenuse=None):
        r"""
        Return a right triangle in a numberfield with an angle of pi*m/n.
        You can specify the length of the first leg (leg0), the second leg (leg1),
        or the hypotenuse.
        """
        from sage.rings.qqbar import QQbar
        z=QQbar.zeta(2*angle.denom())**angle.numer()
        c = z.real()
        s = z.imag()

        if not leg0 is None:
            c,s = leg0*c/c,leg0*s/c
        elif not leg1 is None:
            c,s = leg1*c/s,leg1*s/s
        elif not hypotenuse is None:
            c,s = hypotenuse*c, hypotenuse*s
        
        field, (c,s) = number_field_elements_from_algebraics((c,s))
        return Polygons(field)(edges=[(c,field.zero()),(field.zero(),s),(-c,-s)])
Example #9
0
def polyhedron_to_cone_surface(polyhedron, use_AA=False, scaling_factor=ZZ(1)):
    r"""Construct the Euclidean Cone Surface associated to the surface of a polyhedron and a map
    from the cone surface to the polyhedron.
    
    INPUT:

    - ``polyhedron`` -- A 3-dimensional polyhedron, which should be define over something that coerces into AA

    - ``use_AA`` -- If True, the surface returned will be defined over AA. If false, the algorithm will find the smallest NumberField and write the field there.
    
    - ``scaling_factor`` -- The surface returned will have a metric scaled by multiplication by this factor (compared with the original polyhendron). This can be used to produce a surface defined over a smaller NumberField.
    
    OUTPUT:
    
    A pair consisting of a ConeSurface and a ConeSurfaceToPolyhedronMap.

    EXAMPLES::

    sage: from flatsurf.geometry.polyhedra import *
    sage: vertices=[]
    sage: for i in xrange(3):
    ....:     temp=vector([1 if k==i else 0 for k in xrange(3)])
    ....:     for j in xrange(-1,3,2):
    ....:         vertices.append(j*temp)
    sage: octahedron=Polyhedron(vertices=vertices)
    sage: surface,surface_to_octahedron = \
    ....:     polyhedron_to_cone_surface(octahedron,scaling_factor=AA(1/sqrt(2)))
    sage: TestSuite(surface).run()
    sage: TestSuite(surface_to_octahedron).run(skip="_test_pickling")
    sage: surface.num_polygons()
    8
    sage: surface.base_ring()
    Number Field in a with defining polynomial y^2 - 3
    sage: sqrt3=surface.base_ring().gen()
    sage: tangent_bundle=surface.tangent_bundle()
    sage: v=tangent_bundle(0,(0,0),(sqrt3,2))
    sage: traj=v.straight_line_trajectory()
    sage: traj.flow(10)
    sage: traj.is_saddle_connection()
    True
    sage: traj.combinatorial_length()
    8
    sage: surface_to_octahedron(traj)
    [(-1, 0, 0),
     (0.?e-18, -0.8000000000000000?, -0.2000000000000000?),
     (0.2500000000000000?, -0.750000000000000?, 0.?e-18),
     (0.4000000000000000?, 0.?e-19, 0.6000000000000000?),
     (0.?e-19, 0.500000000000000?, 0.500000000000000?),
     (-2/5, 3/5, 0),
     (-0.2500000000000000?, 0.?e-18, -0.750000000000000?),
     (0.?e-18, -0.2000000000000000?, -0.8000000000000000?),
     (1, 0, 0)]
    sage: surface_to_octahedron(traj.segment(0))
    ((-1, 0, 0), (0.?e-18, -0.8000000000000000?, -0.2000000000000000?))
    sage: surface_to_octahedron(traj.segment(0).start())
    ((-1, 0, 0), (2.886751345948129?, -2.309401076758503?, -0.5773502691896258?))
    sage: # octahedron.plot(wireframe=None,frame=False)+line3d(surface_to_octahedron(traj),radius=0.01)

    """
    assert polyhedron.dim()==3
    c=polyhedron.center()
    vertices=polyhedron.vertices()
    vertex_order={}
    for i,v in enumerate(vertices):
        vertex_order[v]=i
    faces=polyhedron.faces(2)
    face_order={}
    face_edges=[]
    face_vertices=[]
    face_map_data=[]
    for i,f in enumerate(faces):
        face_order[f]=i
        edges=f.as_polyhedron().faces(1)
        face_edges_temp = set()
        for edge in edges:
            edge_temp=set()
            for vertex in edge.vertices():
                v=vertex.vector()
                v.set_immutable()
                edge_temp.add(v)
            face_edges_temp.add(frozenset(edge_temp))

            
        last_edge = next(iter(face_edges_temp))
        v = next(iter(last_edge))
        face_vertices_temp=[v]
        for j in xrange(len(face_edges_temp)-1):
            for edge in face_edges_temp:
                if v in edge and edge!=last_edge:
                    # bingo
                    last_edge=edge
                    for vv in edge:
                        if vv!=v:
                            v=vv
                            face_vertices_temp.append(vv)
                            break
                    break


        v0=face_vertices_temp[0]
        v1=face_vertices_temp[1]
        v2=face_vertices_temp[2]
        n=(v1-v0).cross_product(v2-v0)
        if (v0-c).dot_product(n)<0:
            n=-n
            face_vertices_temp.reverse()
            v0=face_vertices_temp[0]
            v1=face_vertices_temp[1]
            v2=face_vertices_temp[2]

        face_vertices.append(face_vertices_temp)    
        n=n/AA(n.norm())
        w=v1-v0
        w=w/AA(w.norm())
        m=1/scaling_factor*matrix(AA,[w,n.cross_product(w),n]).transpose()
        mi=~m
        mis=mi.submatrix(0,0,2,3)
        face_map_data.append((
            v0, # translation to bring origin in plane to v0
            m.submatrix(0,0,3,2),
            -mis*v0,
            mis
        ))

        it=iter(face_vertices_temp)    
        v_last=next(it)
        face_edge_dict={}
        j=0
        for v in it:
            edge=frozenset([v_last,v])
            face_edge_dict[edge]=j
            j+=1
            v_last=v
        v=next(iter(face_vertices_temp))
        edge=frozenset([v_last,v])
        face_edge_dict[edge]=j
        face_edges.append(face_edge_dict)
        
    gluings={}
    for p1,face_edge_dict1 in enumerate(face_edges):
        for edge, e1 in face_edge_dict1.items():
            found=False
            for p2, face_edge_dict2 in enumerate(face_edges):
                if p1!=p2 and edge in face_edge_dict2:
                    e2=face_edge_dict2[edge]
                    gluings[(p1,e1)]=(p2,e2)
                    found=True
                    break
            if not found:
                print(p1)
                print(e1)
                print(edge)
                raise RuntimeError("Failed to find glued edge")
    polygon_vertices_AA=[]
    for p,vs in enumerate(face_vertices):
        trans=face_map_data[p][2]
        m=face_map_data[p][3]
        polygon_vertices_AA.append([trans+m*v for v in vs])
        
    
    if use_AA==True:
        Polys=Polygons(AA)
        polygons=[]
        for vs in polygon_vertices_AA:
            polygons.append(Polys(vertices=vs))
        S=ConeSurface(surface_list_from_polygons_and_gluings(polygons,gluings))
        return S, \
            ConeSurfaceToPolyhedronMap(S,polyhedron,face_map_data)
    else:
        elts=[]
        for vs in polygon_vertices_AA:
            for v in vs:
                elts.append(v[0])
                elts.append(v[1])
                
        # Find the best number field:
        field,elts2,hom = number_field_elements_from_algebraics(elts,minimal=True)
        if field==QQ:
            # Defined over the rationals!
            polygon_vertices_field2=[]
            j=0
            for vs in polygon_vertices_AA:
                vs2=[]
                for v in vs:
                    vs2.append(vector(field,[elts2[j],elts2[j+1]]))
                    j=j+2
                polygon_vertices_field2.append(vs2)
            Polys=Polygons(field)
            polygons=[]
            for vs in polygon_vertices_field2:
                polygons.append(Polys(vertices=vs))
            S=ConeSurface(surface_list_from_polygons_and_gluings(polygons,gluings))
            return S, \
                ConeSurfaceToPolyhedronMap(S,polyhedron,face_map_data)

        else:        
            # Unfortunately field doesn't come with an real embedding (which is given by hom!)
            # So, we make a copy of the field, and add the embedding.
            field2=NumberField(field.polynomial(),name="a",embedding=hom(field.gen()))
            # The following converts from field to field2:
            hom2=field.hom(im_gens=[field2.gen()])

            polygon_vertices_field2=[]
            j=0
            for vs in polygon_vertices_AA:
                vs2=[]
                for v in vs:
                    vs2.append(vector(field2,[hom2(elts2[j]),hom2(elts2[j+1])]))
                    j=j+2
                polygon_vertices_field2.append(vs2)
            Polys=Polygons(field2)
            polygons=[]
            for vs in polygon_vertices_field2:
                polygons.append(Polys(vertices=vs))
            S=ConeSurface(surface_list_from_polygons_and_gluings(polygons,gluings))
            return S, \
                ConeSurfaceToPolyhedronMap(S,polyhedron,face_map_data)
Example #10
0
    def wrapper(*args, **kwds):
        """
        TESTS::

            sage: from sage.rings.qqbar_decorators import handle_AA_and_QQbar
            sage: @handle_AA_and_QQbar
            ....: def return_base_ring(x):
            ....:     return x.base_ring()

            sage: P.<x> = QQbar[]
            sage: return_base_ring(x)
            Rational Field

            sage: P.<y,z> = QQbar[]
            sage: return_base_ring(y)
            Rational Field

            sage: return_base_ring(ideal(y,z))
            Rational Field
        """

        from sage.misc.flatten import flatten
        from sage.rings.polynomial.polynomial_element import Polynomial
        from sage.rings.polynomial.multi_polynomial import MPolynomial
        from sage.rings.ideal import Ideal, Ideal_generic
        from sage.rings.qqbar import AlgebraicField_common, number_field_elements_from_algebraics

        if not any([
                isinstance(a, (Polynomial, MPolynomial, Ideal_generic))
                and isinstance(a.base_ring(), AlgebraicField_common)
                for a in args
        ]):
            return func(*args, **kwds)

        polynomials = []

        for a in flatten(args, ltypes=(list, tuple, set)):
            if isinstance(a, Ideal_generic):
                polynomials.extend(a.gens())
            elif isinstance(a, Polynomial):
                polynomials.append(a)
            elif isinstance(a, MPolynomial):
                polynomials.append(a)

        orig_elems = flatten([p.coefficients() for p in polynomials])

        # We need minimal=True if these elements are over AA, because
        # same_field=True might trigger an exception otherwise.

        numfield, new_elems, morphism = number_field_elements_from_algebraics(
            orig_elems, same_field=True, minimal=True)

        elem_dict = dict(zip(orig_elems, new_elems))

        def forward_map(item):
            if isinstance(item, Ideal_generic):
                return Ideal([forward_map(g) for g in item.gens()])
            elif isinstance(item, Polynomial):
                return item.map_coefficients(elem_dict.__getitem__,
                                             new_base_ring=numfield)
            elif isinstance(item, MPolynomial):
                return item.map_coefficients(elem_dict.__getitem__,
                                             new_base_ring=numfield)
            elif isinstance(item, list):
                return map(forward_map, item)
            elif isinstance(item, tuple):
                return tuple(map(forward_map, item))
            elif isinstance(item, set):
                return set(map(forward_map, list(item)))
            else:
                return item

        def reverse_map(item):
            if isinstance(item, Ideal_generic):
                return Ideal([reverse_map(g) for g in item.gens()])
            elif isinstance(item, Polynomial):
                return item.map_coefficients(morphism)
            elif isinstance(item, MPolynomial):
                return item.map_coefficients(morphism)
            elif isinstance(item, list):
                return map(reverse_map, item)
            elif isinstance(item, tuple):
                return tuple(map(reverse_map, item))
            elif isinstance(item, set):
                return set(map(reverse_map, list(item)))
            else:
                return item

        args = map(forward_map, args)

        return reverse_map(func(*args, **kwds))
Example #11
0
    def wrapper(*args, **kwds):
        """
        TESTS::

            sage: from sage.rings.qqbar_decorators import handle_AA_and_QQbar
            sage: @handle_AA_and_QQbar
            ....: def return_base_ring(x):
            ....:     return x.base_ring()

            sage: P.<x> = QQbar[]
            sage: return_base_ring(x)
            Rational Field

            sage: P.<y,z> = QQbar[]
            sage: return_base_ring(y)
            Rational Field

            sage: return_base_ring(ideal(y,z))
            Rational Field

        Check that :trac:`29468` is fixed::

            sage: J = QQbar['x,y'].ideal('x^2 - y')
            sage: type(J.groebner_basis())
            <class 'sage.rings.polynomial.multi_polynomial_sequence.PolynomialSequence_generic'>
            sage: J.groebner_basis().is_immutable()
            True

        ::

            sage: @handle_AA_and_QQbar
            ....: def f(x):
            ....:     print(x.ring().base_ring())
            ....:     return x
            sage: R.<x,y> = QQbar[]
            sage: s = Sequence([x, R(sqrt(2)) * y], immutable=True)
            sage: t = f(s)
            Number Field in a with defining polynomial y^2 - 2
            sage: t.ring().base_ring()
            Algebraic Field
            sage: t.is_immutable()
            True
            sage: s == t
            True
        """

        from sage.misc.flatten import flatten
        from sage.rings.polynomial.polynomial_element import Polynomial
        from sage.rings.polynomial.multi_polynomial import MPolynomial
        from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence, is_PolynomialSequence
        from sage.rings.ideal import Ideal, Ideal_generic
        from sage.rings.qqbar import is_AlgebraicField_common, number_field_elements_from_algebraics

        if not any(
                isinstance(a, (Polynomial, MPolynomial, Ideal_generic))
                and is_AlgebraicField_common(a.base_ring())
                or is_PolynomialSequence(a)
                and is_AlgebraicField_common(a.ring().base_ring())
                for a in args):
            return func(*args, **kwds)

        polynomials = []

        for a in flatten(args, ltypes=(list, tuple, set)):
            if isinstance(a, Ideal_generic):
                polynomials.extend(a.gens())
            elif isinstance(a, Polynomial):
                polynomials.append(a)
            elif isinstance(a, MPolynomial):
                polynomials.append(a)

        orig_elems = flatten([p.coefficients() for p in polynomials])

        # We need minimal=True if these elements are over AA, because
        # same_field=True might trigger an exception otherwise.

        numfield, new_elems, morphism = number_field_elements_from_algebraics(
            orig_elems, same_field=True, minimal=True)

        elem_dict = dict(zip(orig_elems, new_elems))

        def forward_map(item):
            if isinstance(item, Ideal_generic):
                return Ideal([forward_map(g) for g in item.gens()])
            elif isinstance(item, Polynomial):
                return item.map_coefficients(elem_dict.__getitem__,
                                             new_base_ring=numfield)
            elif isinstance(item, MPolynomial):
                return item.map_coefficients(elem_dict.__getitem__,
                                             new_base_ring=numfield)
            elif is_PolynomialSequence(item):
                return PolynomialSequence(map(forward_map, item),
                                          immutable=item.is_immutable())
            elif isinstance(item, list):
                return list(map(forward_map, item))
            elif isinstance(item, dict):
                return {k: forward_map(v) for k, v in item.items()}
            elif isinstance(item, tuple):
                return tuple(map(forward_map, item))
            elif isinstance(item, set):
                return set(map(forward_map, list(item)))
            else:
                return item

        def reverse_map(item):
            if isinstance(item, Ideal_generic):
                return Ideal([reverse_map(g) for g in item.gens()])
            elif isinstance(item, Polynomial):
                return item.map_coefficients(morphism)
            elif isinstance(item, MPolynomial):
                return item.map_coefficients(morphism)
            elif is_PolynomialSequence(item):
                return PolynomialSequence(map(reverse_map, item),
                                          immutable=item.is_immutable())
            elif isinstance(item, list):
                return list(map(reverse_map, item))
            elif isinstance(item, tuple):
                return tuple(map(reverse_map, item))
            elif isinstance(item, set):
                return set(map(reverse_map, list(item)))
            else:
                return item

        args = forward_map(args)
        kwds = forward_map(kwds)

        return reverse_map(func(*args, **kwds))
Example #12
0
def polyhedron_to_cone_surface(polyhedron, use_AA=False, scaling_factor=ZZ(1)):
    r"""Construct the Euclidean Cone Surface associated to the surface of a polyhedron and a map
    from the cone surface to the polyhedron.
    
    INPUT:

    - ``polyhedron`` -- A 3-dimensional polyhedron, which should be define over something that coerces into AA

    - ``use_AA`` -- If True, the surface returned will be defined over AA. If false, the algorithm will find the smallest NumberField and write the field there.
    
    - ``scaling_factor`` -- The surface returned will have a metric scaled by multiplication by this factor (compared with the original polyhendron). This can be used to produce a surface defined over a smaller NumberField.
    
    OUTPUT:
    
    A pair consisting of a ConeSurface and a ConeSurfaceToPolyhedronMap.

    EXAMPLES::

    sage: from flatsurf.geometry.polyhedra import *
    sage: vertices=[]
    sage: for i in range(3):
    ....:     temp=vector([1 if k==i else 0 for k in range(3)])
    ....:     for j in range(-1,3,2):
    ....:         vertices.append(j*temp)
    sage: octahedron=Polyhedron(vertices=vertices)
    sage: surface,surface_to_octahedron = \
    ....:     polyhedron_to_cone_surface(octahedron,scaling_factor=AA(1/sqrt(2)))
    sage: TestSuite(surface).run()
    sage: TestSuite(surface_to_octahedron).run(skip="_test_pickling")
    sage: surface.num_polygons()
    8
    sage: surface.base_ring()
    Number Field in a with defining polynomial y^2 - 3 with a = 1.732050807568878?
    sage: sqrt3=surface.base_ring().gen()
    sage: tangent_bundle=surface.tangent_bundle()
    sage: v=tangent_bundle(0,(0,0),(sqrt3,2))
    sage: traj=v.straight_line_trajectory()
    sage: traj.flow(10)
    sage: traj.is_saddle_connection()
    True
    sage: traj.combinatorial_length()
    8
    sage: path3d = surface_to_octahedron(traj)
    sage: len(path3d)
    9
    sage: # We will show that the length of the path is sqrt(42):
    sage: total_length = 0
    sage: for i in range(8):
    ....:     start = path3d[i]
    ....:     end = path3d[i+1]
    ....:     total_length += (vector(end)-vector(start)).norm()
    sage: ZZ(total_length**2)
    42
    """
    assert polyhedron.dim() == 3
    c = polyhedron.center()
    vertices = polyhedron.vertices()
    vertex_order = {}
    for i, v in enumerate(vertices):
        vertex_order[v] = i
    faces = polyhedron.faces(2)
    face_order = {}
    face_edges = []
    face_vertices = []
    face_map_data = []
    for i, f in enumerate(faces):
        face_order[f] = i
        edges = f.as_polyhedron().faces(1)
        face_edges_temp = set()
        for edge in edges:
            edge_temp = set()
            for vertex in edge.vertices():
                v = vertex.vector()
                v.set_immutable()
                edge_temp.add(v)
            face_edges_temp.add(frozenset(edge_temp))

        last_edge = next(iter(face_edges_temp))
        v = next(iter(last_edge))
        face_vertices_temp = [v]
        for j in range(len(face_edges_temp) - 1):
            for edge in face_edges_temp:
                if v in edge and edge != last_edge:
                    # bingo
                    last_edge = edge
                    for vv in edge:
                        if vv != v:
                            v = vv
                            face_vertices_temp.append(vv)
                            break
                    break

        v0 = face_vertices_temp[0]
        v1 = face_vertices_temp[1]
        v2 = face_vertices_temp[2]
        n = (v1 - v0).cross_product(v2 - v0)
        if (v0 - c).dot_product(n) < 0:
            n = -n
            face_vertices_temp.reverse()
            v0 = face_vertices_temp[0]
            v1 = face_vertices_temp[1]
            v2 = face_vertices_temp[2]

        face_vertices.append(face_vertices_temp)
        n = n / AA(n.norm())
        w = v1 - v0
        w = w / AA(w.norm())
        m = 1 / scaling_factor * matrix(
            AA, [w, n.cross_product(w), n]).transpose()
        mi = ~m
        mis = mi.submatrix(0, 0, 2, 3)
        face_map_data.append((
            v0,  # translation to bring origin in plane to v0
            m.submatrix(0, 0, 3, 2),
            -mis * v0,
            mis))

        it = iter(face_vertices_temp)
        v_last = next(it)
        face_edge_dict = {}
        j = 0
        for v in it:
            edge = frozenset([v_last, v])
            face_edge_dict[edge] = j
            j += 1
            v_last = v
        v = next(iter(face_vertices_temp))
        edge = frozenset([v_last, v])
        face_edge_dict[edge] = j
        face_edges.append(face_edge_dict)

    gluings = {}
    for p1, face_edge_dict1 in enumerate(face_edges):
        for edge, e1 in face_edge_dict1.items():
            found = False
            for p2, face_edge_dict2 in enumerate(face_edges):
                if p1 != p2 and edge in face_edge_dict2:
                    e2 = face_edge_dict2[edge]
                    gluings[(p1, e1)] = (p2, e2)
                    found = True
                    break
            if not found:
                print(p1)
                print(e1)
                print(edge)
                raise RuntimeError("Failed to find glued edge")
    polygon_vertices_AA = []
    for p, vs in enumerate(face_vertices):
        trans = face_map_data[p][2]
        m = face_map_data[p][3]
        polygon_vertices_AA.append([trans + m * v for v in vs])

    if use_AA == True:
        Polys = ConvexPolygons(AA)
        polygons = []
        for vs in polygon_vertices_AA:
            polygons.append(Polys(vertices=vs))
        S = ConeSurface(
            surface_list_from_polygons_and_gluings(polygons, gluings))
        return S, \
            ConeSurfaceToPolyhedronMap(S,polyhedron,face_map_data)
    else:
        elts = []
        for vs in polygon_vertices_AA:
            for v in vs:
                elts.append(v[0])
                elts.append(v[1])

        # Find the best number field:
        field, elts2, hom = number_field_elements_from_algebraics(elts,
                                                                  minimal=True)
        if field == QQ:
            # Defined over the rationals!
            polygon_vertices_field2 = []
            j = 0
            for vs in polygon_vertices_AA:
                vs2 = []
                for v in vs:
                    vs2.append(vector(field, [elts2[j], elts2[j + 1]]))
                    j = j + 2
                polygon_vertices_field2.append(vs2)
            Polys = ConvexPolygons(field)
            polygons = []
            for vs in polygon_vertices_field2:
                polygons.append(Polys(vertices=vs))
            S = ConeSurface(
                surface_list_from_polygons_and_gluings(polygons, gluings))
            return S, \
                ConeSurfaceToPolyhedronMap(S,polyhedron,face_map_data)

        else:
            # Unfortunately field doesn't come with an real embedding (which is given by hom!)
            # So, we make a copy of the field, and add the embedding.
            field2 = NumberField(field.polynomial(),
                                 name="a",
                                 embedding=hom(field.gen()))
            # The following converts from field to field2:
            hom2 = field.hom(im_gens=[field2.gen()])

            polygon_vertices_field2 = []
            j = 0
            for vs in polygon_vertices_AA:
                vs2 = []
                for v in vs:
                    vs2.append(
                        vector(field2, [hom2(elts2[j]),
                                        hom2(elts2[j + 1])]))
                    j = j + 2
                polygon_vertices_field2.append(vs2)
            Polys = ConvexPolygons(field2)
            polygons = []
            for vs in polygon_vertices_field2:
                polygons.append(Polys(vertices=vs))
            S = ConeSurface(
                surface_list_from_polygons_and_gluings(polygons, gluings))
            return S, \
                ConeSurfaceToPolyhedronMap(S,polyhedron,face_map_data)