def to_undirected_graph(self): r""" Return the undirected graph obtained from the tree nodes and edges. The graph is endowed with an embedding, so that it will be displayed correctly. EXAMPLES:: sage: t = OrderedTree([]) sage: t.to_undirected_graph() Graph on 1 vertex sage: t = OrderedTree([[[]],[],[]]) sage: t.to_undirected_graph() Graph on 5 vertices If the tree is labelled, we use its labelling to label the graph. This will fail if the labels are not all distinct. Otherwise, we use the graph canonical labelling which means that two different trees can have the same graph. EXAMPLES:: sage: t = OrderedTree([[[]],[],[]]) sage: t.canonical_labelling().to_undirected_graph() Graph on 5 vertices TESTS:: sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph() False sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph() True sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph() False """ from sage.graphs.graph import Graph g = Graph() if self in LabelledOrderedTrees(): relabel = False else: self = self.canonical_labelling() relabel = True roots = [self] g.add_vertex(name=self.label()) emb = {self.label(): []} while roots: node = roots.pop() children = reversed([child.label() for child in node]) emb[node.label()].extend(children) for child in node: g.add_vertex(name=child.label()) emb[child.label()] = [node.label()] g.add_edge(child.label(), node.label()) roots.append(child) g.set_embedding(emb) if relabel: g = g.canonical_label() return g
def _spanning_tree(base, verts): graph = Graph([list(verts), lambda x, y: x is not y]) def length(edge): x, y, _ = edge return abs(CC(x.alg) - CC(y.alg)) tree = graph.min_spanning_tree(length) tree = Graph(tree) tree.add_vertex(base) return tree
def CayleyGraph(vspace, gens, directed=False): """ Generate a Cayley Graph over given vector space with edges generated by given generators. The graph is optionally directed. Try e.g. CayleyGraph(VectorSpace(GF(2), 3), [(1,0,0), (0,1,0), (0,0,1)]) """ G = Graph() for v in vspace: G.add_vertex(tuple(v)) for g in gens: g2 = vspace(g) G.add_edge(tuple(v), tuple(v + g2)) return G
def to_undirected_graph(self): r""" Return the undirected graph obtained from the tree nodes and edges. EXAMPLES:: sage: t = OrderedTree([]) sage: t.to_undirected_graph() Graph on 1 vertex sage: t = OrderedTree([[[]],[],[]]) sage: t.to_undirected_graph() Graph on 5 vertices If the tree is labelled, we use its labelling to label the graph. Otherwise, we use the graph canonical labelling which means that two different trees can have the same graph. EXAMPLES:: sage: t = OrderedTree([[[]],[],[]]) sage: t.canonical_labelling().to_undirected_graph() Graph on 5 vertices sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph() False sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph() True sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph() False """ from sage.graphs.graph import Graph g = Graph() if self in LabelledOrderedTrees(): relabel = False else: self = self.canonical_labelling() relabel = True roots = [self] g.add_vertex(name=self.label()) while len(roots) != 0: node = roots.pop() for child in node: g.add_vertex(name=child.label()) g.add_edge(child.label(), node.label()) roots.append(child) if relabel: g = g.canonical_label() return g
def to_undirected_graph(self): r""" Return the undirected graph obtained from the tree nodes and edges. EXAMPLES:: sage: t = OrderedTree([]) sage: t.to_undirected_graph() Graph on 1 vertex sage: t = OrderedTree([[[]],[],[]]) sage: t.to_undirected_graph() Graph on 5 vertices If the tree is labelled, we use its labelling to label the graph. Otherwise, we use the graph canonical labelling which means that two different trees can have the same graph. EXAMPLES:: sage: t = OrderedTree([[[]],[],[]]) sage: t.canonical_labelling().to_undirected_graph() Graph on 5 vertices sage: t.canonical_labelling().to_undirected_graph() == t.to_undirected_graph() False sage: OrderedTree([[],[]]).to_undirected_graph() == OrderedTree([[[]]]).to_undirected_graph() True sage: OrderedTree([[],[],[]]).to_undirected_graph() == OrderedTree([[[[]]]]).to_undirected_graph() False """ from sage.graphs.graph import Graph g = Graph() if self in LabelledOrderedTrees(): relabel = False else: self = self.canonical_labelling() relabel = True roots = [self] g.add_vertex(name=self.label()) while len(roots) != 0: node = roots.pop() for child in node: g.add_vertex(name=child.label()) g.add_edge(child.label(), node.label()) roots.append(child) if (relabel): g = g.canonical_label() return g
def graph(self): r""" EXAMPLES:: sage: from slabbe import DoubleRootedTree sage: edges = [(0,5),(1,2),(2,6),(3,2),(4,1),(5,7),(7,1),(8,2),(9,4)] sage: D = DoubleRootedTree(edges, 6, 0) sage: D.graph() Graph on 10 vertices """ if self._graph is None: G = Graph() G.add_vertex(self._rootA) G.add_vertex(self._rootB) for a,b in self._edges: G.add_edge(a,b) self._graph = G return self._graph
def GridGraph(dim_list): """ Returns an n-dimensional grid graph. INPUT: - ``dim_list`` - a list of integers representing the number of nodes to extend in each dimension. PLOTTING: When plotting, this graph will use the default spring-layout algorithm, unless a position dictionary is specified. EXAMPLES:: sage: G = graphs.GridGraph([2,3,4]) sage: G.show() # long time :: sage: C = graphs.CubeGraph(4) sage: G = graphs.GridGraph([2,2,2,2]) sage: C.show() # long time sage: G.show() # long time TESTS: The graph name contains the dimension:: sage: g = graphs.GridGraph([5, 7]) sage: g.name() 'Grid Graph for [5, 7]' sage: g = graphs.GridGraph([2, 3, 4]) sage: g.name() 'Grid Graph for [2, 3, 4]' sage: g = graphs.GridGraph([2, 4, 3]) sage: g.name() 'Grid Graph for [2, 4, 3]' One dimensional grids (i.e., path) have simple vertex labels:: sage: g = graphs.GridGraph([5]) sage: g.vertices() [0, 1, 2, 3, 4] The graph is correct:: sage: dim = [randint(1,4) for i in range(4)] sage: g = graphs.GridGraph(dim) sage: import networkx sage: h = Graph( networkx.grid_graph(list(dim)) ) sage: g.is_isomorphic(h) True Trivial cases:: sage: g = graphs.GridGraph([]); g; g.vertices() Grid Graph for []: Graph on 0 vertices [] sage: g = graphs.GridGraph([1]); g; g.vertices() Grid Graph for [1]: Graph on 1 vertex [0] sage: g = graphs.GridGraph([2]); g; g.vertices() Grid Graph for [2]: Graph on 2 vertices [0, 1] sage: g = graphs.GridGraph([1,1]); g; g.vertices() Grid Graph for [1, 1]: Graph on 1 vertex [(0, 0)] sage: g = graphs.GridGraph([1, 1, 1]); g; g.vertices() Grid Graph for [1, 1, 1]: Graph on 1 vertex [(0, 0, 0)] sage: g = graphs.GridGraph([1,1,2]); g; g.vertices() Grid Graph for [1, 1, 2]: Graph on 2 vertices [(0, 0, 0), (0, 0, 1)] All dimensions must be positive integers:: sage: g = graphs.GridGraph([2,-1,3]) Traceback (most recent call last): ... ValueError: All dimensions must be positive integers ! """ dim = [int(a) for a in dim_list] if any(a <= 0 for a in dim): raise ValueError("All dimensions must be positive integers !") g = Graph() n_dim = len(dim) if n_dim==1: # Vertices are labeled from 0 to dim[0]-1 g = PathGraph(dim[0]) elif n_dim==2: # We use the Grid2dGraph generator to also get the positions g = Grid2dGraph(*dim) elif n_dim>2: # Vertices are tuples of dimension n_dim, and the graph contains at # least vertex (0, 0, ..., 0) g.add_vertex(tuple([0]*n_dim)) import itertools for u in itertools.product(*[range(d) for d in dim]): for i in range(n_dim): if u[i]+1<dim[i]: v = list(u) v[i] = u[i]+1 g.add_edge(u, tuple(v)) g.name("Grid Graph for {}".format(dim)) return g
def ChessboardGraphGenerator(dim_list, rook = True, rook_radius = None, bishop = True, bishop_radius = None, knight = True, knight_x = 1, knight_y = 2, relabel = False): r""" Returns a Graph built on a `d`-dimensional chessboard with prescribed dimensions and interconnections. This function allows to generate many kinds of graphs corresponding to legal movements on a `d`-dimensional chessboard: Queen Graph, King Graph, Knight Graphs, Bishop Graph, and many generalizations. It also allows to avoid redondant code. INPUT: - ``dim_list`` -- an iterable object (list, set, dict) providing the dimensions `n_1, n_2, \ldots, n_d`, with `n_i \geq 1`, of the chessboard. - ``rook`` -- (default: ``True``) boolean value indicating if the chess piece is able to move as a rook, that is at any distance along a dimension. - ``rook_radius`` -- (default: None) integer value restricting the rook-like movements to distance at most `rook_radius`. - ``bishop`` -- (default: ``True``) boolean value indicating if the chess piece is able to move like a bishop, that is along diagonals. - ``bishop_radius`` -- (default: None) integer value restricting the bishop-like movements to distance at most `bishop_radius`. - ``knight`` -- (default: ``True``) boolean value indicating if the chess piece is able to move like a knight. - ``knight_x`` -- (default: 1) integer indicating the number on steps the chess piece moves in one dimension when moving like a knight. - ``knight_y`` -- (default: 2) integer indicating the number on steps the chess piece moves in the second dimension when moving like a knight. - ``relabel`` -- (default: ``False``) a boolean set to ``True`` if vertices must be relabeled as integers. OUTPUT: - A Graph build on a `d`-dimensional chessboard with prescribed dimensions, and with edges according given parameters. - A string encoding the dimensions. This is mainly useful for providing names to graphs. EXAMPLES: A `(2,2)`-King Graph is isomorphic to the complete graph on 4 vertices:: sage: G, _ = graphs.ChessboardGraphGenerator( [2,2] ) sage: G.is_isomorphic( graphs.CompleteGraph(4) ) True A Rook's Graph in 2 dimensions is isomporphic to the Cartesian product of 2 complete graphs:: sage: G, _ = graphs.ChessboardGraphGenerator( [3,4], rook=True, rook_radius=None, bishop=False, knight=False ) sage: H = ( graphs.CompleteGraph(3) ).cartesian_product( graphs.CompleteGraph(4) ) sage: G.is_isomorphic(H) True TESTS: Giving dimensions less than 2:: sage: graphs.ChessboardGraphGenerator( [0, 2] ) Traceback (most recent call last): ... ValueError: The dimensions must be positive integers larger than 1. Giving non integer dimensions:: sage: graphs.ChessboardGraphGenerator( [4.5, 2] ) Traceback (most recent call last): ... ValueError: The dimensions must be positive integers larger than 1. Giving too few dimensions:: sage: graphs.ChessboardGraphGenerator( [2] ) Traceback (most recent call last): ... ValueError: The chessboard must have at least 2 dimensions. Giving a non-iterable object as first parameter:: sage: graphs.ChessboardGraphGenerator( 2, 3 ) Traceback (most recent call last): ... TypeError: The first parameter must be an iterable object. Giving too small rook radius:: sage: graphs.ChessboardGraphGenerator( [2, 3], rook=True, rook_radius=0 ) Traceback (most recent call last): ... ValueError: The rook_radius must be either None or have an integer value >= 1. Giving wrong values for knights movements:: sage: graphs.ChessboardGraphGenerator( [2, 3], rook=False, bishop=False, knight=True, knight_x=1, knight_y=-1 ) Traceback (most recent call last): ... ValueError: The knight_x and knight_y values must be integers of value >= 1. """ from sage.rings.integer_ring import ZZ # We decode the dimensions of the chessboard try: dim = list(dim_list) nb_dim = len(dim) except TypeError: raise TypeError('The first parameter must be an iterable object.') if nb_dim < 2: raise ValueError('The chessboard must have at least 2 dimensions.') if any(a not in ZZ or a < 1 for a in dim): raise ValueError('The dimensions must be positive integers larger than 1.') dimstr = str(tuple(dim)) # We check the radius toward neighbors if rook: if rook_radius is None: rook_radius = max(dim) elif not rook_radius in ZZ or rook_radius < 1: raise ValueError('The rook_radius must be either None or have an integer value >= 1.') if bishop: if bishop_radius is None: bishop_radius = max(dim) elif not bishop_radius in ZZ or bishop_radius < 1: raise ValueError('The bishop_radius must be either None or have an integer value >= 1.') if knight and ( not knight_x in ZZ or not knight_y in ZZ or knight_x < 1 or knight_y < 1 ): raise ValueError('The knight_x and knight_y values must be integers of value >= 1.') # We build the set of vertices of the d-dimensionnal chessboard from itertools import product V = [list(x) for x in list(product(*[range(_) for _ in dim]))] from sage.combinat.combination import Combinations combin = Combinations(range(nb_dim),2) from sage.graphs.graph import Graph G = Graph() for u in V: uu = tuple(u) G.add_vertex(uu) if rook: # We add edges to vertices we can reach when moving in one dimension for d in xrange(nb_dim): v = u[:] for k in xrange(v[d]+1, min(dim[d],v[d]+1+rook_radius)): v[d] = k G.add_edge( uu, tuple(v) ) if bishop or knight: # We add edges to vertices we can reach when moving in two dimensions for dx,dy in combin: n = dim[dx] m = dim[dy] v = u[:] i = u[dx] j = u[dy] if bishop: # Diagonal for k in xrange(1, min(n-i,m-j,bishop_radius+1)): v[dx] = i+k v[dy] = j+k G.add_edge( uu, tuple(v) ) # Anti-diagonal for k in xrange(min(i, m-j-1, bishop_radius)): v[dx] = i-k-1 v[dy] = j+k+1 G.add_edge( uu, tuple(v) ) if knight: # Moving knight_x in one dimension and knight_y in another # dimension if i+knight_y < n: if j+knight_x < m: v[dx] = i+knight_y v[dy] = j+knight_x G.add_edge( uu, tuple(v) ) if j-knight_x >= 0: v[dx] = i+knight_y v[dy] = j-knight_x G.add_edge( uu, tuple(v) ) if j+knight_y < m: if i+knight_x < n: v[dx] = i+knight_x v[dy] = j+knight_y G.add_edge( uu, tuple(v) ) if i-knight_x >= 0: v[dx] = i-knight_x v[dy] = j+knight_y G.add_edge( uu, tuple(v) ) if relabel: G.relabel( inplace=True ) return G, dimstr
def ChessboardGraphGenerator(dim_list, rook=True, rook_radius=None, bishop=True, bishop_radius=None, knight=True, knight_x=1, knight_y=2, relabel=False): r""" Returns a Graph built on a `d`-dimensional chessboard with prescribed dimensions and interconnections. This function allows to generate many kinds of graphs corresponding to legal movements on a `d`-dimensional chessboard: Queen Graph, King Graph, Knight Graphs, Bishop Graph, and many generalizations. It also allows to avoid redondant code. INPUT: - ``dim_list`` -- an iterable object (list, set, dict) providing the dimensions `n_1, n_2, \ldots, n_d`, with `n_i \geq 1`, of the chessboard. - ``rook`` -- (default: ``True``) boolean value indicating if the chess piece is able to move as a rook, that is at any distance along a dimension. - ``rook_radius`` -- (default: None) integer value restricting the rook-like movements to distance at most `rook_radius`. - ``bishop`` -- (default: ``True``) boolean value indicating if the chess piece is able to move like a bishop, that is along diagonals. - ``bishop_radius`` -- (default: None) integer value restricting the bishop-like movements to distance at most `bishop_radius`. - ``knight`` -- (default: ``True``) boolean value indicating if the chess piece is able to move like a knight. - ``knight_x`` -- (default: 1) integer indicating the number on steps the chess piece moves in one dimension when moving like a knight. - ``knight_y`` -- (default: 2) integer indicating the number on steps the chess piece moves in the second dimension when moving like a knight. - ``relabel`` -- (default: ``False``) a boolean set to ``True`` if vertices must be relabeled as integers. OUTPUT: - A Graph build on a `d`-dimensional chessboard with prescribed dimensions, and with edges according given parameters. - A string encoding the dimensions. This is mainly useful for providing names to graphs. EXAMPLES: A `(2,2)`-King Graph is isomorphic to the complete graph on 4 vertices:: sage: G, _ = graphs.ChessboardGraphGenerator( [2,2] ) sage: G.is_isomorphic( graphs.CompleteGraph(4) ) True A Rook's Graph in 2 dimensions is isomporphic to the Cartesian product of 2 complete graphs:: sage: G, _ = graphs.ChessboardGraphGenerator( [3,4], rook=True, rook_radius=None, bishop=False, knight=False ) sage: H = ( graphs.CompleteGraph(3) ).cartesian_product( graphs.CompleteGraph(4) ) sage: G.is_isomorphic(H) True TESTS: Giving dimensions less than 2:: sage: graphs.ChessboardGraphGenerator( [0, 2] ) Traceback (most recent call last): ... ValueError: The dimensions must be positive integers larger than 1. Giving non integer dimensions:: sage: graphs.ChessboardGraphGenerator( [4.5, 2] ) Traceback (most recent call last): ... ValueError: The dimensions must be positive integers larger than 1. Giving too few dimensions:: sage: graphs.ChessboardGraphGenerator( [2] ) Traceback (most recent call last): ... ValueError: The chessboard must have at least 2 dimensions. Giving a non-iterable object as first parameter:: sage: graphs.ChessboardGraphGenerator( 2, 3 ) Traceback (most recent call last): ... TypeError: The first parameter must be an iterable object. Giving too small rook radius:: sage: graphs.ChessboardGraphGenerator( [2, 3], rook=True, rook_radius=0 ) Traceback (most recent call last): ... ValueError: The rook_radius must be either None or have an integer value >= 1. Giving wrong values for knights movements:: sage: graphs.ChessboardGraphGenerator( [2, 3], rook=False, bishop=False, knight=True, knight_x=1, knight_y=-1 ) Traceback (most recent call last): ... ValueError: The knight_x and knight_y values must be integers of value >= 1. """ from sage.rings.integer_ring import ZZ # We decode the dimensions of the chessboard try: dim = list(dim_list) nb_dim = len(dim) except TypeError: raise TypeError('The first parameter must be an iterable object.') if nb_dim < 2: raise ValueError('The chessboard must have at least 2 dimensions.') if any(a not in ZZ or a < 1 for a in dim): raise ValueError( 'The dimensions must be positive integers larger than 1.') dimstr = str(tuple(dim)) # We check the radius toward neighbors if rook: if rook_radius is None: rook_radius = max(dim) elif not rook_radius in ZZ or rook_radius < 1: raise ValueError( 'The rook_radius must be either None or have an integer value >= 1.' ) if bishop: if bishop_radius is None: bishop_radius = max(dim) elif not bishop_radius in ZZ or bishop_radius < 1: raise ValueError( 'The bishop_radius must be either None or have an integer value >= 1.' ) if knight and (not knight_x in ZZ or not knight_y in ZZ or knight_x < 1 or knight_y < 1): raise ValueError( 'The knight_x and knight_y values must be integers of value >= 1.') # We build the set of vertices of the d-dimensionnal chessboard from itertools import product V = [list(x) for x in list(product(*[range(_) for _ in dim]))] from sage.combinat.combination import Combinations combin = Combinations(range(nb_dim), 2) from sage.graphs.graph import Graph G = Graph() for u in V: uu = tuple(u) G.add_vertex(uu) if rook: # We add edges to vertices we can reach when moving in one dimension for d in xrange(nb_dim): v = u[:] for k in xrange(v[d] + 1, min(dim[d], v[d] + 1 + rook_radius)): v[d] = k G.add_edge(uu, tuple(v)) if bishop or knight: # We add edges to vertices we can reach when moving in two dimensions for dx, dy in combin: n = dim[dx] m = dim[dy] v = u[:] i = u[dx] j = u[dy] if bishop: # Diagonal for k in xrange(1, min(n - i, m - j, bishop_radius + 1)): v[dx] = i + k v[dy] = j + k G.add_edge(uu, tuple(v)) # Anti-diagonal for k in xrange(min(i, m - j - 1, bishop_radius)): v[dx] = i - k - 1 v[dy] = j + k + 1 G.add_edge(uu, tuple(v)) if knight: # Moving knight_x in one dimension and knight_y in another # dimension if i + knight_y < n: if j + knight_x < m: v[dx] = i + knight_y v[dy] = j + knight_x G.add_edge(uu, tuple(v)) if j - knight_x >= 0: v[dx] = i + knight_y v[dy] = j - knight_x G.add_edge(uu, tuple(v)) if j + knight_y < m: if i + knight_x < n: v[dx] = i + knight_x v[dy] = j + knight_y G.add_edge(uu, tuple(v)) if i - knight_x >= 0: v[dx] = i - knight_x v[dy] = j + knight_y G.add_edge(uu, tuple(v)) if relabel: G.relabel(inplace=True) return G, dimstr
def core_subgraph_generator(NCS, forbid_identifications={}): # NCS is an object from the NaturalCoreSubgraph class which holds all the information we need. # forbid_identifications is a dictionary. Any foo:bar entries (foo is a vertex, bar is a set) will forbid foo from being identified with any element of bar. When running check_all_realizations_of_configuration, forbid_identifications is empty. When running check_all_realizations_from_expanded_c7a4x5x5_case, we will have added entries to forbid_identifications. # We use Observation ? to make a dictionary of all the forbidden identifications, in addition to what was given by forbid_identifications. Then, we generate all identifications (partitions of the vertex set) which avoid these forbidden identifications. For each identification, we check each of the three conditions from Observation ??. For any identifications that make it through the three conditions, we yield the graph formed by the identification and a list of its faces. # forbidden_dict will be the dictionary of all the forbidden identifications. # Initialize each vertex to have any empty set of forbidden vertices. forbidden_dict = {x: set() for x in range(NCS.order)} # If we have manually entered any forbidden identifications, go ahead and add those first. for x in forbid_identifications.keys(): forbidden_dict[x] |= set(forbid_identifications[x]) # If we have an open central face, then we apply Observation ? to the vertices on the spine before moving on to the specified faces. if NCS.is_open: init_spec_face = 1 # This will tell a later loop to skip the central face. # Vertices on the spine cannot be identified which are too close. # Handle vertices close to the ends separately. # Note: for any open configurations in the target set, the spine has at least four vertices. forbidden_dict[NCS.spine[0]] |= set(NCS.spine[:3]) forbidden_dict[NCS.spine[1]] |= set(NCS.spine[:4]) forbidden_dict[NCS.spine[-2]] |= set(NCS.spine[-4:]) forbidden_dict[NCS.spine[-1]] |= set(NCS.spine[-3:]) # Now handle all middle vertices on the spine. for i in range(2, len(NCS.spine) - 2): forbidden_dict[NCS.spine[i]] |= set(NCS.spine[i - 2:i + 3]) # Furthermore, spine vertices which are joining two specified outside faces ('sandwiched' vertices) cannot be identified with anything else on the spine. sandwiched_vertices = { x for x in range(1, len(NCS.facial_length_list)) if NCS.facial_length_list[x - 1] != 'x' and NCS.facial_length_list[x] != 'x' } for v in NCS.spine: if v in sandwiched_vertices: forbidden_dict[v] |= set(NCS.spine) else: forbidden_dict[v] |= set(sandwiched_vertices) else: # If we have a specified central face length... init_spec_face = 0 # ...then we tell the next loop to include the central face. # Now, for all specified faces, we apply Observation ??. No two vertices on the same specified face can be identified. for face in NCS.faces[init_spec_face:]: for v in face: forbidden_dict[v] |= set(face) # Now that we have finished prepared forbidden_dict, we feed it to the partition generator to inspect the identifications. # Initialize the printing variables. count = 0 # Counts the number of partitions being checked. countCS = 0 # Counts how many made it to be yielded as core subgraphs. print "#Partitions #CS Time (Cum.)" print "..........0 0 begin!" begin = time.clock() # restricted_partition_generator generates all identifications of the vertices (partitions of the vertex set). Its output is a partition, stored as a list of lists of vertices. for partition in restricted_partition_generator(range(NCS.order), NCS.order - 1, forbidden_dict): count += 1 if count % 10000 == 0: # Print every so often -- more helpful for large configurations. print "." * max(11 - len(str(count)), 0) + str(count) + " " * max( 9 - len(str(countCS)), 0) + str(countCS) + " " + timestring(time.clock() - begin) # We will want to move between the current labelings of the initial plane subgraph and the new graph formed by the identification. ident_dict will help with this by storing foo:bar where foo is a vertex from the original graph and bar is a vertex in the identified graph. If vertices v1 and v2 are identified, then ident_dict[v1] == ident_dict[v2]. The labels of the vertices in the identified graph are the indices of the parts in partition. ident_dict = { y: x for x in range(len(partition)) for y in partition[x] } # To start checking the identified graph, we make the set of edges in the identified graph. new_edges = set() # For each edge in the old graph between v1 and v2, we add an edge between the parts y1 and y2 (where y1 is the part containing v1 and y2 is the part containing v2). Since we are using a set, any duplicate edges get thrown out. for e in NCS.edges: a = ident_dict[e[0]] b = ident_dict[e[1]] if a > b: # We enforce an ordering so there are no duplicates via reversal. Given a partition, an edge (X,Y) in the identified graph is represented by an edge (x1,y1) where x1 in X and y1 in Y. However, it might also be represented by an edge (y2,x2) where x2 in X and y2 in Y. We don't want to represent an edge more than once in our set new_edges. a, b = b, a new_edges.add((a, b)) # Note: Our dictionary of forbidden identifications ensures that a != b. some_condition_failed = False # If some_condition_failed switches to True, then we stop checking this partition and continue to the next partition. # Now that we have our edges, our first condition to check is if the identified graph is subcubic. (Observation ?.1) # For each vertex in the identified graph, we count how many edges it is in. for x in range(len(partition)): degree_of_x = 0 for e in new_edges: if x in e: degree_of_x += 1 if degree_of_x > 3: # If we find a new vertex of degree more than 3, break immediately and continue to the next partition. some_condition_failed = True break if some_condition_failed: continue # To check the next conditions, we use the actual Graph object and get information about its specified faces. # First, get the graph. J = Graph(list(new_edges)) # The graph we will check (J_prime) might have an additional edge to close the central face. J_prime = Graph(J) # We add the edge if the configuration is open and the spine ends were not identified. new_end_1, new_end_2 = ident_dict[NCS.faces[0][0]], ident_dict[ NCS.faces[0][-1]] if NCS.is_open and new_end_1 != new_end_2: J_prime.add_edge( [new_end_1, new_end_2] ) # If the graph already has this edge, that is okay. Then the edge is already serving the desired purpose. # Then, get the list of central/specified faces for this new graph. We will need to check for possible identification of faces in the identification process, as we want the faces to be distinct when we check the next two conditions of Observation ? and get the open regions of the graph. # Note: We are not checking that the faces have been identified with the same orientations of their boundaries (clockwise vs. counterclockwise). Although this happens to be a necessary condition for a core subgraph, it is enforced by our planarity check later. new_faces = [ ] # Initialize new faces -- it will be the faces of J_prime. new_faces.append([ident_dict[x] for x in NCS.faces[0]]) # Start with central face. central_face = new_faces[ 0] # It will help readability to refer to 'central_face' instead of 'new_faces[0]'. # If the central face length is unspecified and the spine ends were identified, then we need to remove one of the spine ends to avoid duplicate vertices in the face list. if NCS.is_open and new_end_1 == new_end_2: central_face.pop() # Now we deal with the rest of the faces. for face in NCS.faces[1:]: new_face = [ident_dict[x] for x in face] # Convert to new vertex labels. for prev_face in new_faces[ 1:]: # We start comparison at index 1 to avoid comparing with the central face. No specified face can be identified with the central face because the resulting face would contain a cut edge (the shared edge between the two faces in the natural core subgraph). Since the face must also have a length at most 9, this violates Lemma ?. if set(new_face) == set( prev_face ): # If the set of vertices of the current new face is the same set of vertices as a previous face, then these faces must have been identified (because the graph is not a cycle). break else: new_faces.append(new_face) # Now we can check to see if the identified graph has any trapped 2-vertices. (Observation ?.3 -- we check this before Observation ?.2 because it is easier) for v in J_prime.vertices(): if J_prime.degree(v) != 2: continue # The 2-vertex v is "trapped" if it appears more than once on the boundary walks of the fixed faces of J_prime. boundary_count_for_v = 0 for face in new_faces: boundary_count_for_v += face.count(v) if boundary_count_for_v > 1: some_condition_failed = True break if some_condition_failed: continue # Now we begin checking Observation ?.2. The first thing we check is that the spine does not wrap on itself -- Observation ?.2 says that the spinal path should be a boundary walk of a face in J_prime. A boundary walk does not repeat an edge in the same direction. # (We need to check this only when the spine was open to begin with.) if NCS.is_open: for i, j in combinations(range(len(central_face) - 1), 2): if central_face[i] == central_face[j] and central_face[ i + 1] == central_face[j + 1]: some_condition_failed = True break if some_condition_failed: continue # Now we finish checking Observation ?.2. For each fixed face of J_prime, we add a new hub vertex to be a representative for the face. Then for each edge on the boundary of the face, another vertex is added which is connected to both the endpoints of the edge and to the midpoint of the edge and to the hub vertex representing the face. In other words, we glue a wheel-like structure into each face in a particular way along the face boundary. Then, we will check the resulting graph for planarity. i = J_prime.order() # i will index the label of each new vertex. # First, we subdivide each edge so that we can connect some of our new vertices to the midpoints of our original edges. edge_dict = { } # edge_dict will keep track of the labels of the midpoints of our original edges. for edge in J_prime.edges( ): # The Graph edges iterator does not add edges part-way through the for loop. Even though the edges of J_prime are changing over the course of the for loop, the objects in the iterator remain only those originally in J_prime when the for loop first started. J_prime.subdivide_edge((edge[0], edge[1]), 1) edge_dict[(edge[0], edge[1])] = i edge_dict[( edge[1], edge[0] )] = i # Keep track of both orientations of the edge -- we do not know which orientations we will need. i += 1 for face in new_faces: face_hub_vertex = i # face_hub_vertex is the hub of the wheel-like structure being embedded on the face. i += 1 J_prime.add_vertex(face_hub_vertex) for j in range(len(face)): edge_hub_vertex = i # For each edge on the face, edge_hub_vertex is the vertex which is adjacent to face_hub_vertex and also to both endpoints and midpoint of the edge. i += 1 J_prime.add_vertex(edge_hub_vertex) J_prime.add_edge( (edge_hub_vertex, face[j - 1]) ) # First endpoint. Still works when j==0 due to Python's indexing. J_prime.add_edge( (edge_hub_vertex, face[j])) # Second endpoint. J_prime.add_edge( (edge_hub_vertex, edge_dict[(face[j - 1], face[j])])) # Midpoint. J_prime.add_edge((edge_hub_vertex, face_hub_vertex)) # Finally, check for planarity. if J_prime.is_planar(): # If we made it this far, then all conditions were passed! countCS += 1 # Print info and yield J (not J_prime). print print " >>> " + "Core Subgraph #%d" % ( countCS), "(Partition #%d)" % (count) print " " * 10 + "Partition: %s" % (str(partition)) # We print the edges ten to each line. ed = [e[:2] for e in J.edges()] ed_str_li = [] rows = len(ed) / 10 for x in range(rows): ed_str_li.append(str(ed[10 * x:10 * (x + 1)])[1:-1] + ",") ed_str_li.append(str(ed[10 * rows:])[1:-1]) print " " * 10 + "Edges: %s" % (ed_str_li[0]) for s in ed_str_li[1:]: print " " * 17 + s yield J, new_faces print "Total #partitions: ", count
def geometric_basis(G, E, p): r""" Return a geometric basis, based on a vertex. INPUT: - ``G`` -- the graph of the bounded regions of a Voronoi Diagram - ``E`` -- the subgraph of ``G`` formed by the edges that touch an unbounded region - ``p`` -- a vertex of ``E`` OUTPUT: A geometric basis. It is formed by a list of sequences of paths. Each path is a list of vertices, that form a closed path in `G`, based at `p`, that goes to a region, surrounds it, and comes back by the same path it came. The concatenation of all these paths is equivalent to `E`. EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import geometric_basis sage: points = [(-3,0),(3,0),(0,3),(0,-3)]+ [(0,0),(0,-1),(0,1),(1,0),(-1,0)] sage: V = VoronoiDiagram(points) sage: G = Graph() sage: for reg in V.regions().values(): ....: G = G.union(reg.vertex_graph()) ....: sage: E = Graph() sage: for reg in V.regions().values(): ....: if reg.rays() or reg.lines(): ....: E = E.union(reg.vertex_graph()) ....: sage: p = E.vertices()[0] sage: geometric_basis(G, E, p) [[A vertex at (-2, -2), A vertex at (2, -2), A vertex at (2, 2), A vertex at (1/2, 1/2), A vertex at (1/2, -1/2), A vertex at (2, -2), A vertex at (-2, -2)], [A vertex at (-2, -2), A vertex at (2, -2), A vertex at (1/2, -1/2), A vertex at (1/2, 1/2), A vertex at (-1/2, 1/2), A vertex at (-1/2, -1/2), A vertex at (1/2, -1/2), A vertex at (2, -2), A vertex at (-2, -2)], [A vertex at (-2, -2), A vertex at (2, -2), A vertex at (1/2, -1/2), A vertex at (-1/2, -1/2), A vertex at (-2, -2)], [A vertex at (-2, -2), A vertex at (-1/2, -1/2), A vertex at (-1/2, 1/2), A vertex at (1/2, 1/2), A vertex at (2, 2), A vertex at (-2, 2), A vertex at (-1/2, 1/2), A vertex at (-1/2, -1/2), A vertex at (-2, -2)], [A vertex at (-2, -2), A vertex at (-1/2, -1/2), A vertex at (-1/2, 1/2), A vertex at (-2, 2), A vertex at (-2, -2)]] """ EC = [v[0] for v in orient_circuit(E.eulerian_circuit())] i = EC.index(p) EC = EC[ i:] + EC[:i + 1] # A counterclockwise eulerian circuit on the boundary, based at p if len(G.edges()) == len(E.edges()): if E.is_cycle(): return [EC] I = Graph() for e in G.edges(): if not E.has_edge(e): I.add_edge(e) # interior graph # treat the case where I is empty if not I.vertices(): for v in E.vertices(): if len(E.neighbors(v)) > 2: I.add_vertex(v) for i in range(len(EC)): # q and r are the points we will cut through if EC[i] in I.vertices(): q = EC[i] connecting_path = EC[:i] break elif EC[-i] in I.vertices(): q = EC[-i] connecting_path = list(reversed(EC[-i:])) break distancequotients = [ (E.distance(q, v)**2 / I.distance(q, v), v) for v in E.vertices() if v in I.connected_component_containing_vertex(q) and not v == q ] r = max(distancequotients)[1] cutpath = I.shortest_path(q, r) Gcut = copy(G) Ecut = copy(E) Ecut.delete_vertices([q, r]) Gcut.delete_vertices(cutpath) # I think this cannot happen, but just in case, we check it to raise # an error instead of giving a wrong answer if Gcut.connected_components_number() != 2: raise ValueError("unable to compute a correct path") G1, G2 = Gcut.connected_components_subgraphs() for v in cutpath: neighs = G.neighbors(v) for n in neighs: if n in G1.vertices() + cutpath: G1.add_edge(v, n, None) if n in G2.vertices() + cutpath: G2.add_edge(v, n, None) if EC[EC.index(q) + 1] in G2.vertices(): G1, G2 = G2, G1 E1, E2 = Ecut.connected_components_subgraphs() if EC[EC.index(q) + 1] in E2.vertices(): E1, E2 = E2, E1 for i in range(len(cutpath) - 1): E1.add_edge(cutpath[i], cutpath[i + 1], None) E2.add_edge(cutpath[i], cutpath[i + 1], None) for v in [q, r]: for n in E.neighbors(v): if n in E1.vertices(): E1.add_edge(v, n, None) if n in E2.vertices(): E2.add_edge(v, n, None) gb1 = geometric_basis(G1, E1, q) gb2 = geometric_basis(G2, E2, q) resul = [ connecting_path + path + list(reversed(connecting_path)) for path in gb1 + gb2 ] for r in resul: i = 0 while i < len(r) - 2: if r[i] == r[i + 2]: r.pop(i) r.pop(i) if i > 0: i -= 1 else: i += 1 return resul
def random_cycle_paths(max_cycle_len, max_path_len, max_vertices, p=0, min_cycle_len=1): '''Funkcja losująca grafy nieskierowane o dokładnie jedym cyklu, z którego wychodzą ścieżki dwóch typów. Pierwszym z nich jest typ "zewnętrzny", czyli ścieżki, które zaczynają się w jednym z wierzchołków cyklu i których pozostałe wierzchołki są z cyklem rozłączne. Drugi typ jest "wewnętrzny", do którego należą ścieżki o dwóch wierzchołkach końcowych należących do cyklu lub innej ścieżki wewnętrznej. Wszystkie ścieżki wewnętrzne są losowane tak, żeby graf był planarny pod warukiem, że rysowanie ścieżek wewnętrznych ograniczamy do wnętrza cyklu. :param min_cycle_len: Int Minimalna dozwolona długość cyklu. :param max_cycle_len: Int Największa dozwolona długość cyklu. Jeżeli długość wylosowanego cyklu będzie równa `1`, to wygenerowany zostanie wierzchołek, a jeżeli będzie równa `2`, to wygenerowana zostanie krawędź. :param max_path_len: Int Największa dozwolona długość ścieżki wychodzącej z cyklu. :param p: Float Określa, z jakim prawdopodobieństwem do grafu zostanie dodana kolejna ścieżka zewnętrzna. Z prawdopodobieństwem `1-p` zostanie dodana krawędź wewnętrzna. :param max_vertices: Int Największa dozwolona liczba krawędzi. :return: tuple Para składająca się z opisanego grafu skierowanego, oraz listy wierzchołków składającej się z wierzchołków cyklu oraz ścieżek "zewnętrznych". ''' if p < 0 or p > 1: raise ValueError("Niepoprawna wartość prawdopodobieństwa. `p` musi " "należeć do przedziału [0, 1]") if min_cycle_len < 1: raise ValueError("Minimalna długość cyklu nie może być mniejsza od 1.") if min_cycle_len > max_cycle_len: raise ValueError("Minimalna długość cyklu musi być mniejsza lub równa " "maksymalnej.") if min_cycle_len < 3 and p < 1: warnings.warn("Minimalna długość cyklu pozwala na stworzenie cykli " "bez wnętrza, a wartość `p` dopuszcza istnienie ścieżek " "wewnętrznych. W przypadku wylosowania krótkiego cyklu, " "wszystkie ścieżki będą zewnętrzne.") G = Graph() cycle_len = np.random.randint(min_cycle_len, max_cycle_len + 1) if cycle_len == 1: p = 1 G.add_vertex(0) outside_vertices = [0] elif cycle_len == 2: p = 1 G.add_edge((0, 1)) outside_vertices = [0, 1] else: G.add_cycle(list(range(0, cycle_len))) outside_vertices = list(range(0, cycle_len)) n = cycle_len got_max_vertices = n >= max_vertices cycle_partitions = [list(range(0, n))] for i in range(cycle_len): if got_max_vertices: break n_paths = np.random.poisson(1) path_lengths = np.random.poisson(lam=int(max_path_len / 2), size=n_paths) for path_length in path_lengths: if n + path_length > max_vertices: path_length = max_vertices - n got_max_vertices = True if path_length == 0: if got_max_vertices: break else: continue if np.random.rand(1) > p: available_parts = [tab for tab in cycle_partitions if i in tab] cycle_part = \ available_parts[np.random.randint(0, len(available_parts))] j = i while j == i: j = np.random.choice(cycle_part) # split k = 0 parts = [[], []] part_id = 0 new_path = list(range(n, n + path_length - 1)) for k in cycle_part: parts[part_id].append(k) if k in [i, j]: if k == i: parts[part_id] += new_path else: parts[part_id] += new_path[::-1] part_id = 1 - part_id parts[part_id].append(k) cycle_partitions.remove(cycle_part) cycle_partitions.append(parts[0]) cycle_partitions.append(parts[1]) G.add_path([i] + new_path + [j]) else: G.add_path([i] + list(range(n, n + path_length))) outside_vertices += list(range(n, n + path_length)) n += path_length if got_max_vertices: break return G, outside_vertices