def push_vector_forward(self,tangent_vector): r"""Applies the mapping to the provided vector.""" ring = tangent_vector.bundle().base_ring() if tangent_vector.polygon_label() == self._p: point=tangent_vector.point() vertex1=self._domain.polygon(self._p).vertex(self._v1) vertex2=self._domain.polygon(self._p).vertex(self._v2) wp = wedge_product(vertex2-vertex1,point-vertex1) if wp > 0: # in new polygon 1 return self.codomain().tangent_vector( \ self._p, \ self._tp(tangent_vector.point()), \ tangent_vector.vector(), \ ring = ring) if wp < 0: # in new polygon 2 return self.codomain().tangent_vector( \ self._new_label, \ self._tnew_label(tangent_vector.point()), \ tangent_vector.vector(), \ ring = ring) # Otherwise wp==0 w = tangent_vector.vector() wp = wedge_product(vertex2-vertex1,w) if wp > 0: # in new polygon 1 return self.codomain().tangent_vector( \ self._p, \ self._tp(tangent_vector.point()), \ tangent_vector.vector(), \ ring = ring) # in new polygon 2 return self.codomain().tangent_vector( \ self._new_label, \ self._tnew_label(tangent_vector.point()), \ tangent_vector.vector(), \ ring = ring) else: # Not in a polygon that was changed. Just copy the data. return self._codomain.tangent_vector( \ tangent_vector.polygon_label(), \ tangent_vector.point(), \ tangent_vector.vector(), \ ring = ring)
def push_vector_forward(self, tangent_vector): r"""Applies the mapping to the provided vector.""" ring = tangent_vector.bundle().base_ring() if tangent_vector.polygon_label() == self._p: point = tangent_vector.point() vertex1 = self._domain.polygon(self._p).vertex(self._v1) vertex2 = self._domain.polygon(self._p).vertex(self._v2) wp = wedge_product(vertex2 - vertex1, point - vertex1) if wp > 0: # in new polygon 1 return self.codomain().tangent_vector( \ self._p, \ self._tp(tangent_vector.point()), \ tangent_vector.vector(), \ ring = ring) if wp < 0: # in new polygon 2 return self.codomain().tangent_vector( \ self._new_label, \ self._tnew_label(tangent_vector.point()), \ tangent_vector.vector(), \ ring = ring) # Otherwise wp==0 w = tangent_vector.vector() wp = wedge_product(vertex2 - vertex1, w) if wp > 0: # in new polygon 1 return self.codomain().tangent_vector( \ self._p, \ self._tp(tangent_vector.point()), \ tangent_vector.vector(), \ ring = ring) # in new polygon 2 return self.codomain().tangent_vector( \ self._new_label, \ self._tnew_label(tangent_vector.point()), \ tangent_vector.vector(), \ ring = ring) else: # Not in a polygon that was changed. Just copy the data. return self._codomain.tangent_vector( \ tangent_vector.polygon_label(), \ tangent_vector.point(), \ tangent_vector.vector(), \ ring = ring)
def pull_vector_back(self, tangent_vector): r""" Applies the inverse of the mapping to the provided vector. """ ring = tangent_vector.bundle().base_ring() if tangent_vector.polygon_label() == self._saved_label: p = tangent_vector.point() v = self._domain.polygon(self._saved_label).vertex( self._glued_edge) e = self._domain.polygon(self._saved_label).edge(self._glued_edge) from flatsurf.geometry.polygon import wedge_product wp = wedge_product(p - v, e) if wp > 0: # in polygon with the removed label return self.domain().tangent_vector( \ self._removed_label, \ (~ self._remove_map)(tangent_vector.point()), \ (~ self._remove_map_derivative)*tangent_vector.vector(), \ ring = ring) if wp < 0: # in polygon with the removed label return self.domain().tangent_vector( \ self._saved_label, \ tangent_vector.point(), \ tangent_vector.vector(), \ ring = ring) # Otherwise wp==0 w = tangent_vector.vector() wp = wedge_product(w, e) if wp > 0: # in polygon with the removed label return self.domain().tangent_vector( \ self._removed_label, \ (~ self._remove_map)(tangent_vector.point()), \ (~ self._remove_map_derivative)*tangent_vector.vector(), \ ring = ring) return self.domain().tangent_vector( \ self._saved_label, \ tangent_vector.point(), \ tangent_vector.vector(), \ ring = ring) else: return self._domain.tangent_vector( \ tangent_vector.polygon_label(), \ tangent_vector.point(), \ tangent_vector.vector(), \ ring = ring)
def edge_needs_flip_Linfinity(s, p1, e1): r""" Check whether the provided edge which bouds two triangles should be flipped to get closer to the L-infinity Delaunay decomposition. EXAMPLES:: sage: from flatsurf import * sage: t1 = polygons(vertices=[(0,0), (1,0), (1,1)]) sage: t2 = polygons(vertices=[(0,0), (1,1), (0,1)]) sage: m = matrix(2, [2,1,1,1]) sage: t1 = m*t1 sage: t2 = m*t2 sage: s = similarity_surfaces([t1,t2], {(0,0):(1,1), (0,1):(1,2), (0,2):(1,0)}) sage: from flatsurf.geometry.mappings import edge_needs_flip_Linfinity sage: edge_needs_flip_Linfinity(s, 0, 0) False sage: edge_needs_flip_Linfinity(s, 0, 1) False sage: edge_needs_flip_Linfinity(s, 0, 2) True sage: edge_needs_flip_Linfinity(s, 1, 0) True sage: edge_needs_flip_Linfinity(s, 1, 1) False sage: edge_needs_flip_Linfinity(s, 1, 2) False """ p2,e2 = s.opposite_edge(p1,e1) poly1 = s.polygon(p1) poly2 = s.polygon(p2) assert poly1.num_edges() == 3 assert poly2.num_edges() == 3 # convexity check of the quadrilateral if wedge_product(poly2.edge(e2-1), poly1.edge(e1+1)) <= 0 or \ wedge_product(poly1.edge(e1-1), poly2.edge(e2+1)) <=0: return False # compare the norms edge1 = poly1.edge(e1) edge = poly2.edge(e2-1) + poly1.edge(e1+1) n1 = max(abs(edge1[0]), abs(edge1[1])) n = max(abs(edge[0]), abs(edge[1])) return n < n1
def pull_vector_back(self,tangent_vector): r""" Applies the inverse of the mapping to the provided vector. """ ring = tangent_vector.bundle().base_ring() if tangent_vector.polygon_label() == self._saved_label: p=tangent_vector.point() v=self._domain.polygon(self._saved_label).vertex(self._glued_edge) e=self._domain.polygon(self._saved_label).edge(self._glued_edge) from flatsurf.geometry.polygon import wedge_product wp = wedge_product(p-v,e) if wp > 0: # in polygon with the removed label return self.domain().tangent_vector( \ self._removed_label, \ (~ self._remove_map)(tangent_vector.point()), \ (~ self._remove_map_derivative)*tangent_vector.vector(), \ ring = ring) if wp < 0: # in polygon with the removed label return self.domain().tangent_vector( \ self._saved_label, \ tangent_vector.point(), \ tangent_vector.vector(), \ ring = ring) # Otherwise wp==0 w = tangent_vector.vector() wp = wedge_product(w,e) if wp > 0: # in polygon with the removed label return self.domain().tangent_vector( \ self._removed_label, \ (~ self._remove_map)(tangent_vector.point()), \ (~ self._remove_map_derivative)*tangent_vector.vector(), \ ring = ring) return self.domain().tangent_vector( \ self._saved_label, \ tangent_vector.point(), \ tangent_vector.vector(), \ ring = ring) else: return self._domain.tangent_vector( \ tangent_vector.polygon_label(), \ tangent_vector.point(), \ tangent_vector.vector(), \ ring = ring)
def subdivide_a_polygon(s): r""" Return a SurfaceMapping which cuts one polygon along a diagonal or None if the surface is triangulated. """ from flatsurf.geometry.polygon import wedge_product for l,poly in s.label_iterator(polygons=True): n = poly.num_edges() if n>3: for i in xrange(n): e1=poly.edge(i) e2=poly.edge((i+1)%n) if wedge_product(e1,e2) != 0: return SplitPolygonsMapping(s,l,i, (i+2)%n) raise ValueError("Unable to triangulate polygon with label "+str(l)+\ ": "+str(poly)) return None
def subdivide_a_polygon(s): r""" Return a SurfaceMapping which cuts one polygon along a diagonal or None if the surface is triangulated. """ from flatsurf.geometry.polygon import wedge_product for l, poly in s.label_iterator(polygons=True): n = poly.num_edges() if n > 3: for i in range(n): e1 = poly.edge(i) e2 = poly.edge((i + 1) % n) if wedge_product(e1, e2) != 0: return SplitPolygonsMapping(s, l, i, (i + 2) % n) raise ValueError("Unable to triangulate polygon with label "+str(l)+\ ": "+str(poly)) return None
def triangulate(self, in_place=False): r""" Return a triangulated version of this surface. EXAMPLES:: sage: from flatsurf import * sage: s=translation_surfaces.mcmullen_L(1,1,1,1) sage: ss=s.triangulate() sage: gs=ss.graphical_surface() sage: gs.make_all_visible() sage: print(gs) Graphical version of Similarity Surface TranslationSurface built from 6 polygons """ if in_place: s=self else: s=self.mutable_copy() from flatsurf.geometry.polygon import wedge_product loop=True while loop: loop=False for l,poly in s.label_iterator(polygons=True): n = poly.num_edges() if n>3: for i in xrange(n): e1=poly.edge(i) e2=poly.edge((i+1)%n) if wedge_product(e1,e2) != 0: s.subdivide_polygon(l,i,(i+2)%n) loop=True break if loop: break else: # This should never happen: raise ValueError("Unable to triangulate polygon with label "+ \ str(l)+": "+str(poly)) return s
def triangle_flip(self, l1, e1, in_place=False, test=False): r""" Flips the diagonal of the quadrilateral formed by two triangles glued together along the provided edge (l1,e1). This can be broken into two steps: join along the edge to form a convex quadilateral, then cut along the other diagonal. Raises a ValueError if this quadrilateral would be non-convex. Parameters ---------- l1 label of polygon e1 : integer edge of the polygon in_place : boolean if True do the flip to the current surface which must be mutable. In this case the updated surface will be returned. Otherwise a mutable copy is made and then an edge is flipped, which is then returned. test : boolean if True we don't actually flip, and we return True or False depending on whether or not the flip would be successful. EXAMPLES:: sage: from flatsurf import * sage: s=similarity_surfaces.right_angle_triangle(ZZ(1),ZZ(1)) sage: print(s.polygon(0)) Polygon: (0, 0), (1, 0), (0, 1) sage: print s.triangle_flip(0, 0, test=True) False sage: print s.triangle_flip(0, 1, test=True) True sage: print s.triangle_flip(0, 2, test=True) False sage: from flatsurf import * sage: s=similarity_surfaces.right_angle_triangle(ZZ(1),ZZ(1)) sage: from flatsurf.geometry.surface import Surface_fast sage: s=s.__class__(Surface_fast(s, mutable=True)) sage: try: ....: s.triangle_flip(0,0,in_place=True) ....: except ValueError as e: ....: print e Gluing triangles along this edge yields a non-convex quadrilateral. sage: print s.triangle_flip(0,1,in_place=True) ConeSurface built from 2 polygons sage: print s.polygon(0) Polygon: (0, 0), (1, 1), (0, 1) sage: print s.polygon(1) Polygon: (0, 0), (-1, -1), (0, -1) sage: for p in s.edge_iterator(gluings=True): ....: print p ((0, 0), (1, 0)) ((0, 1), (0, 2)) ((0, 2), (0, 1)) ((1, 0), (0, 0)) ((1, 1), (1, 2)) ((1, 2), (1, 1)) sage: try: ....: s.triangle_flip(0,2,in_place=True) ....: except ValueError as e: ....: print e ....: Gluing triangles along this edge yields a non-convex quadrilateral. sage: from flatsurf import * sage: p=polygons((2,0),(-1,3),(-1,-3)) sage: s=similarity_surfaces.self_glued_polygon(p) sage: from flatsurf.geometry.surface import Surface_fast sage: s=s.__class__(Surface_fast(s,mutable=True)) sage: s.triangle_flip(0,1,in_place=True) HalfTranslationSurface built from 1 polygon sage: for x in s.label_iterator(polygons=True): ....: print x (0, Polygon: (0, 0), (3, 3), (1, 3)) sage: for x in s.edge_iterator(gluings=True): ....: print x ((0, 0), (0, 0)) ((0, 1), (0, 1)) ((0, 2), (0, 2)) sage: TestSuite(s).run() """ if test: # Just test if the flip would be successful p1=self.polygon(l1) if not p1.num_edges()==3: return false l2,e2 = self.opposite_edge(l1,e1) p2 = self.polygon(l2) if not p2.num_edges()==3: return false sim = self.edge_transformation(l2,e2) hol = sim( p2.vertex( (e2+2)%3 ) - p1.vertex((e1+2)%3) ) from flatsurf.geometry.polygon import wedge_product return wedge_product(p1.edge((e1+2)%3), hol) > 0 and \ wedge_product(p1.edge((e1+1)%3), hol) > 0 if in_place: s=self.underlying_surface() else: from flatsurf.geometry.surface import Surface_fast s=Surface_fast(surface=self.underlying_surface(), \ mutable=True, dictionary=True) p1=self.polygon(l1) if not p1.num_edges()==3: raise ValueError("The polygon with the provided label is not a triangle.") l2,e2 = self.opposite_edge(l1,e1) p2 = self.polygon(l2) if not p2.num_edges()==3: raise ValueError("The polygon opposite the provided edge is not a triangle.") sim = self.edge_transformation(l2,e2) m = sim.derivative() hol = sim( p2.vertex( (e2+2)%3 ) ) - p1.vertex((e1+2)%3) from flatsurf import polygons try: np1 = polygons(edges=[hol, m * p2.edge((e2+2)%3), p1.edge((e1+1)%3)]) np2 = polygons(edges=[-hol, p1.edge((e1+2)%3), m * p2.edge((e2+1)%3)]) except (ValueError, TypeError): raise ValueError("Gluing triangles along this edge yields a non-convex quadrilateral.") # Old gluings: pairs = [self.opposite_edge(l2,(e2+2)%3), \ self.opposite_edge(l1,(e1+1)%3), \ self.opposite_edge(l1,(e1+2)%3), \ self.opposite_edge(l2,(e2+1)%3)] for i, (l,e) in enumerate(pairs): if l==l1: if e==(e1+1)%3: pairs[i]=(l1,2) elif e==(e1+2)%3: pairs[i]=(l2,1) else: raise ValueError("Surfaced passed has errors in polygon gluings.") elif l==l2: if e==(e2+1)%3: pairs[i]=(l2,2) elif e==(e2+2)%3: pairs[i]=(l1,1) else: raise ValueError("Surfaced passed has errors in polygon gluings.") if l1==l2: s.change_polygon(l1,np1) s.change_edge_gluing(l1,0,l1,0) s.change_edge_gluing(l1,1,pairs[0][0],pairs[0][1]) s.change_edge_gluing(l1,2,pairs[1][0],pairs[1][1]) else: s.change_polygon(l1,np1) s.change_polygon(l2,np2) s.change_edge_gluing(l1,0,l2,0) s.change_edge_gluing(l1,1,pairs[0][0],pairs[0][1]) s.change_edge_gluing(l1,2,pairs[1][0],pairs[1][1]) s.change_edge_gluing(l2,1,pairs[2][0],pairs[2][1]) s.change_edge_gluing(l2,2,pairs[3][0],pairs[3][1]) if in_place: return self else: return self.__class__(s)
def _edge_needs_flip_Linfinity(self, p1, e1, p2, e2): r""" Check whether the provided edge which bounds two triangles should be flipped to get closer to the L-infinity Delaunay decomposition. TESTS:: sage: from flatsurf import * sage: s = Surface_list(base_ring=QQ) sage: t1 = polygons((1,0),(-1,1),(0,-1)) sage: t2 = polygons((0,1),(-1,0),(1,-1)) sage: s.add_polygon(polygons(vertices=[(0,0), (1,0), (0,1)])) 0 sage: s.add_polygon(polygons(vertices=[(1,1), (0,1), (1,0)])) 1 sage: s.change_polygon_gluings(0, [(1,0), (1,1), (1,2)]) sage: s = TranslationSurface(s) sage: [s._edge_needs_flip_Linfinity(0, i, 1, i) for i in range(3)] [False, False, False] sage: ss = matrix(2, [1,1,0,1]) * s sage: [ss._edge_needs_flip_Linfinity(0, i, 1, i) for i in range(3)] [False, False, False] sage: ss = matrix(2, [1,0,1,1]) * s sage: [ss._edge_needs_flip_Linfinity(0, i, 1, i) for i in range(3)] [False, False, False] sage: ss = matrix(2, [1,2,0,1]) * s sage: [ss._edge_needs_flip_Linfinity(0, i, 1, i) for i in range(3)] [False, False, True] sage: ss = matrix(2, [1,0,2,1]) * s sage: [ss._edge_needs_flip_Linfinity(0, i, 1, i) for i in range(3)] [True, False, False] """ # safety check for now assert self.opposite_edge(p1, e1) == (p2, e2), "not opposite edges" # triangles poly1 = self.polygon(p1) poly2 = self.polygon(p2) if poly1.num_edges() != 3 or poly2.num_edges() != 3: raise ValueError("edge must be adjacent to two triangles") edge1 = poly1.edge(e1) edge1L = poly1.edge(e1 - 1) edge1R = poly1.edge(e1 + 1) edge2 = poly2.edge(e2) edge2L = poly2.edge(e2 - 1) edge2R = poly2.edge(e2 + 1) sim = self.edge_transformation(p2, e2) m = sim.derivative() # matrix carrying p2 to p1 if not m.is_one(): edge2 = m * edge2 edge2L = m * edge2L edge2R = m * edge2R # convexity check of the quadrilateral from flatsurf.geometry.polygon import wedge_product if wedge_product(edge2L, edge1R) <= 0 or \ wedge_product(edge1L, edge2R) <=0: return False # compare the norms new_edge = edge2L + edge1R n1 = max(abs(edge1[0]), abs(edge1[1])) n = max(abs(new_edge[0]), abs(new_edge[1])) return n < n1
def __init__(self, s, label0, edges): r""" Construct a cylinder on the surface `s` from an initial label and a sequence of edges crossed. Parameters ---------- s: A SimilaritySurface the surface conaining the cylinder label0: An initial label representing a polygon the cylinder passes through. edges: a list giving the sequence of edges the cylinder crosses until it closes. """ self._s = s self._label0 = label0 self._edges = tuple(edges) ss = s.minimal_cover(cover_type="planar") SG = SimilarityGroup(s.base_ring()) labels = [(label0, SG.one())] # labels of polygons on the cover ss. for e in edges: labels.append(ss.opposite_edge(labels[-1],e)[0]) if labels[0][0] != labels[-1][0]: raise ValueError("Combinatorial path does not close.") trans = labels[-1][1] if not trans.is_translation(): raise NotImplemented("Only cylinders with translational monodromy are currently supported") m = trans.matrix() v = vector(s.base_ring(),(m[0][2],m[1][2])) # translation vector from flatsurf.geometry.polygon import wedge_product p = ss.polygon(labels[0]) e = edges[0] min_y = wedge_product(v, p.vertex(e)) max_y = wedge_product(v, p.vertex((e+1)%p.num_edges())) if min_y >= max_y: raise ValueError("Combinatorial data does not represent a cylinder") # Stores the vertices where saddle connections starts: min_list = [0] max_list = [0] for i in xrange(1, len(edges)): e = edges[i] p = ss.polygon(labels[i]) y = wedge_product(v, p.vertex(e)) if y == min_y: min_list.append(i) elif y > min_y: min_list = [i] min_y = y if min_y >= max_y: raise ValueError("Combinatorial data does not represent a cylinder") y = wedge_product(v, p.vertex((e+1)%p.num_edges())) if y == max_y: max_list.append(i) elif y < max_y: max_list = [i] max_y = y if min_y >= max_y: raise ValueError("Combinatorial data does not represent a cylinder") # Extract the saddle connections on the right side: from flatsurf.geometry.surface_objects import SaddleConnection sc_set_right = set() vertices = [] for i in min_list: l = labels[i] p = ss.polygon(l) vertices.append((i,p.vertex(edges[i]))) i,vert_i = vertices[-1] vert_i = vert_i - v j,vert_j = vertices[0] if vert_i != vert_j: li = labels[i] li = (li[0], SG(-v)*li[1]) lio = ss.opposite_edge(li,edges[i]) lj = labels[j] sc = SaddleConnection(s, (lio[0][0], (lio[1]+1) % ss.polygon(lio[0]).num_edges()), (~lio[0][1])(vert_j)-(~lio[0][1])(vert_i)) sc_set_right.add(sc) i = j vert_i = vert_j for j,vert_j in vertices[1:]: if vert_i != vert_j: li = labels[i] li = (li[0], SG(-v)*li[1]) lio = ss.opposite_edge(li,edges[i]) lj = labels[j] sc = SaddleConnection(s, (lio[0][0], (lio[1]+1) % ss.polygon(lio[0]).num_edges()), (~lio[0][1])(vert_j)-(~lio[0][1])(vert_i), limit = j-i) sc_set_right.add(sc) i = j vert_i =vert_j # Extract the saddle connections on the left side: sc_set_left = set() vertices = [] for i in max_list: l = labels[i] p = ss.polygon(l) vertices.append((i,p.vertex((edges[i]+1)%p.num_edges()))) i,vert_i = vertices[-1] vert_i = vert_i - v j,vert_j = vertices[0] if vert_i != vert_j: li = labels[i] li = (li[0], SG(-v)*li[1]) lio = ss.opposite_edge(li,edges[i]) lj = labels[j] sc = SaddleConnection(s, (lj[0], (edges[j]+1) % ss.polygon(lj).num_edges()), (~lj[1])(vert_i)-(~lj[1])(vert_j)) sc_set_left.add(sc) i = j vert_i =vert_j for j,vert_j in vertices[1:]: if vert_i != vert_j: li = labels[i] lio = ss.opposite_edge(li,edges[i]) lj = labels[j] sc = SaddleConnection(s, (lj[0], (edges[j]+1) % ss.polygon(lj).num_edges()), (~lj[1])(vert_i)-(~lj[1])(vert_j)) sc_set_left.add(sc) i = j vert_i =vert_j self._boundary1 = frozenset(sc_set_right) self._boundary2 = frozenset(sc_set_left) self._boundary = frozenset(self._boundary1.union(self._boundary2)) edge_intersections = [] i = min_list[0] l = labels[i] p = ss.polygon(l) right_point = p.vertex(edges[i]) # point on the right boundary i = max_list[0] l = labels[i] p = ss.polygon(l) left_point = p.vertex((edges[i]+1)%p.num_edges()) from flatsurf.geometry.polygon import solve for i in xrange(len(edges)): l = labels[i] p = ss.polygon(l) e = edges[i] v1 = p.vertex(e) v2 = p.vertex((e+1)%p.num_edges()) a,b = solve(left_point, v, v1, v2-v1) w1 = (~(l[1]))(v1 + b*(v2-v1)) a,b = solve(right_point, v, v1, v2-v1) w2 = (~(l[1]))(v1 + b*(v2-v1)) edge_intersections.append((w1,w2)) polygons = [] P = Polygons(s.base_ring()) pair1 = edge_intersections[-1] l1 = labels[-2][0] e1 = edges[-1] for i in xrange(len(edges)): l2 = labels[i][0] pair2 = edge_intersections[i] e2 = edges[i] trans = s.edge_transformation(l1,e1) pair1p = (trans(pair1[0]), trans(pair1[1])) polygon_verts = [pair1p[0], pair1p[1]] if pair2[1] != pair1p[1]: polygon_verts.append(pair2[1]) if pair2[0] != pair1p[0]: polygon_verts.append(pair2[0]) polygons.append((l2,P(vertices=polygon_verts))) l1 = l2 pair1 = pair2 e1 = e2 self._polygons = tuple(polygons)
def __init__(self, surface, start_data, direction, end_data=None, end_direction=None, holonomy=None, end_holonomy=None, check=True, limit=1000): r""" Construct a saddle connecton on a SimilaritySurface. The only necessary parameters are the surface, start_data, and direction (to start). If there is missing data that can not be inferred from the surface type, then a straight-line trajectory will be computed to confirm that this is indeed a saddle connection. The trajectory will pass through at most limit polygons before we give up. Details of the parameters are provided below. Parameters ---------- surface : a SimilaritySurface which will contain the saddle connection being constructed. start_data : a pair consisting of the label of the polygon where the saddle connection starts and the starting vertex. direction : 2-dimensional vector with entries in the base_ring of the surface representing the direction the saddle connection is moving in (in the coordinates of the initial polygon). end_data : a pair consisting of the label of the polygon where the saddle connection terminates and the terminating vertex. end_direction : 2-dimensional vector with entries in the base_ring of the surface representing the direction to move backward from the end point (in the coordinates of the terminal polygon). If the surface is a DilationSurface or better this will be the negation of the direction vector. If the surface is a HalfDilation surface or better, then this will be either the direction vector or its negation. In either case the value can be inferred from the end_data. holonomy : 2-dimensional vector with entries in the base_ring of the surface the holonomy of the saddle connection measured from the start. To compute this you develop the saddle connection into the plane starting from the starting polygon. end_holonomy : 2-dimensional vector with entries in the base_ring of the surface the holonomy of the saddle connection measured from the end (with the opposite orientation). To compute this you develop the saddle connection into the plane starting from the terminating polygon. For a translation surface, this will be the negation of holonomy, and for a HalfTranslation surface it will be either equal to holonomy or equal to its negation. In both these cases the end_holonomy can be inferred and does not need to be passed to the constructor. check : boolean If all data above is provided or can be inferred, then when check=False this geometric data is not verified. With check=True the data is always verified by straight-line flow. Erroroneous data will result in a ValueError being thrown. Defaults to true. limit : The combinatorial limit (in terms of number of polygons crossed) to flow forward to check the saddle connection geometry. """ from .similarity_surface import SimilaritySurface assert isinstance(surface,SimilaritySurface) self._s=surface # Sanitize the direction vector: V=self._s.vector_space() self._direction=V(direction) if self._direction==V.zero(): raise ValueError("Direction must be nonzero.") # To canonicalize the direction vector we ensure its endpoint lies in the boundary of the unit square. xabs=self._direction[0].abs() yabs=self._direction[1].abs() if xabs>yabs: self._direction=self._direction/xabs else: self._direction=self._direction/yabs # Fix end_direction if not standard. if end_direction is not None: xabs=end_direction[0].abs() yabs=end_direction[1].abs() if xabs>yabs: end_direction=end_direction/xabs else: end_direction=end_direction/yabs self._start_data=tuple(start_data) if end_direction is None: from .half_dilation_surface import HalfDilationSurface from .dilation_surface import DilationSurface # Attempt to infer the end_direction. if isinstance(self._s,DilationSurface): end_direction=-self._direction elif isinstance(self._s,HalfDilationSurface) and end_data is not None: p=self._s.polygon(end_data[0]) if wedge_product(p.edge(end_data[1]), self._direction)>=0 and \ wedge_product(p.edge( (p.num_edges()+end_data[1]-1)%p.num_edges() ), self._direction)>0: end_direction=self._direction else: end_direction=-self._direction if end_holonomy is None and holonomy is not None: # Attempt to infer the end_holonomy: from .half_translation_surface import HalfTranslationSurface from .translation_surface import TranslationSurface if isinstance(self._s,TranslationSurface): end_holonomy=-holonomy if isinstance(self._s,HalfTranslationSurface): if direction==end_direction: end_holonomy=holonomy else: end_holonomy=-holonomy if end_data is None or end_direction is None or holonomy is None or end_holonomy is None or check: v=self.start_tangent_vector() traj=v.straight_line_trajectory() traj.flow(limit) if not traj.is_saddle_connection(): raise ValueError("Did not obtain saddle connection by flowing forward. Limit="+str(limit)) tv=traj.terminal_tangent_vector() self._end_data=(tv.polygon_label(), tv.vertex()) if end_data is not None: if end_data!=self._end_data: raise ValueError("Provided or inferred end_data="+str(end_data)+" does not match actual end_data="+str(self._end_data)) self._end_direction=tv.vector() # Canonicalize again. xabs=self._end_direction[0].abs() yabs=self._end_direction[1].abs() if xabs>yabs: self._end_direction = self._end_direction / xabs else: self._end_direction = self._end_direction / yabs if end_direction is not None: if end_direction!=self._end_direction: raise ValueError("Provided or inferred end_direction="+str(end_direction)+" does not match actual end_direction="+str(self._end_direction)) if traj.segments()[0].is_edge(): # Special case (The below method causes error if the trajectory is just an edge.) self._holonomy = self._s.polygon(start_data[0]).edge(start_data[1]) self._end_holonomy = self._s.polygon(self._end_data[0]).edge(self._end_data[1]) else: from .similarity import SimilarityGroup sim=SimilarityGroup(self._s.base_ring()).one() itersegs = iter(traj.segments()) next(itersegs) for seg in itersegs: sim = sim * self._s.edge_transformation(seg.start().polygon_label(), seg.start().position().get_edge()) self._holonomy = sim(traj.segments()[-1].end().point())- \ traj.initial_tangent_vector().point() self._end_holonomy = -( (~sim.derivative())*self._holonomy ) if holonomy is not None: if holonomy!=self._holonomy: print("Combinatorial length: "+str(traj.combinatorial_length())) print("Start: "+str(traj.initial_tangent_vector().point())) print("End: "+str(traj.terminal_tangent_vector().point())) print("Start data:"+str(start_data)) print("End data:"+str(end_data)) raise ValueError("Provided holonomy "+str(holonomy)+ " does not match computed holonomy of "+str(self._holonomy)) if end_holonomy is not None: if end_holonomy!=self._end_holonomy: raise ValueError("Provided or inferred end_holonomy "+str(end_holonomy)+ " does not match computed end_holonomy of "+str(self._end_holonomy)) else: self._end_data=tuple(end_data) self._end_direction=end_direction self._holonomy=holonomy self._end_holonomy=end_holonomy # Make vectors immutable self._direction.set_immutable() self._end_direction.set_immutable() self._holonomy.set_immutable() self._end_holonomy.set_immutable()
def rel_deformation(self, deformation, local=False, limit=100): r""" Perform a rel deformation of the surface and return the result. This algorithm currently assumes that all polygons affected by this deformation are triangles. That should be fixable in the future. INPUT: - ``deformation`` (dictionary) - A dictionary mapping singularities of the surface to deformation vectors (in some 2-dimensional vector space). The rel deformation being done will move the singularities (relative to each other) linearly to the provided vector for each vertex. If a singularity is not included in the dictionary then the vector will be treated as zero. - ``local`` - (boolean) - If true, the algorithm attempts to deform all the triangles making up the surface without destroying any of them. So, the area of the triangle must be positive along the full interval of time of the deformation. If false, then the deformation must have a particular form: all vectors for the deformation must be paralell. In this case we achieve the deformation with the help of the SL(2,R) action and Delaunay triangulations. - ``limit`` (integer) - Restricts the length of the size of SL(2,R) deformations considered. The algorithm should be roughly worst time linear in limit. TODO: - Support arbitrary rel deformations. - Remove the requirement that triangles be used. EXAMPLES:: sage: from flatsurf import * sage: s = translation_surfaces.arnoux_yoccoz(4) sage: field = s.base_ring() sage: a = field.gen() sage: V = VectorSpace(field,2) sage: deformation1 = {s.singularity(0,0):V((1,0))} sage: s1 = s.rel_deformation(deformation1).canonicalize() sage: deformation2 = {s.singularity(0,0):V((a,0))} sage: s2 = s.rel_deformation(deformation2).canonicalize() sage: m = Matrix([[a,0],[0,~a]]) sage: s2.cmp((m*s1).canonicalize()) 0 """ s = self # Find a common field field = s.base_ring() for singularity, v in iteritems(deformation): if v.parent().base_field() != field: from sage.structure.element import get_coercion_model cm = get_coercion_model() field = cm.common_parent(field, v.parent().base_field()) from sage.modules.free_module import VectorSpace vector_space = VectorSpace(field, 2) from collections import defaultdict vertex_deformation = defaultdict( vector_space.zero) # dictionary associating the vertices. deformed_labels = set() # list of polygon labels being deformed. for singularity, vect in iteritems(deformation): # assert s==singularity.surface() for label, v in singularity.vertex_set(): vertex_deformation[(label, v)] = vect deformed_labels.add(label) assert s.polygon(label).num_edges() == 3 from flatsurf.geometry.polygon import wedge_product, ConvexPolygons if local: ss = s.copy(mutable=True, new_field=field) us = ss.underlying_surface() P = ConvexPolygons(field) for label in deformed_labels: polygon = s.polygon(label) a0 = vector_space(polygon.vertex(1)) b0 = vector_space(polygon.vertex(2)) v0 = vector_space(vertex_deformation[(label, 0)]) v1 = vector_space(vertex_deformation[(label, 1)]) v2 = vector_space(vertex_deformation[(label, 2)]) a1 = v1 - v0 b1 = v2 - v0 # We deform by changing the triangle so that its vertices 1 and 2 have the form # a0+t*a1 and b0+t*b1 # respectively. We are deforming from t=0 to t=1. # We worry that the triangle degenerates along the way. # The area of the deforming triangle has the form # A0 + A1*t + A2*t^2. A0 = wedge_product(a0, b0) A1 = wedge_product(a0, b1) + wedge_product(a1, b0) A2 = wedge_product(a1, b1) if A2 != field.zero(): # Critical point of area function c = A1 / (-2 * A2) if field.zero() < c and c < field.one(): assert A0 + A1 * c + A2 * c**2 > field.zero( ), "Triangle with label %r degenerates at critical point before endpoint" % label assert A0 + A1 + A2 > field.zero( ), "Triangle with label %r degenerates at or before endpoint" % label # Triangle does not degenerate. us.change_polygon( label, P(vertices=[vector_space.zero(), a0 + a1, b0 + b1])) return ss else: # Non local deformation # We can only do this deformation if all the rel vector are parallel. # Check for this. nonzero = None for singularity, vect in iteritems(deformation): vvect = vector_space(vect) if vvect != vector_space.zero(): if nonzero is None: nonzero = vvect else: assert wedge_product(nonzero,vvect)==0, \ "In non-local deformation all deformation vectos must be parallel" assert nonzero is not None, "Deformation appears to be trivial." from sage.matrix.constructor import Matrix m = Matrix([[nonzero[0], -nonzero[1]], [nonzero[1], nonzero[0]]]) mi = ~m g = Matrix([[1, 0], [0, 2]], ring=field) prod = m * g * mi ss = None k = 0 while True: if ss is None: ss = s.copy(mutable=True, new_field=field) else: # In place matrix deformation ss.apply_matrix(prod) ss.delaunay_triangulation(direction=nonzero, in_place=True) deformation2 = {} for singularity, vect in iteritems(deformation): found_start = None for label, v in singularity.vertex_set(): if wedge_product(s.polygon(label).edge(v),nonzero) >= 0 and \ wedge_product(nonzero,-s.polygon(label).edge((v+2)%3)) > 0: found_start = (label, v) found = None for vv in range(3): if wedge_product(ss.polygon(label).edge(vv),nonzero) >= 0 and \ wedge_product(nonzero,-ss.polygon(label).edge((vv+2)%3)) > 0: found = vv deformation2[ss.singularity(label, vv)] = vect break assert found is not None break assert found_start is not None try: sss = ss.rel_deformation(deformation2, local=True) sss.apply_matrix(mi * g**(-k) * m) sss.delaunay_triangulation(direction=nonzero, in_place=True) return sss except AssertionError as e: pass k = k + 1 if limit is not None and k >= limit: assert False, "Exeeded limit iterations"
def __init__(self, s, label0, edges): r""" Construct a cylinder on the surface `s` from an initial label and a sequence of edges crossed. Parameters ---------- s: A SimilaritySurface the surface conaining the cylinder label0: An initial label representing a polygon the cylinder passes through. edges: a list giving the sequence of edges the cylinder crosses until it closes. """ self._s = s self._label0 = label0 self._edges = tuple(edges) ss = s.minimal_cover(cover_type="planar") SG = SimilarityGroup(s.base_ring()) labels = [(label0, SG.one())] # labels of polygons on the cover ss. for e in edges: labels.append(ss.opposite_edge(labels[-1], e)[0]) if labels[0][0] != labels[-1][0]: raise ValueError("Combinatorial path does not close.") trans = labels[-1][1] if not trans.is_translation(): raise NotImplemented( "Only cylinders with translational monodromy are currently supported" ) m = trans.matrix() v = vector(s.base_ring(), (m[0][2], m[1][2])) # translation vector from flatsurf.geometry.polygon import wedge_product p = ss.polygon(labels[0]) e = edges[0] min_y = wedge_product(v, p.vertex(e)) max_y = wedge_product(v, p.vertex((e + 1) % p.num_edges())) if min_y >= max_y: raise ValueError( "Combinatorial data does not represent a cylinder") # Stores the vertices where saddle connections starts: min_list = [0] max_list = [0] for i in range(1, len(edges)): e = edges[i] p = ss.polygon(labels[i]) y = wedge_product(v, p.vertex(e)) if y == min_y: min_list.append(i) elif y > min_y: min_list = [i] min_y = y if min_y >= max_y: raise ValueError( "Combinatorial data does not represent a cylinder") y = wedge_product(v, p.vertex((e + 1) % p.num_edges())) if y == max_y: max_list.append(i) elif y < max_y: max_list = [i] max_y = y if min_y >= max_y: raise ValueError( "Combinatorial data does not represent a cylinder") # Extract the saddle connections on the right side: from flatsurf.geometry.surface_objects import SaddleConnection sc_set_right = set() vertices = [] for i in min_list: l = labels[i] p = ss.polygon(l) vertices.append((i, p.vertex(edges[i]))) i, vert_i = vertices[-1] vert_i = vert_i - v j, vert_j = vertices[0] if vert_i != vert_j: li = labels[i] li = (li[0], SG(-v) * li[1]) lio = ss.opposite_edge(li, edges[i]) lj = labels[j] sc = SaddleConnection( s, (lio[0][0], (lio[1] + 1) % ss.polygon(lio[0]).num_edges()), (~lio[0][1])(vert_j) - (~lio[0][1])(vert_i)) sc_set_right.add(sc) i = j vert_i = vert_j for j, vert_j in vertices[1:]: if vert_i != vert_j: li = labels[i] li = (li[0], SG(-v) * li[1]) lio = ss.opposite_edge(li, edges[i]) lj = labels[j] sc = SaddleConnection( s, (lio[0][0], (lio[1] + 1) % ss.polygon(lio[0]).num_edges()), (~lio[0][1])(vert_j) - (~lio[0][1])(vert_i), limit=j - i) sc_set_right.add(sc) i = j vert_i = vert_j # Extract the saddle connections on the left side: sc_set_left = set() vertices = [] for i in max_list: l = labels[i] p = ss.polygon(l) vertices.append((i, p.vertex((edges[i] + 1) % p.num_edges()))) i, vert_i = vertices[-1] vert_i = vert_i - v j, vert_j = vertices[0] if vert_i != vert_j: li = labels[i] li = (li[0], SG(-v) * li[1]) lio = ss.opposite_edge(li, edges[i]) lj = labels[j] sc = SaddleConnection( s, (lj[0], (edges[j] + 1) % ss.polygon(lj).num_edges()), (~lj[1])(vert_i) - (~lj[1])(vert_j)) sc_set_left.add(sc) i = j vert_i = vert_j for j, vert_j in vertices[1:]: if vert_i != vert_j: li = labels[i] lio = ss.opposite_edge(li, edges[i]) lj = labels[j] sc = SaddleConnection( s, (lj[0], (edges[j] + 1) % ss.polygon(lj).num_edges()), (~lj[1])(vert_i) - (~lj[1])(vert_j)) sc_set_left.add(sc) i = j vert_i = vert_j self._boundary1 = frozenset(sc_set_right) self._boundary2 = frozenset(sc_set_left) self._boundary = frozenset(self._boundary1.union(self._boundary2)) edge_intersections = [] i = min_list[0] l = labels[i] p = ss.polygon(l) right_point = p.vertex(edges[i]) # point on the right boundary i = max_list[0] l = labels[i] p = ss.polygon(l) left_point = p.vertex((edges[i] + 1) % p.num_edges()) from flatsurf.geometry.polygon import solve for i in range(len(edges)): l = labels[i] p = ss.polygon(l) e = edges[i] v1 = p.vertex(e) v2 = p.vertex((e + 1) % p.num_edges()) a, b = solve(left_point, v, v1, v2 - v1) w1 = (~(l[1]))(v1 + b * (v2 - v1)) a, b = solve(right_point, v, v1, v2 - v1) w2 = (~(l[1]))(v1 + b * (v2 - v1)) edge_intersections.append((w1, w2)) polygons = [] P = ConvexPolygons(s.base_ring()) pair1 = edge_intersections[-1] l1 = labels[-2][0] e1 = edges[-1] for i in range(len(edges)): l2 = labels[i][0] pair2 = edge_intersections[i] e2 = edges[i] trans = s.edge_transformation(l1, e1) pair1p = (trans(pair1[0]), trans(pair1[1])) polygon_verts = [pair1p[0], pair1p[1]] if pair2[1] != pair1p[1]: polygon_verts.append(pair2[1]) if pair2[0] != pair1p[0]: polygon_verts.append(pair2[0]) polygons.append((l2, P(vertices=polygon_verts))) l1 = l2 pair1 = pair2 e1 = e2 self._polygons = tuple(polygons)
def __init__(self, surface, start_data, direction, end_data=None, end_direction=None, holonomy=None, end_holonomy=None, check=True, limit=1000): r""" Construct a saddle connecton on a SimilaritySurface. The only necessary parameters are the surface, start_data, and direction (to start). If there is missing data that can not be inferred from the surface type, then a straight-line trajectory will be computed to confirm that this is indeed a saddle connection. The trajectory will pass through at most limit polygons before we give up. Details of the parameters are provided below. Parameters ---------- surface : a SimilaritySurface which will contain the saddle connection being constructed. start_data : a pair consisting of the label of the polygon where the saddle connection starts and the starting vertex. direction : 2-dimensional vector with entries in the base_ring of the surface representing the direction the saddle connection is moving in (in the coordinates of the initial polygon). end_data : a pair consisting of the label of the polygon where the saddle connection terminates and the terminating vertex. end_direction : 2-dimensional vector with entries in the base_ring of the surface representing the direction to move backward from the end point (in the coordinates of the terminal polygon). If the surface is a DilationSurface or better this will be the negation of the direction vector. If the surface is a HalfDilation surface or better, then this will be either the direction vector or its negation. In either case the value can be inferred from the end_data. holonomy : 2-dimensional vector with entries in the base_ring of the surface the holonomy of the saddle connection measured from the start. To compute this you develop the saddle connection into the plane starting from the starting polygon. end_holonomy : 2-dimensional vector with entries in the base_ring of the surface the holonomy of the saddle connection measured from the end (with the opposite orientation). To compute this you develop the saddle connection into the plane starting from the terminating polygon. For a translation surface, this will be the negation of holonomy, and for a HalfTranslation surface it will be either equal to holonomy or equal to its negation. In both these cases the end_holonomy can be inferred and does not need to be passed to the constructor. check : boolean If all data above is provided or can be inferred, then when check=False this geometric data is not verified. With check=True the data is always verified by straight-line flow. Erroroneous data will result in a ValueError being thrown. Defaults to true. limit : The combinatorial limit (in terms of number of polygons crossed) to flow forward to check the saddle connection geometry. """ from .similarity_surface import SimilaritySurface assert isinstance(surface, SimilaritySurface) self._s = surface # Sanitize the direction vector: V = self._s.vector_space() self._direction = V(direction) if self._direction == V.zero(): raise ValueError("Direction must be nonzero.") # To canonicalize the direction vector we ensure its endpoint lies in the boundary of the unit square. xabs = self._direction[0].abs() yabs = self._direction[1].abs() if xabs > yabs: self._direction = self._direction / xabs else: self._direction = self._direction / yabs # Fix end_direction if not standard. if end_direction is not None: xabs = end_direction[0].abs() yabs = end_direction[1].abs() if xabs > yabs: end_direction = end_direction / xabs else: end_direction = end_direction / yabs self._start_data = tuple(start_data) if end_direction is None: from .half_dilation_surface import HalfDilationSurface from .dilation_surface import DilationSurface # Attempt to infer the end_direction. if isinstance(self._s, DilationSurface): end_direction = -self._direction elif isinstance(self._s, HalfDilationSurface) and end_data is not None: p = self._s.polygon(end_data[0]) if wedge_product(p.edge(end_data[1]), self._direction)>=0 and \ wedge_product(p.edge( (p.num_edges()+end_data[1]-1)%p.num_edges() ), self._direction)>0: end_direction = self._direction else: end_direction = -self._direction if end_holonomy is None and holonomy is not None: # Attempt to infer the end_holonomy: from .half_translation_surface import HalfTranslationSurface from .translation_surface import TranslationSurface if isinstance(self._s, TranslationSurface): end_holonomy = -holonomy if isinstance(self._s, HalfTranslationSurface): if direction == end_direction: end_holonomy = holonomy else: end_holonomy = -holonomy if end_data is None or end_direction is None or holonomy is None or end_holonomy is None or check: v = self.start_tangent_vector() traj = v.straight_line_trajectory() traj.flow(limit) if not traj.is_saddle_connection(): raise ValueError( "Did not obtain saddle connection by flowing forward. Limit=" + str(limit)) tv = traj.terminal_tangent_vector() self._end_data = (tv.polygon_label(), tv.vertex()) if end_data is not None: if end_data != self._end_data: raise ValueError("Provided or inferred end_data=" + str(end_data) + " does not match actual end_data=" + str(self._end_data)) self._end_direction = tv.vector() # Canonicalize again. xabs = self._end_direction[0].abs() yabs = self._end_direction[1].abs() if xabs > yabs: self._end_direction = self._end_direction / xabs else: self._end_direction = self._end_direction / yabs if end_direction is not None: if end_direction != self._end_direction: raise ValueError("Provided or inferred end_direction=" + str(end_direction) + " does not match actual end_direction=" + str(self._end_direction)) if traj.segments()[0].is_edge(): # Special case (The below method causes error if the trajectory is just an edge.) self._holonomy = self._s.polygon(start_data[0]).edge( start_data[1]) self._end_holonomy = self._s.polygon(self._end_data[0]).edge( self._end_data[1]) else: from .similarity import SimilarityGroup sim = SimilarityGroup(self._s.base_ring()).one() itersegs = iter(traj.segments()) next(itersegs) for seg in itersegs: sim = sim * self._s.edge_transformation( seg.start().polygon_label(), seg.start().position().get_edge()) self._holonomy = sim(traj.segments()[-1].end().point())- \ traj.initial_tangent_vector().point() self._end_holonomy = -((~sim.derivative()) * self._holonomy) if holonomy is not None: if holonomy != self._holonomy: print("Combinatorial length: " + str(traj.combinatorial_length())) print("Start: " + str(traj.initial_tangent_vector().point())) print("End: " + str(traj.terminal_tangent_vector().point())) print("Start data:" + str(start_data)) print("End data:" + str(end_data)) raise ValueError("Provided holonomy " + str(holonomy) + " does not match computed holonomy of " + str(self._holonomy)) if end_holonomy is not None: if end_holonomy != self._end_holonomy: raise ValueError( "Provided or inferred end_holonomy " + str(end_holonomy) + " does not match computed end_holonomy of " + str(self._end_holonomy)) else: self._end_data = tuple(end_data) self._end_direction = end_direction self._holonomy = holonomy self._end_holonomy = end_holonomy # Make vectors immutable self._direction.set_immutable() self._end_direction.set_immutable() self._holonomy.set_immutable() self._end_holonomy.set_immutable()