def platonic_icosahedron(): r"""Produce a triple consisting of a polyhedral version of the platonic icosahedron, the associated cone surface, and a ConeSurfaceToPolyhedronMap from the surface to the polyhedron. EXAMPLES:: sage: from flatsurf.geometry.polyhedra import platonic_icosahedron sage: polyhedron,surface,surface_to_polyhedron = platonic_icosahedron() sage: TestSuite(surface).run() r""" vertices=[] phi=AA(1+sqrt(5))/2 F=NumberField(phi.minpoly(),"phi",embedding=phi) phi=F.gen() for i in xrange(3): for s1 in xrange(-1,3,2): for s2 in xrange(-1,3,2): p=3*[None] p[i]=s1*phi p[(i+1)%3]=s2 p[(i+2)%3]=0 vertices.append(vector(F,p)) p=Polyhedron(vertices=vertices) s,m = polyhedron_to_cone_surface(p) return p,s,m
def platonic_dodecahedron(): r"""Produce a triple consisting of a polyhedral version of the platonic dodecahedron, the associated cone surface, and a ConeSurfaceToPolyhedronMap from the surface to the polyhedron. EXAMPLES:: sage: from flatsurf.geometry.polyhedra import platonic_dodecahedron sage: polyhedron,surface,surface_to_polyhedron = platonic_dodecahedron() sage: TestSuite(surface).run() r""" vertices=[] phi=AA(1+sqrt(5))/2 F=NumberField(phi.minpoly(),"phi",embedding=phi) phi=F.gen() for x in xrange(-1,3,2): for y in xrange(-1,3,2): for z in xrange(-1,3,2): vertices.append(vector(F,(x,y,z))) for x in xrange(-1,3,2): for y in xrange(-1,3,2): vertices.append(vector(F,(0,x*phi,y/phi))) vertices.append(vector(F,(y/phi,0,x*phi))) vertices.append(vector(F,(x*phi,y/phi,0))) scale=AA(2/sqrt(1+(phi-1)**2+(1/phi-1)**2)) p=Polyhedron(vertices=vertices) s,m = polyhedron_to_cone_surface(p,scaling_factor=scale) return p,s,m
def _mpfr_(self, R): r""" TESTS:: sage: RR(E(7) + E(7,6)) 1.24697960371747 sage: 2*cos(2*pi/7).n() 1.24697960371747 """ if not self.is_real(): raise TypeError("self is not real") from sage.rings.qqbar import QQbar, AA return AA(QQbar(self))._mpfr_(R)
def platonic_tetrahedron(): r"""Produce a triple consisting of a polyhedral version of the platonic tetrahedron, the associated cone surface, and a ConeSurfaceToPolyhedronMap from the surface to the polyhedron. EXAMPLES:: sage: from flatsurf.geometry.polyhedra import platonic_tetrahedron sage: polyhedron,surface,surface_to_polyhedron = platonic_tetrahedron() sage: TestSuite(surface).run() r""" vertices = [] for x in range(-1, 3, 2): for y in range(-1, 3, 2): vertices.append(vector(QQ, (x, y, x * y))) p = Polyhedron(vertices=vertices) s, m = polyhedron_to_cone_surface(p, scaling_factor=AA(1 / sqrt(2))) return p, s, m
def abs(self): """ Return the absolute value of ``self`` as an algebraic real number. EXAMPLES:: sage: f = 5/2*E(3)+E(5)/7 sage: f.abs() 2.597760303873084? TESTS:: sage: [E(n).abs() for n in range(1, 11)] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] sage: UniversalCyclotomicField().zero().abs() 0 """ square = self * self.conjugate() return AA(square).sqrt()
def platonic_octahedron(): r"""Produce a triple consisting of a polyhedral version of the platonic octahedron, the associated cone surface, and a ConeSurfaceToPolyhedronMap from the surface to the polyhedron. EXAMPLES:: sage: from flatsurf.geometry.polyhedra import platonic_octahedron sage: polyhedron,surface,surface_to_polyhedron = platonic_octahedron() sage: TestSuite(surface).run() r""" vertices = [] for i in range(3): temp = vector(QQ, [1 if k == i else 0 for k in range(3)]) for j in range(-1, 3, 2): vertices.append(j * temp) octahedron = Polyhedron(vertices=vertices) surface,surface_to_octahedron = \ polyhedron_to_cone_surface(octahedron,scaling_factor=AA(sqrt(2))) return octahedron, surface, surface_to_octahedron
def complex_roots(p, skip_squarefree=False, retval='interval', min_prec=0): """ Compute the complex roots of a given polynomial with exact coefficients (integer, rational, Gaussian rational, and algebraic coefficients are supported). Returns a list of pairs of a root and its multiplicity. Roots are returned as a ComplexIntervalFieldElement; each interval includes exactly one root, and the intervals are disjoint. By default, the algorithm will do a squarefree decomposition to get squarefree polynomials. The skip_squarefree parameter lets you skip this step. (If this step is skipped, and the polynomial has a repeated root, then the algorithm will loop forever!) You can specify retval='interval' (the default) to get roots as complex intervals. The other options are retval='algebraic' to get elements of QQbar, or retval='algebraic_real' to get only the real roots, and to get them as elements of AA. EXAMPLES:: sage: from sage.rings.polynomial.complex_roots import complex_roots sage: x = polygen(ZZ) sage: complex_roots(x^5 - x - 1) [(1.167303978261419?, 1), (-0.764884433600585? - 0.352471546031727?*I, 1), (-0.764884433600585? + 0.352471546031727?*I, 1), (0.181232444469876? - 1.083954101317711?*I, 1), (0.181232444469876? + 1.083954101317711?*I, 1)] sage: v=complex_roots(x^2 + 27*x + 181) Unfortunately due to numerical noise there can be a small imaginary part to each root depending on CPU, compiler, etc, and that affects the printing order. So we verify the real part of each root and check that the imaginary part is small in both cases:: sage: v # random [(-14.61803398874990?..., 1), (-12.3819660112501...? + 0.?e-27*I, 1)] sage: sorted((v[0][0].real(),v[1][0].real())) [-14.61803398874989?, -12.3819660112501...?] sage: v[0][0].imag() < 1e25 True sage: v[1][0].imag() < 1e25 True sage: K.<im> = NumberField(x^2 + 1) sage: eps = 1/2^100 sage: x = polygen(K) sage: p = (x-1)*(x-1-eps)*(x-1+eps)*(x-1-eps*im)*(x-1+eps*im) This polynomial actually has all-real coefficients, and is very, very close to (x-1)^5:: sage: [RR(QQ(a)) for a in list(p - (x-1)^5)] [3.87259191484932e-121, -3.87259191484932e-121] sage: rts = complex_roots(p) sage: [ComplexIntervalField(10)(rt[0] - 1) for rt in rts] [-7.8887?e-31, 0, 7.8887?e-31, -7.8887?e-31*I, 7.8887?e-31*I] We can get roots either as intervals, or as elements of QQbar or AA. :: sage: p = (x^2 + x - 1) sage: p = p * p(x*im) sage: p -x^4 + (im - 1)*x^3 + im*x^2 + (-im - 1)*x + 1 Two of the roots have a zero real component; two have a zero imaginary component. These zero components will be found slightly inaccurately, and the exact values returned are very sensitive to the (non-portable) results of NumPy. So we post-process the roots for printing, to get predictable doctest results. :: sage: def tiny(x): ....: return x.contains_zero() and x.absolute_diameter() < 1e-14 sage: def smash(x): ....: x = CIF(x[0]) # discard multiplicity ....: if tiny(x.imag()): return x.real() ....: if tiny(x.real()): return CIF(0, x.imag()) sage: rts = complex_roots(p); type(rts[0][0]), sorted(map(smash, rts)) (<type 'sage.rings.complex_interval.ComplexIntervalFieldElement'>, [-1.618033988749895?, -0.618033988749895?*I, 1.618033988749895?*I, 0.618033988749895?]) sage: rts = complex_roots(p, retval='algebraic'); type(rts[0][0]), sorted(map(smash, rts)) (<class 'sage.rings.qqbar.AlgebraicNumber'>, [-1.618033988749895?, -0.618033988749895?*I, 1.618033988749895?*I, 0.618033988749895?]) sage: rts = complex_roots(p, retval='algebraic_real'); type(rts[0][0]), rts (<class 'sage.rings.qqbar.AlgebraicReal'>, [(-1.618033988749895?, 1), (0.618033988749895?, 1)]) TESTS: Verify that :trac:`12026` is fixed:: sage: f = matrix(QQ, 8, lambda i, j: 1/(i + j + 1)).charpoly() sage: from sage.rings.polynomial.complex_roots import complex_roots sage: len(complex_roots(f)) 8 """ if skip_squarefree: factors = [(p, 1)] else: factors = p.squarefree_decomposition() prec = 53 while True: CC = ComplexField(prec) CCX = CC['x'] all_rts = [] ok = True for (factor, exp) in factors: cfac = CCX(factor) rts = cfac.roots(multiplicities=False) # Make sure the number of roots we found is the degree. If # we don't find that many roots, it's because the # precision isn't big enough and though the (possibly # exact) polynomial "factor" is squarefree, it is not # squarefree as an element of CCX. if len(rts) < factor.degree(): ok = False break irts = interval_roots(factor, rts, max(prec, min_prec)) if irts is None: ok = False break if retval != 'interval': factor = QQbar.common_polynomial(factor) for irt in irts: all_rts.append((irt, factor, exp)) if ok and intervals_disjoint([rt for (rt, fac, mult) in all_rts]): all_rts = sort_complex_numbers_for_display(all_rts) if retval == 'interval': return [(rt, mult) for (rt, fac, mult) in all_rts] elif retval == 'algebraic': return [(QQbar.polynomial_root(fac, rt), mult) for (rt, fac, mult) in all_rts] elif retval == 'algebraic_real': rts = [] for (rt, fac, mult) in all_rts: qqbar_rt = QQbar.polynomial_root(fac, rt) if qqbar_rt.imag().is_zero(): rts.append((AA(qqbar_rt), mult)) return rts prec = prec * 2
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)