def test_L_with_slit_mpq(): import cppyy mpq = cppyy.gbl.mpq_class R2 = flatsurf.Vector[mpq] surface = surfaces.L(R2) slit = R2(mpq(5, 3), mpq(4, 3)) e = flatsurf.HalfEdge(1) surface = surface.insertAt(e, slit).surface() assert e != flatsurf.HalfEdge( 1), "HalfEdge& not updated correctly in " + repr(surface) e = surface.nextAtVertex(e) surface = surface.slit(e).surface() connections = surface.connections().bound(16).sector(flatsurf.HalfEdge(1)) assert len([1 for c in connections]) == 15
def test_hexagon_eantic(): surface = surfaces.hexagon() decompositions = {(1, 0): 0, (2, 0): 0} for connection in surface.connections().bound(16).sector( flatsurf.HalfEdge(1)): decomposition = flatsurf.makeFlowDecomposition(surface, connection.vector()) assert repr(decomposition).startswith("FlowDecomposition") assert str(decomposition).startswith("FlowDecomposition") decomposition.decompose(-1) n_cylinders = 0 n_without_periodic_trajectory = 0 for component in decomposition.components(): n_cylinders += bool(component.cylinder() == True) n_without_periodic_trajectory += bool( component.withoutPeriodicTrajectory() == True) if component.cylinder(): # 3 ways to compute area area1 = component.area() h = component.circumferenceHolonomy() p0 = [p for p in component.perimeter()][0] sc = p0.saddleConnection() p0 = sc.vector() vertical = component.vertical() assert vertical.projectPerpendicular(h) == 0 assert vertical.project(h) > 0 area2 = p0.x() * h.y() - p0.y() * h.x() v = vertical.vertical() vx = v.x() vy = v.y() area3 = vertical.projectPerpendicular(p0) * vertical.project(h) assert area1 == 2 * area2 assert area3 == area2 * (vx * vx + vy * vy) assert all(component.cylinder() for component in decomposition.cylinders()) assert len(decomposition.cylinders()) == n_cylinders assert all(component.withoutPeriodicTrajectory() for component in decomposition.minimalComponents()) assert len( decomposition.minimalComponents()) == n_without_periodic_trajectory assert len(decomposition.undeterminedComponents()) == 0 t = (n_cylinders, n_without_periodic_trajectory) decompositions[t] += 1 if n_without_periodic_trajectory == 0: assert decomposition.parabolic() assert decompositions == {(1, 0): 7, (2, 0): 3}
def test_square_longlong(): surface = surfaces.square(flatsurf.Vector['long long']) connections = surface.saddleConnections(flatsurf.Bound(16), flatsurf.HalfEdge(1)) # https://bitbucket.org/wlav/cppyy/issues/103/std-distance-returns-random-output # assert len(connections) == 60 # This does not work, the iterator that backs connections returns # references that are only valid until the iterator is advanced, so the # conversion to list does not work at the moment. # assert len(list(connections)) assert len([1 for c in connections]) == 60
async def path(self): r""" The path drawn by the user or explicitly set. EXAMPLES:: >>> from flatsurf import translation_surfaces >>> S = translation_surfaces.square_torus(); >>> from ipyvue_flatsurf import Widget >>> W = Widget(S) Unfortunately, this cannot be tested withuot an actual notebook running, see https://github.com/flatsurf/ipyvue-async/issues/2:: >>> await W.path # doctest: +SKIP [...] Note that the path is not re-requested once it has been determined. To clear the path, you need to set it to `None`:: >>> W.path = None >>> await W.path # doctest: +SKIP The path can be given as a flatsurf path or as a list of flatsurf saddle connections:: >>> import asyncio >>> W.path = [] >>> asyncio.run(W.path) [] """ if self._path is None: self.action = "path" import asyncio path = await self.poll(self.query("flatsurf", "path", "completed", return_when=asyncio.FIRST_COMPLETED)) # TODO: Unfortunately, we have to query explicitly for the layout, # see https://github.com/flatsurf/vue-flatsurf/issues/55. Also we # cannot be sure that we are getting the layout from the one that # gave us the path, see # https://github.com/flatsurf/ipyvue-async/issues/1. layout = await self.poll(self.query("flatsurf", "layout", "now", return_when=asyncio.FIRST_COMPLETED)) S = self.triangulation inner = [edge.positive().id() for edge in S.edges() if layout['halfEdges'][str(edge)]['inner']] if len(path) < 2: raise NotImplementedError("Cannot represent trivial paths yet.") if 'vertex' not in path[0]: raise NotImplementedError("Cannot represent path that does not start at a vertex yet.") if 'vertex' not in path[-1]: raise NotImplementedError("Cannot represent path that does not end at a vertex yet.") from pyflatsurf import flatsurf path = [{ 'vertex': [flatsurf.HalfEdge(x) for x in p['vertex']] } if 'vertex' in p else { 'halfEdge': flatsurf.HalfEdge(p['halfEdge']) } for p in path] connections = [] # The input path consists of a list of points that are either at a # vertex or somewhere on a half edge. Connections between vertex # points are in principle easy since we can exactly represent them # in libflatsurf as saddle connections. However, a connection that # starts or ends inside a half edge has no correspondence in # libflatsurf (yet) so we need to rewrite it as a homotopic # sequence of connections of the first kind. Actually, we rewrite # it as a sequence of half edges, the simplest saddle connections. # To that end, we treat every half edge point as a point at the # vertex where this half edge starts. # The half edges that the path is crossing, i.e., the half edges # that we are allowed to cross when reconstructing an equivalent # representation of the path. inner = [flatsurf.HalfEdge(e) for e in inner] + [flatsurf.HalfEdge(-e) for e in inner] for source, target in zip(path, path[1:]): def source_faces(x): if 'halfEdge' in x: return [S.previousAtVertex(x['halfEdge'])] else: return x['vertex'] def target_faces(x): if 'halfEdge' in x: return [x['halfEdge']] else: return x['vertex'] # We now pretend that we start in one of the faces attached to # "start" and search for a path to a face attached to "target". paths = {} queue = [] def enqueue_face(face, partial): if face in paths: return paths[face] = partial queue.append(face) next = S.nextInFace(face) enqueue_face(next, partial + [face]) for source_face in source_faces(source): enqueue_face(source_face, []) while queue: face = queue.pop() if face in inner: enqueue_face(-face, paths[face] + [face]) for target_face in target_faces(target): if target_face in paths: connections.extend(paths[target_face]) break else: raise ValueError(f"Could not reconstruct the partial path from {source} to {target} that was reported by the frontend.") self._path = flatsurf.Path[type(S)]([flatsurf.SaddleConnection[type(S)](S, halfEdge) for halfEdge in connections]) return self._path
def test_hexagon_exactreal(): from pyexactreal import exactreal surface = surfaces.random_hexagon() connections = surface.saddleConnections(flatsurf.Bound(16), flatsurf.HalfEdge(1))
def test_hexagon_eantic(): surface = surfaces.hexagon() connections = surface.saddleConnections(flatsurf.Bound(16), flatsurf.HalfEdge(1)) assert len([1 for c in connections]) == 10
def test_deformation_square(capsys): Edge = flatsurf.Edge vector = flatsurf.Vector['mpq_class'] # A square of size (3, 3) square = surfaces.square(vector).scale(3) # Insert an extra vertex at (2, 1) that is easier to move around without # having to change the entire surface. square = square.insertAt(flatsurf.HalfEdge(1), vector(2, 1)).surface() # A forbidden shift, collapsing a half edge during the shift; this is # supposed to fail but it should fail silently. shift = lambda e: vector(-2, -4) if e in [Edge( 4), Edge(5), Edge(6)] else vector(0, 0) capsys.readouterr() import cppyy with pytest.raises(cppyy.gbl.std.invalid_argument): square + [shift(e) for e in square.edges()] # Verify that #179 has been fixed, i.e., no warnings are printed output = capsys.readouterr() assert output.out == "" assert output.err == "" # A much smaller shift in the same direction is allowed square + [shift(e) / 128 for e in square.edges()] # A shift half the length collapses the extra vertex square + [shift(e) / 2 for e in square.edges()] # Another shift with a vertex ending up on the interior of a half edge, i.e., requires a flip. shift = lambda e: vector(-1, 0) if e in [Edge( 4), Edge(5), Edge(6)] else vector(0, 0) square + [shift(e) for e in square.edges()] # Another shift with a vertex crossing over the interior of a half edge, i.e., requires a flip. shift = lambda e: vector(-2, 0) if e in [Edge( 4), Edge(5), Edge(6)] else vector(0, 0) square + [shift(e) for e in square.edges()] # Another shift with a vertex crossing over the interior of a half edge but # then ending up on another vertex, i.e., requires a flip and a collapse. shift = lambda e: vector(-4, -2) if e in [Edge( 4), Edge(5), Edge(6)] else vector(0, 0) square + [shift(e) for e in square.edges()] # Another shift with a vertex crossing lots of half edges, i.e., requiring many flips. shift = lambda e: vector(-23, 0) if e in [Edge( 4), Edge(5), Edge(6)] else vector(0, 0) square + [shift(e) for e in square.edges()] # Insert a vertex in the other triangle so we can move both simultaneously square = square.insertAt(flatsurf.HalfEdge(3), vector(1, 2)).surface() # Shift both inserted vertices towards the same vertex simultaneously shift = lambda e: vector(-1, -2) if e in [Edge( 4), Edge(5), Edge(6)] else vector( -2, -1) if e in [Edge(7), Edge(8), Edge(9)] else vector(0, 0) square + [shift(e) / 2 for e in square.edges()] # Shift both inserted vertices onto the same vertex simultaneously square + [shift(e) for e in square.edges()] # Shift both inserted vertices a long way across the surface in the same direction shift = lambda e: vector(-23, 0) if e in [Edge( 4), Edge(5), Edge(6)] else vector( -23, 0) if e in [Edge(7), Edge(8), Edge(9)] else vector(0, 0) square + [shift(e) for e in square.edges()] # Shift both inserted vertices a long way across the surface in different directions (invalid because it the two marked vertices meet at some point.) shift = lambda e: vector(-23, 0) if e in [Edge( 4), Edge(5), Edge(6)] else vector( 0, -23) if e in [Edge(7), Edge(8), Edge(9)] else vector(0, 0) with pytest.raises(cppyy.gbl.std.invalid_argument): square + [shift(e) for e in square.edges()] # Shift both inserted vertices a long way across the surface in different directions shift = lambda e: vector(-23, 0) if e in [Edge( 4), Edge(5), Edge(6)] else vector( -1, -23) if e in [Edge(7), Edge(8), Edge(9)] else vector(0, 0) square + [shift(e) for e in square.edges()] # Shift both inserted vertices a long way across the surface in different directions and at different velocities shift = lambda e: vector(-23, 0) if e in [Edge( 4), Edge(5), Edge(6)] else vector( 0, -47) if e in [Edge(7), Edge(8), Edge(9)] else vector(0, 0) square + [shift(e) for e in square.edges()]
def test_printing(): surface = surfaces.hexagon(flatsurf.Vector['eantic::renf_elem_class']) assert str(surface.fromHalfEdge(flatsurf.HalfEdge(1))) == "(2, 0)" assert repr(surface.fromHalfEdge(flatsurf.HalfEdge(1))) == "(2, 0)"
def test_edge(): half_edge = flatsurf.HalfEdge(1) assert half_edge.edge().positive() == half_edge
def test_printing(): surface = surfaces.hexagon() assert str(surface.fromEdge(flatsurf.HalfEdge(1))) == "(2, 0)" assert repr(surface.fromEdge(flatsurf.HalfEdge(1))) == "(2, 0)"
def test_hexagon_exactreal(): from pyexactreal import exactreal surface = surfaces.random_hexagon() connections = surface.connections().bound(16).sector(flatsurf.HalfEdge(1)) assert len([1 for c in connections]) >= 10
def test_hexagon_eantic(): surface = surfaces.hexagon() connections = surface.connections().bound(16).sector(flatsurf.HalfEdge(1)) assert len([1 for c in connections]) == 10
def test_L_mpq(): surface = surfaces.L(flatsurf.Vector['mpq_class']) connections = surface.connections().bound(16).sector(flatsurf.HalfEdge(1)) assert len([1 for c in connections]) == 60