r""" Sage source code associated with the article Generating discrete planes with substitutions by Valérie Berthe, Jérémié Bourdon, Timo Jolivet, Anne Siegel ... AUTHORS: - Timo Jolivet (2014) ############################################################################## # # Copyright (C) 2014 # Valérie Berthe, Jérémié Bourdon, Timo Jolivet, Anne Siegel # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ # ############################################################################## USAGE: - Run sage: attach("generation_graphs.sage") - Browse the code examples below: 1. Graph G^Brun 2. Graph H^Brun 3. "Translated seeds" graph for Brun substitutions 4. Graph G^JP 5. Graph H^JP 6. "Translated seeds" graph for JP substitutions - Examine the implementation of the functions used: - is_upper_admissible(x,filter=None) - is_admissible_face(x,i,filter=None) - preimg_E1Star(s, filter=None, show_map=False) - reduce(G) - generation_graph(X, subs, iterations=10, filter=None) - definitions of several patterns and substitutions (U, VBrun1, VBrun2, etc.) GENERATION GRAPHS FOR BRUN 1. Computing the graph G^Brun defined in Section 4.4 and used in Theorem 5.2:: sage: G = generation_graph(VBrun1 + VBrun2, [brun1,brun2,brun3], filter="Brun") sage: G.delete_vertices(U) sage: G = reduce(G) We manually delete the vertices of G which are not at the end of any infinite path labelled by infinitely many 3s:: sage: G.delete_vertex(Face([0,-1,1],1)) sage: G.delete_vertex(Face([1,0,0],2)) sage: G.delete_vertex(Face([1,0,0],3)) sage: G.delete_vertex(Face([1,1,0],3)) sage: G.delete_vertex(Face([0,-1,1],3)) sage: G.delete_vertex(Face([0,-1,1],2)) The result has 9 vertices and 17 edges and it can be plotted (see Fig. 2 in the article):: sage: len(G.vertices()) 9 sage: len(G.edges()) 17 sage: G.plot(color_by_label=True,edge_labels=True) 2. Computing the graph H^Brun defined in Section 4.4 and used in Lemma 4.9:: sage: H = generation_graph(WBrun1+WBrun2+WBrun3+WBrun4, [brun1,brun2,brun3], filter="Brun") sage: len(H.vertices()) 101 sage: len(H.edges()) 240 One way to check that every path containing sufficiently many 3's always originates from VBrun1 or VBrun2 is to check that the strongly connected components of H which contain edges labelled by 3 are all included in VBrun1 + VBrun2:: sage: X = H.strongly_connected_components_subgraphs() # compute strongly connected components sage: X = [C for C in X if 3 in C.edge_labels()] # keep only the ones containing edges 3 sage: for C in X: set(C).issubset(VBrun1 + VBrun2) # check that all the vertices are in VBrun1 or VBrun2 True True 3. The "translated seeds" graph for Brun, in the proof of Lemma 4.12 (see Fig. 3). We first compute its vertices:: sage: P = Face([0,0,0],2) + Face([1,0,0],2) + Face([1,0,0],3) + Face([0,-1,1],3) sage: P2 = Brun2(P) sage: P32 = Brun3(P2) - Face([0,-1,1],1) sage: P232 = Brun2(P32) - Face([1,0,0],2) sage: P3232 = Brun3(P232) sage: P13232 = Patch(f for f in Brun1(P3232) if f.vector()[2] > 0) \ - Patch(f for f in Brun1(P3232) if f.vector()[2] == 2 and f.type() != 3) sage: P12 = Brun1(P2) - Face([1,0,0],2) sage: P312 = Brun3(P12) sage: P2312 = Brun2(P312) sage: P332 = Brun3(P32) sage: P3332 = Brun3(P332) - Face([-1,1,0],1) - Face([-1,1,0],2) sage: P23332 = Brun2(P3332) \ - Patch(f for f in Brun2(P3332) if f.vector()[2] == 1 and f.type() == 3) \ - Patch(f for f in Brun2(P3332) if f.vector()[2] == 3 and f.type() != 3) \ - Patch(f for f in Brun2(P3332) if f.vector()[2] == 4) \ - Face([-2,-4,3],3) - Face([-2,-3,3],3) - Face([-1,-1,1],2) sage: P123332 = Brun1(P23332) - Patch(f for f in Brun1(P23332) if f.vector()[1] == -3) sage: P323332 = Brun3(P23332) - Face([-2, 0, 1], 1) sage: P2323332 = Brun2(P323332) - Patch(f for f in Brun2(P323332) if not -4 <= f.vector()[1] <= -2) sage: P12323332 = Brun1(P2323332) - Patch(f for f in Brun1(P2323332) if f.vector()[1] >= -4) sage: P32323332 = Brun3(P2323332) sage: P312323332 = Brun3(P12323332) sage: P232323332 = Brun2(P32323332) sage: P1332 = Brun1(P332) - U.translate([-1,1,0]) - Face([-1,2,0],3) - U.translate([-1,-4,2]) sage: P31332 = Brun3(P1332) - Face([-2,1,0],1) - Face([-2,1,0],2) - Face([-1,1,0],2) We check the validity of the graph, i.e., that for every edge P ->^i Q in the graph, there is an translate of Q in Brun_i(P). We check this individually for each edge. Each line below corresponds to one edge of the graph. In each case, the list returned by Brun_i(P).occurrences_of(Q) is nonempty:: sage: Brun2(P).occurrences_of(P2) [(0, 0, 0)] sage: sage: Brun1(P2).occurrences_of(P12) [(0, 0, 0)] sage: sage: Brun1(P12).occurrences_of(P12) [(0, 1, 0)] sage: sage: Brun3(P2).occurrences_of(P32) [(0, 0, 0)] sage: sage: Brun3(P12).occurrences_of(P32) [(1, 0, 0)] sage: sage: Brun3(P32).occurrences_of(P332) [(0, 0, 0)] sage: sage: Brun2(P32).occurrences_of(P232) [(0, 0, 0)] sage: sage: Brun1(P332).occurrences_of(P1332) [(0, 0, 0)] sage: sage: Brun3(P332).occurrences_of(P3332) [(0, 0, 0)] sage: sage: Brun1(P1332).occurrences_of(P1332) [(0, -1, 0)] sage: sage: Brun3(P1332).occurrences_of(P31332) [(0, 0, 0)] sage: sage: Brun1(P232).occurrences_of(P232) [(0, 1, 0)] sage: sage: Brun3(P232).occurrences_of(P3232) [(0, 0, 0)] sage: sage: Brun1(P3332).occurrences_of(P3332) [(0, 1, 0)] sage: sage: Brun2(P3332).occurrences_of(P23332) [(0, 0, 0)] sage: sage: Brun1(P31332).occurrences_of(P31332) [(0, 1, 0)] sage: sage: Brun2(P31332).occurrences_of(P123332) [(-1, 2, 0)] sage: sage: Brun1(P3232).occurrences_of(P13232) [(0, 0, 0)] sage: sage: Brun2(P3232).occurrences_of(VBrun1) [(2, 3, -2)] sage: sage: Brun1(P13232).occurrences_of(P13232) [(0, -1, 0)] sage: sage: Brun2(P13232).occurrences_of(VBrun1) [(2, 4, -3)] sage: sage: Brun1(P23332).occurrences_of(P123332) [(0, 0, 0)] sage: sage: Brun2(P23332).occurrences_of(VBrun1) [(0, 5, -3)] sage: sage: Brun3(P23332).occurrences_of(P323332) [(0, 0, 0)] sage: sage: Brun1(P123332).occurrences_of(P123332) [(0, -2, 0)] sage: sage: Brun2(P123332).occurrences_of(VBrun1) [(0, 7, -5)] sage: sage: Brun3(P123332).occurrences_of(P323332) [(-2, 0, 0)] sage: sage: Brun2(P323332).occurrences_of(P2323332) [(0, 0, 0)] sage: sage: Brun3(P323332).occurrences_of(VBrun2) [(2, 3, -3)] sage: sage: Brun1(P2323332).occurrences_of(P12323332) [(0, 0, 0)] sage: sage: Brun3(P2323332).occurrences_of(P32323332) [(0, 0, 0)] sage: sage: Brun1(P12323332).occurrences_of(P12323332) [(0, -3, 0)] sage: sage: Brun3(P12323332).occurrences_of(P312323332) [(0, 0, 0)] sage: sage: Brun1(P32323332).occurrences_of(VBrun1) [(-3, 11, -4)] sage: sage: Brun2(P32323332).occurrences_of(P232323332) [(0, 0, 0)] sage: sage: Brun1(P312323332).occurrences_of(VBrun1) [(-6, 11, -4), (-6, 9, -3)] sage: sage: Brun2(P312323332).occurrences_of(VBrun1) [(-6, -9, 6)] sage: sage: Brun1(P232323332).occurrences_of(VBrun1) [(-3, -15, 6)] sage: sage: Brun2(P232323332).occurrences_of(VBrun1) [(-3, 15, -9), (-3, 18, -11)] sage: sage: Brun3(P232323332).occurrences_of(VBrun2) [(-9, 9, -3)] GENERATION GRAPHS FOR JACOBI-PERRON 4. We compute four iterations of the generation graph G^JP defined in Section 4.5 and used in Theorem 5.3. The graph is actually but thanks to Lemma 4.10 its structure is simple to understand (there are two infinite branches which do not contain any edge labelled by 3 or 4). This allows us to manually remove the vertices of this approximation of G^JP that are not at the end of an infinite path containing infinitely many labels 3s and 4s:: sage: G = generation_graph(VJP1+VJP2+VJP3+VJP4, [theta1,theta2,theta3,theta4], iterations=4, filter="JP") sage: G.delete_vertices(U) sage: G.delete_vertex(Face([-1,1,0],3)) sage: G.delete_vertex(Face([1,0,0],3)) sage: G.delete_vertex(Face([0,1,0],1)) sage: G.delete_vertices(G.connected_component_containing_vertex(Face((-1,1,0),1))) sage: G.delete_vertex(Face([1,-1,0],2)) sage: G.delete_vertex(Face([1,-1,0],3)) sage: G.delete_vertex(Face([1,-1,0],1)) sage: G.delete_vertex(Face([1,1,0],3)) sage: G.delete_vertices(G.connected_component_containing_vertex(Face((0,1,0),3))) sage: G.delete_vertex(Face([-1,1,0],2)) sage: G.plot(color_by_label=True,edge_labels=True,vertex_size=5).show(figsize=15) 5. The Graph H^JP defined in Section 4.5 and used in Lemma 4.11:: sage: H = generation_graph(WJP1+WJP2+WJP3+WJP4+WJP5+WJP6+WJP7+WJP8, [theta1,theta2,theta3,theta4], iterations=4, filter="JP") sage: H.delete_vertices(U) One way to check that every path containing sufficiently many 3's always originates from VJP1, VJP2, VJP3 or VJP4 is to check that the strongly connected components of H which contain edges labelled by 3 or 4 originate from the infinite graph pictured on p. 21. The structure of this graph is similar to that of G^JP, except that there are four infinite branches:: sage: X = H.strongly_connected_components_subgraphs() # compute strongly connected components sage: X = [C for C in X if 3 in C.edge_labels()] # keep only the ones containing edges 3 or 4 sage: len(X) 1 sage: X[0].plot(color_by_label=True,edge_labels=True,vertex_size=5).show(figsize=15) 6. The "translated seeds" graph for Jacobi-Perron, in the proof of Lemma 4.13:: sage: P3 = Theta3(U) sage: P33 = Theta3(P3) - Face([1,0,0],3) - Face([0,1,0],3) sage: P233 = U + Face([1,-1,0],1) - Face([0,0,0],1) sage: P4233 = U + Face([1,1,-1],2) sage: Theta3(U).occurrences_of(P3) [(0, 0, 0)] sage: Theta1(P3).occurrences_of(P3) [(0, 0, 0)] sage: Theta3(P3).occurrences_of(P33) [(0, 0, 0)] sage: Theta1(P33).occurrences_of(P33) [(0, 0, 0)] sage: Theta2(P33).occurrences_of(P233) [(-1, 1, 0)] sage: Theta2(P233).occurrences_of(P233) [(1, 0, 0)] sage: Theta4(P233).occurrences_of(P4233) [(0, 0, 0)] """ ############################################################################## ### Main functions ############################################################################## from sage.combinat.e_one_star import Face, Patch, E1Star def is_upper_admissible(x,filter=None): r""" Tests whether there exists a positive vector v such that 0 <= . INPUT: - ``x`` - an integer vector - ``filter`` - a string (default: ``None'') force a condition on the allowed vectors v in the above definition (option available only in dimension 3) - 'Brun' - condition v[0] < v[1] < v[2] - 'JP' - condition v[0] < v[2] and v[1] < v[2] EXAMPLES:: sage: is_upper_admissible((0,0,0)) True sage: is_upper_admissible((2,1,0)) True sage: is_upper_admissible((2,1,-3)) True sage: is_upper_admissible((2,1,3)) True sage: is_upper_admissible((-1,1,-1)) True sage: is_upper_admissible((-1,0,0)) False sage: is_upper_admissible((-1,-1,-1)) False """ if vector(x).is_zero(): return True if all(a <= 0 for a in x): return False if filter is None: if any(a > 0 for a in x): return True elif filter == "Brun" or filter == "JP": if x[2] > 0: return True elif x[2] < 0: if x[0] > 0: if x[1] > 0 and x[0] + x[1] > -x[2]: return True if x[1] <= 0: if filter == "JP" and x[0] > -x[2]: return True elif filter == "Brun" and x[0] > -x[1] -x[2]: return True elif x[0] <= 0: if x[1] > 0 and x[1] > -x[2]: return True elif x[2] == 0: if filter == "JP": return True elif filter == "Brun": if x[1] < 0: if x[0] > -x[1]: return True elif x[1] >= 0: return True return False def is_admissible_face(x,i,filter=None): r""" Tests whether there exists a discrete plane that contains the unit face of type i placed at x, i.e., tests whether there exists a positive vector such that 0 <= < . INPUT: - ``x`` - an d-dimensional integer vector - ``i`` - an integer in [1,...,d] - ``filter`` - a string (default: ``None'') force a condition on the allowed vectors v in the above definition (option available only in dimension 3) - 'Brun' - v[0] < v[1] < v[2] - 'JP' - v[0] < v[2] and v[1] < v[2] EXAMPLES:: sage: is_admissible_face((0,0,0),2) True sage: is_admissible_face((1,0,0),2) True sage: is_admissible_face((0,1,0),2) False sage: is_admissible_face((1,1,0),2) False sage: is_admissible_face((1,-1,0),2) True """ y = list(x) y[i-1] -= 1 y = vector(y) if y.is_zero(): return False return is_upper_admissible(x,filter) and is_upper_admissible(-y,filter) def preimg_E1Star(s, filter=None, show_map=False): r""" Returns the function which, given input a face f, returns all the preimages of f, that is, all the faces g such that ``f in E1Star([g])``, with the restriction that there exists a discrete plane containing g. INPUT: - ``s`` - a substitution - ``filter`` - a string (default: ``None'') force a condition on the allowed discrete planes allowed for g in the description above (see the options of function is_admissible_face for more details) - ``show_map`` - (default: ``False``) print the preimages EXAMPLES:: sage: t = preimg_E1Star(brun3) sage: f = Face((2,1,-1),3) sage: t(f) [[(-1, 2, -1), 1]*, [(-1, 2, 0), 3]*] sage: f in E1Star(brun3)(Patch([Face((-1,2,-1),1)])) True sage: f in E1Star(brun3)(Patch([Face((-1,2,0),3)])) True :: sage: t = preimg_E1Star(brun3) sage: t(Face((1,0,0),3)) [[(0, 1, -1), 1]*, [(0, 1, 0), 3]*] sage: t = preimg_E1Star(brun3,"Brun") sage: t(Face((1,0,0),3)) [[(0, 1, 0), 3]*] :: sage: t = preimg_E1Star(brun3,show_map=True) (1, [((x3, x1, x2 + x3), 2)]) (2, [((x3, x1, x2 + x3), 3)]) (3, [((x3, x1, x2 + x3 - 1), 1), ((x3, x1, x2 + x3), 3)]) """ if isinstance(s, E1Star): s = s.sigma() M = s.incidence_matrix() alphabet = s.domain().alphabet() V = vector(var(["x%d"%a for a in alphabet])) D = dict((a,[]) for a in alphabet) for j in alphabet: w = s(j) for n, i in enumerate(w): v = M*V - vector(w[n+1:].parikh_vector()) D[j].append((v, i)) if show_map: for x in D.items(): print x def preimg(f): i = f.type() v = f.vector() L = [] for w,j in D[i]: w = w.subs(dict((x,y) for x,y in zip(V,v))) if is_admissible_face(w,j,filter=filter): L.append(Face(vector(ZZ,w),j)) return L return preimg def reduce(G): r""" Recursively remove all the vertices of G which are not at the end of an infinite path in G. """ G = DiGraph(G) while True: to_remove = [x for x in G if G.in_degree(x) == 0] G.delete_vertices(to_remove) if not to_remove: break return G def generation_graph(X, subs, iterations=10, filter=None): r""" Construct the generation graph according to Definition 3.9, where ``X`` is a set of faces and ``subs`` is a set of substitutions. A filter set can be used with option ``filter``. INPUT: - ``X`` - the set of faces to start from - ``subs`` - a list of substitutions (``WordMorphism`` or ``E1Star``) - ``iterations`` - (default: ``10``) max number of iterations - ``filter`` - a string ('Brun' or 'JP', default: ``None'') force a condition on the allowed discrete planes allowed for face preimages in the graph construction (see the options of function is_admissible_face for more details) """ preimg_subs = dict((k+1, preimg_E1Star(s, filter=filter)) for k,s in enumerate(subs)) G = DiGraph(loops=True, multiedges=True) to_treat = set(X) for k in range(iterations): to_treat_next = set() while to_treat: f = to_treat.pop() for i in preimg_subs.keys(): for g in preimg_subs[i](f): if not (g,f,i) in G.edges(): G.add_edge(g,f,i) to_treat_next.add(g) to_treat = to_treat_next print "Iteration %d: %d vertices, %d edges."%(k+1, len(G.vertices()), len(G.edges())) if not to_treat: print "Stabilized after %d iterations."%(k+1) break else: if k == iterations-1: print "Did not stabilize after %d iterations (%d remaining vertices to treat)."%(k+1,len(to_treat)) return G ############################################################################## ### Patterns, substitutions and minimal annuli ############################################################################## U = Patch([Face([0,0,0], 1), Face([0,0,0], 2), Face([0,0,0], 3)]) thickness_1_U = Patch(Face(*x) for x in [ ((0,0,0),1), ((0,0,0),2), ((0,0,0),3), ((0,1,0),1), ((-1,1,0),2), ((0,0,1),1), ((-1,0,1),3), ((1,0,0),2), ((1,-1,0),1), ((0,0,1),2), ((0,-1,1),3), ((1,0,0),3), ((1,0,-1),1), ((0,1,0),3), ((0,1,-1),2), ((1,-1,-1),1), ((1,0,-1),2), ((1,-1,0),3), ((0,1,-1),1), ((-1,1,-1),2), ((-1,1,0),3), ((0,-1,1),1), ((-1,0,1),2), ((-1,-1,1),3), ((0,1,1),1), ((-1,1,1),2), ((-1,1,1),3), ((1,-1,1),1), ((1,0,1),2), ((1,-1,1),3), ((1,1,-1),1), ((1,1,-1),2), ((1,1,0),3)]) # Brun substitutions and annuli brun1 = WordMorphism({1:[1], 2:[2], 3:[3,2]}) brun2 = WordMorphism({1:[1], 2:[3], 3:[2,3]}) brun3 = WordMorphism({1:[2], 2:[3], 3:[1,3]}) Brun1 = E1Star(brun1) Brun2 = E1Star(brun2) Brun3 = E1Star(brun3) # Jacobi-Perron substitutions and annuli # we have jp(a,b) == tau3 * tau1^a * tau2^b jp = lambda a,b : WordMorphism({1:[3], 2:[1]+[3]*a, 3:[2]+[3]*b}) JP = lambda a,b : E1Star(jp(a,b)) tau1 = WordMorphism({1:[1], 2:[2,1], 3:[3]}) tau2 = WordMorphism({1:[1], 2:[2], 3:[3,1]}) tau3 = WordMorphism({1:[3], 2:[1], 3:[2]}) theta1 = tau2 theta2 = tau1 * tau2 theta3 = tau3 * tau2 theta4 = tau3 * tau1 * tau2 Theta1 = E1Star(theta1) Theta2 = E1Star(theta2) Theta3 = E1Star(theta3) Theta4 = E1Star(theta4) # Annulus patterns VBrun1 = Patch(Face(*x) for x in [ ((-1, -1, 1), 3), ((-1, 0, 1), 3), ((-1, 1, 0), 2), ((-1, 1, 0), 3), ((0, -1, 1), 3), ((0, 0, 0), 1), ((0, 0, 0), 2), ((0, 0, 0), 3), ((0, 1, 0), 3), ((1, -1, 1), 3), ((1, 0, 0), 2), ((1, 0, 0), 3), ((1, 1, 0), 3)]) VBrun2 = Patch(Face(*x) for x in [ ((-1, 0, 1), 2), ((-1, 0, 1), 3), ((-1, 1, 0), 2), ((-1, 1, 0), 3), ((0, -1, 1), 1), ((0, -1, 1), 2), ((0, -1, 1), 3), ((0, 0, 0), 1), ((0, 0, 0), 2), ((0, 0, 0), 3), ((0, 1, 0), 3), ((1, -1, 1), 3), ((1, 0, 0), 2), ((1, 0, 0), 3), ((1, 1, -1), 1), ((1, 1, -1), 2), ((1, 1, -1), 3)]) VJP1 = VBrun1 VJP2 = VBrun2 - Face((0,-1,1),2) VJP3 = Patch(Face(*x) for x in [ ((-1, -1, 1), 3), ((-1, 0, 1), 3), ((-1, 1, 1), 3), ((0, -1, 1), 3), ((0, 0, 0), 1), ((0, 0, 0), 2), ((0, 0, 0), 3), ((0, 1, 0), 1), ((0, 1, 0), 3), ((1, -1, 0), 1), ((1, -1, 0), 3), ((1, 0, 0), 3), ((1, 1, 0), 3)]) VJP4 = Patch(Face(*x) for x in [ ((-1, 0, 1), 2), ((-1, 0, 1), 3), ((-1, 1, 1), 3), ((0, -1, 1), 1), ((0, -1, 1), 3), ((0, 0, 0), 1), ((0, 0, 0), 2), ((0, 0, 0), 3), ((0, 1, 0), 1), ((0, 1, 0), 3), ((1, -1, 0), 1), ((1, -1, 0), 3), ((1, 0, 0), 3), ((1, 1, -1), 1), ((1, 1, -1), 2), ((1, 1, -1), 3)]) WBrun1 = Patch(Face(*x) for x in [ ((-2, 0, 1), 3), ((0, -2, 2), 3), ((-1, -1, 1), 1), ((1, -2, 1), 1), ((2, -2, 1), 3), ((0, -1, 1), 2), ((2, 1, -1), 1), ((0, 2, -1), 3), ((0, 2, -1), 2), ((-1, -1, 1), 2), ((2, 1, -1), 2), ((0, 2, -1), 1), ((-2, 2, 0), 3), ((1, -2, 1), 3), ((1, 2, -1), 2), ((-1, -2, 2), 3), ((2, -1, 1), 3), ((2, 0, 0), 3), ((-2, 1, 0), 2), ((-2, -1, 2), 3), ((2, 1, -1), 3), ((-2, 0, 1), 2), ((1, -2, 1), 2), ((1, 2, -1), 3), ((2, 0, 0), 2), ((-1, 2, 0), 3), ((-2, -2, 2), 3), ((-2, 1, 0), 3)]) WBrun2 = Patch(Face(*x) for x in [ ((-2, 0, 1), 3), ((2, 1, 0), 3), ((2, 2, -1), 2), ((2, -1, 0), 1), ((0, -2, 1), 2), ((0, 2, 0), 3), ((-1, -1, 1), 2), ((1, 2, -1), 1), ((0, -2, 1), 3), ((-1, 1, 0), 1), ((-2, -1, 1), 3), ((-1, 2, 0), 3), ((2, -1, 0), 3), ((1, 2, -1), 2), ((-1, -2, 2), 3), ((2, -2, 1), 3), ((2, 0, 0), 3), ((1, -2, 1), 3), ((-2, -1, 1), 2), ((-2, 1, 1), 3), ((-2, -2, 2), 3), ((-2, 2, 0), 2), ((1, 2, -1), 3), ((0, -2, 1), 1), ((-2, 2, 0), 3), ((2, -1, 0), 2)]) WBrun3 = Patch(Face(*x) for x in [ ((-2, 0, 1), 3), ((0, 2, -1), 3), ((1, -2, 1), 1), ((2, 2, -2), 2), ((2, -1, 0), 1), ((0, -2, 2), 3), ((0, 2, -1), 2), ((2, 2, -2), 1), ((-1, 1, 0), 1), ((2, 1, -1), 2), ((0, 2, -1), 1), ((-2, 2, 0), 3), ((-1, 2, 0), 3), ((-1, -2, 2), 3), ((2, -2, 1), 3), ((2, 0, 0), 3), ((1, -2, 1), 3), ((-2, 1, 1), 3), ((-1, -1, 2), 3), ((2, 1, -1), 3), ((-2, 0, 1), 2), ((-2, 2, 0), 2), ((1, -2, 1), 2), ((1, 2, -1), 3), ((2, 2, -2), 3), ((2, -1, 0), 3), ((-2, -1, 2), 3), ((2, -1, 0), 2)]) WBrun4 = Patch(Face(*x) for x in [ ((-1, -1, 2), 2), ((-2, 0, 1), 3), ((0, -2, 2), 3), ((2, 2, -2), 2), ((2, -1, 0), 1), ((-1, 2, -1), 1), ((2, -2, 1), 1), ((0, 2, -1), 3), ((0, 2, -1), 2), ((0, -2, 2), 2), ((1, -2, 2), 3), ((1, 2, -2), 1), ((2, -2, 1), 2), ((-1, 1, 0), 1), ((2, 1, -1), 2), ((0, -2, 2), 1), ((-2, 2, 0), 3), ((2, -1, 0), 3), ((1, 2, -2), 2), ((-1, -1, 2), 3), ((2, -2, 1), 3), ((2, 0, 0), 3), ((-1, 2, -1), 3), ((-2, 1, 1), 3), ((-2, -1, 2), 3), ((2, 1, -1), 3), ((-2, 0, 1), 2), ((-2, 2, 0), 2), ((1, -1, 1), 2), ((1, 2, -2), 3), ((2, 2, -2), 3), ((-1, 2, -1), 2), ((2, -1, 0), 2)]) WJP1 = WBrun1 - Face((1,-2,1),2) WJP2 = WBrun2 - Face((0,-2,1),2) WJP3 = WBrun3 - Face((1,-2,1),2) + Face((0,-1,1),2) WJP4 = WBrun4 - Face((0,-2,2),2) + Face((0,-1,1),2) typecycle = {1:2, 2:1, 3:3} WJP5 = Patch(Face([f.vector()[1],f.vector()[0],f.vector()[2]], typecycle[f.type()]) for f in WJP1) WJP6 = Patch(Face([f.vector()[1],f.vector()[0],f.vector()[2]], typecycle[f.type()]) for f in WJP2) WJP7 = Patch(Face([f.vector()[1],f.vector()[0],f.vector()[2]], typecycle[f.type()]) for f in WJP3) WJP8 = Patch(Face([f.vector()[1],f.vector()[0],f.vector()[2]], typecycle[f.type()]) for f in WJP4)