def dijkstra(graph, source, dest): """ Given a graph, source, dest, return the path of least cost between the source and dest * assumes the graph is a correct defaultdict generated by graph_utils.py's "make_graph" Args: graph: the standard graph defaultdict that represents vertices and edges (see graph.py) source: a string, representing the source vertex in the graph dest: a string, representing the terminal vertex in the graph Returns: The (path of the least-cost route, cost of the route cost), or 'NO SUCH ROUTE' if it doesn't exist Raises: DoesNotExistError: If the source or dest strings don't exist in the graph """ ''' ERROR HANDLING ''' # make sure the vertices exist in the graph for vertex in [source, dest]: if vertex not in [v for (v, tuple) in graph.iteritems()]: raise errors.DoesNotExistError( '"{0}" does not exist as a connected vertex in the graph'. format(vertex)) # priority queue, composed of tuples of (cost, source, path) q = [] # keeps track of the visited nodes seen = set() # however, if the source is terminal already, we don't want to return 0 # we avoid this by adding neighbouring vertices to priority queue if source == dest: for (v2, cost) in graph.get(source): heapq.heappush(q, (0 + cost, v2, [source])) else: # otherwise, we begin with 0 cost, graph source, no path heapq.heappush(q, (0, source, [])) while q: # while there are elements in the priority queue (cost, v1, path) = heapq.heappop(q) # get the least cost path so far in the heap if v1 not in seen: # if we haven't visited v1 yet seen.add(v1) # consider v1 as visited # add the node to the end of the path path = path + [v1] if v1 == dest: return (path, cost) # for adjacent nodes, add them to the priority queue for (v2, c) in graph.get(v1, ()): if v2 not in seen: # as long as they haven't been visited already heapq.heappush(q, (cost + c, v2, path)) # there isn't a solution return 'NO SUCH ROUTE'
def route_distance(graph, route): """ Given a graph, route, return the total cost of the route just using dictionary lookup * assumes the graph is a correct defaultdict generated by graph_utils.py's "make_graph" Args: graph: the standard graph defaultdict that represents vertices and edges (see graph.py) route: a string, composed of only vertices representing the route to take in the graph Returns: The cost of the route in the graph, or 'NO SUCH ROUTE' if it doesn't exist Raises: DoesNotExistError: If one of the vertices on the route doesn't exist in the graph """ ''' ERROR HANDLING ''' # make sure the vertices exist in the graph for vertex in route: if vertex not in [v for (v, tuple) in graph.iteritems()]: raise errors.DoesNotExistError( '"{0}" does not exist as a connected vertex in the graph'. format(vertex)) cost = 0 for i in range(len(route) - 1): v1 = route[i] # get the vertex v1 of edge v1 -> v2 v2 = route[i + 1] # get the vertex v2 of edge v1 -> v2 edge_cost = dict(graph.get(v1)).get( v2, None) # get the cost between the two vertices if edge_cost == None: return 'NO SUCH ROUTE' cost += edge_cost return cost
def get_elem_bface_info_ver4(fo, mesh, phys_groups, num_phys_groups, gmsh_element_database): ''' This function imports element and boundary face info for Gmsh 4.1. Inputs: ------- fo: file object mesh: mesh object phys_groups: list of physical group objects num_phys_groups: number of physical groups gmsh_element_database: database on Gmsh elements Outputs: -------- fo: file object (current position is modified) mesh: mesh object (modified) ''' fl = fo.readline() lint = [int(l) for l in fl.split()] num_entity_blocks = lint[0] num_elems_bfaces = lint[1] if num_elems_bfaces == 0: raise ValueError("No elements or boundary faces to import") for _ in range(num_entity_blocks): fl = fo.readline() lint = [int(l) for l in fl.split()] ndims = lint[0] entity_tag = lint[1] etype = lint[2] # Gmsh element type num_in_block = lint[3] # Find physical boundary group found = False for phys_group in phys_groups: if entity_tag in phys_group.entity_tags and \ ndims == phys_group.ndims: found = True break if not found: raise errors.DoesNotExistError( "All elements and boundary " + "faces must be assigned to a physical group") if ndims == mesh.ndims: # Element # Get element type data gorder = gmsh_element_database[etype].gorder gbasis = gmsh_element_database[etype].gbasis # Loop for _ in range(num_in_block): # Make sure only one type of volume element in type fo.readline() if mesh.num_elems == 0: mesh.set_params(gbasis=gbasis, gorder=gorder, num_elems=0) else: if gorder != mesh.gorder or gbasis != mesh.gbasis: raise ValueError(">1 element type not supported") # Increment number of elements mesh.num_elems += 1 elif ndims == mesh.ndims - 1: # Boundary face if phys_group.boundary_group_num >= 0: bgroup = mesh.boundary_groups[phys_group.name] else: # Group has not been assigned yet bgroup = mesh.add_boundary_group(phys_group.name) phys_group.boundary_group_num = bgroup.number # Loop and increment number of boundary faces for _ in range(num_in_block): fo.readline() bgroup.num_boundary_faces += 1 else: # Skip for _ in range(num_in_block): fo.readline() return mesh
def get_elem_bface_info_ver2(fo, mesh, phys_groups, num_phys_groups, gmsh_element_database): ''' This function imports element and boundary face info for Gmsh 2.2. Inputs: ------- fo: file object mesh: mesh object phys_groups: list of physical group objects num_phys_groups: number of physical groups gmsh_element_database: database on Gmsh elements Outputs: -------- fo: file object (current position is modified) mesh: mesh object (modified) ''' # Number of elements and boundary faces num_elems_bfaces = int(fo.readline()) if num_elems_bfaces == 0: raise ValueError("No elements or boundary faces to import") # Loop for _ in range(num_elems_bfaces): fl = fo.readline() ls = fl.split() etype = int(ls[1]) # Gmsh element type phys_num = int(ls[3]) # Extract physical group found = False for phys_group in phys_groups: if phys_group.gmsh_phys_num == phys_num: found = True break if not found: raise errors.DoesNotExistError( "All elements and boundary" + "faces must be assigned to a physical group") # Process if phys_group.ndims == mesh.ndims: # Element # Make sure only one type of volume element in type gorder = gmsh_element_database[etype].gorder gbasis = gmsh_element_database[etype].gbasis if mesh.num_elems == 0: mesh.set_params(gbasis=gbasis, gorder=gorder, num_elems=0) else: if gorder != mesh.gorder or gbasis != mesh.gbasis: raise ValueError(">1 element type not supported") # Increment number of elements mesh.num_elems += 1 elif phys_group.ndims == mesh.ndims - 1: # Boundary face try: bgroup = mesh.boundary_groups[phys_group.name] except KeyError: # Add new boundary group bgroup = mesh.add_boundary_group(phys_group.name) phys_group.boundary_group_num = bgroup.number # Increment number of boundary faces bgroup.num_boundary_faces += 1
def fill_mesh(fo, ver, mesh, phys_groups, num_phys_groups, gmsh_element_database, old_to_new_node_IDs): ''' This function fills the mesh. Inputs: ------- fo: file object ver: Gmsh file version mesh: mesh object phys_groups: list of physical group objects num_phys_groups: number of physical groups gmsh_element_database: Gmsh element database old_to_new_node_IDs: maps Gmsh-assigned (old) node IDs to new IDs Outputs: -------- fo: file object (current position is modified) mesh: mesh object (modified) ''' # Allocate boundary groups and faces for bgroup in mesh.boundary_groups.values(): bgroup.allocate_boundary_faces() # Allocate element-to-node_IDs map mesh.allocate_elem_to_node_IDs_map() # Over-allocate interior_faces since we haven't distinguished # between interior and boundary faces yet num_faces_per_elem = mesh.gbasis.NFACES mesh.num_interior_faces = mesh.num_elems * num_faces_per_elem mesh.allocate_interior_faces() # reset num_interior_faces - use as a counter mesh.num_interior_faces = 0 # Table to store face info and connect elements to faces node0_to_faces_info = [{} for n in range(mesh.num_nodes)] # list of dicts # Go to Gmsh elements section go_to_line_below_string(fo, "$Elements") # Boundary face counter num_bfaces_per_bgroup = [0 for i in range(mesh.num_boundary_groups)] # Process elements and boundary faces if ver == VERSION2: process_elems_bfaces_ver2(fo, mesh, phys_groups, num_phys_groups, gmsh_element_database, old_to_new_node_IDs, num_bfaces_per_bgroup, node0_to_faces_info) else: process_elems_bfaces_ver4(fo, mesh, phys_groups, num_phys_groups, gmsh_element_database, old_to_new_node_IDs, num_bfaces_per_bgroup, node0_to_faces_info) # Verify footer fl = fo.readline() if not fl.startswith("$EndElements"): raise errors.FileReadError # Fill boundary and interior face info for elem_ID in range(mesh.num_elems): for face_ID in range(mesh.gbasis.NFACES): # Get local q = 1 face nodes gbasis = mesh.gbasis local_node_nums = gbasis.get_local_face_principal_node_nums( mesh.gorder, face_ID) num_face_nodes = local_node_nums.shape[0] # Convert to global node IDs global_node_nums = mesh.elem_to_node_IDs[elem_ID][local_node_nums] # Add to face info table face_info, already_added = add_face_info_to_table( node0_to_faces_info, num_face_nodes, global_node_nums, False, -1, elem_ID, face_ID) if already_added: # Face was already added to table previously # Identify element as either boundary face or interior face if face_info.at_boundary: # Boundary face if face_info.num_adjacent_elems != 1: raise ValueError("More than one element adjacent " + "to boundary face") # Find physical group found = False for phys_group in phys_groups: if phys_group.boundary_group_num == \ face_info.boundary_group_num: found = True break if not found: raise errors.DoesNotExistError("Physical boundary " + "group not found") boundary_group = mesh.boundary_groups[phys_group.name] # Fill in info boundary_face = boundary_group.boundary_faces[ face_info.face_ID] boundary_face.elem_ID = elem_ID boundary_face.face_ID = face_ID else: # Interior face if face_info.num_adjacent_elems != 2: raise ValueError("More than two elements adjacent " + "to interior face") # Fill in info int_face = mesh.interior_faces[mesh.num_interior_faces] int_face.elemL_ID = face_info.elem_ID int_face.faceL_ID = face_info.face_ID int_face.elemR_ID = elem_ID int_face.faceR_ID = face_ID # Increment number of interior faces mesh.num_interior_faces += 1 delete_face_info_from_table(node0_to_faces_info, num_face_nodes, global_node_nums) # Any faces not accounted for? num_faces_left = 0 for n in range(mesh.num_nodes): faces_info = node0_to_faces_info[n] for node_IDs_sort in faces_info.keys(): print(node_IDs_sort) num_faces_left += 1 if num_faces_left != 0: raise ValueError("Above %d faces not identified" % (num_faces_left) + " as valid boundary or interior faces") # Make sure number of interior faces makes sense if mesh.num_interior_faces > mesh.num_elems * num_faces_per_elem: raise ValueError # Remove superfluous (empty) interior faces mesh.interior_faces = mesh.interior_faces[:mesh.num_interior_faces] # Create elements mesh.create_elements()
def process_elems_bfaces_ver4(fo, mesh, phys_groups, num_phys_groups, gmsh_element_database, old_to_new_node_IDs, num_bfaces_per_bgroup, node0_to_faces_info): ''' This function processes element and boundary face info for Gmsh 4.1. Inputs: ------- fo: file object mesh: mesh object phys_groups: list of physical group objects num_phys_groups: number of physical groups gmsh_element_database: Gmsh element database old_to_new_node_IDs: maps Gmsh-assigned (old) node IDs to new IDs num_bfaces_per_bgroup: number of boundary faces per boundary group node0_to_faces_info: see above description of add_face_info_to_table Outputs: -------- fo: file object (current position is modified) mesh: mesh object (modified) node0_to_faces_info: see above description of add_face_info_to_table (modified) ''' fl = fo.readline() lint = [int(l) for l in fl.split()] num_entity_blocks = lint[0] num_elems_bfaces = lint[1] num_elems = 0 # counter for number of elements for _ in range(num_entity_blocks): fl = fo.readline() lint = [int(l) for l in fl.split()] ndims = lint[0] entity_tag = lint[1] etype = lint[2] # Gmsh element type num_in_block = lint[3] if ndims == mesh.ndims: # Volume element # Get info gorder = gmsh_element_database[etype].gorder gbasis = gmsh_element_database[etype].gbasis # Extract and process nodes for _ in range(num_in_block): fl = fo.readline() lint = [int(l) for l in fl.split()] # Convert from Gmsh to quail node ordering and store nodes = np.array(lint[1:]) for n in range(len(nodes)): nodes[n] = old_to_new_node_IDs[nodes[n]] new_node_IDs = nodes[gmsh_element_database[etype].node_order] mesh.elem_to_node_IDs[num_elems] = new_node_IDs # Increment number of elements num_elems += 1 elif ndims == mesh.ndims - 1: # Boundary face # Find physical boundary group found = False for phys_group in phys_groups: if entity_tag in phys_group.entity_tags: if phys_group.ndims == ndims: bgroup_num = phys_group.boundary_group_num found = True break if not found: raise errors.DoesNotExistError("Physical boundary group " + "not found") bgroup = mesh.boundary_groups[phys_group.name] # Get info gbasis = gmsh_element_database[etype].gbasis num_face_nodes = gbasis.get_num_basis_coeff(1) # Loop for _ in range(num_in_block): fl = fo.readline() lint = [int(l) for l in fl.split()] # Convert node IDs nodes = np.array(lint[1:]) for n in range(len(nodes)): nodes[n] = old_to_new_node_IDs[nodes[n]] # Add face info to table _, _ = add_face_info_to_table( node0_to_faces_info, num_face_nodes, nodes, True, bgroup_num, -1, num_bfaces_per_bgroup[bgroup_num]) # Increment number of boundary faces num_bfaces_per_bgroup[bgroup_num] += 1 else: # Skip for _ in range(num_in_block): fo.readline()
def process_elems_bfaces_ver2(fo, mesh, phys_groups, num_phys_groups, gmsh_element_database, old_to_new_node_IDs, num_bfaces_per_bgroup, node0_to_faces_info): ''' This function processes element and boundary face info for Gmsh 2.2. Inputs: ------- fo: file object mesh: mesh object phys_groups: list of physical group objects num_phys_groups: number of physical groups gmsh_element_database: Gmsh element database old_to_new_node_IDs: maps Gmsh-assigned (old) node IDs to new IDs num_bfaces_per_bgroup: number of boundary faces per boundary group node0_to_faces_info: see above description of add_face_info_to_table Outputs: -------- fo: file object (current position is modified) mesh: mesh object (modified) node0_to_faces_info: see above description of add_face_info_to_table (modified) ''' num_elems_bfaces = int(fo.readline()) num_elems = 0 # counter for number of elements # Loop through entities for _ in range(num_elems_bfaces): fl = fo.readline() ls = fl.split() etype = int(ls[1]) # Gmsh element type phys_num = int(ls[3]) # Find physical group found = False for phys_group in phys_groups: if phys_group.gmsh_phys_num == phys_num: found = True break if not found: raise errors.DoesNotExistError("Physical group not found!") # Get nodes num_tags = int(ls[2]) # number of tags tag_offset = 3 # 3 integers (including num_tags) before tags start istart = num_tags + tag_offset # starting index of node numbering num_nodes = gmsh_element_database[etype].num_nodes elist = ls[istart:] # list of nodes (string format) if len(elist) != num_nodes: raise Exception("Wrong number of nodes") # Convert nodes IDs node_IDs = np.zeros(num_nodes, dtype=int) for i in range(num_nodes): node_IDs[i] = old_to_new_node_IDs[int(elist[i])] if phys_group.boundary_group_num >= 0: # This is a boundary face # Get info gbasis = gmsh_element_database[etype].gbasis gorder = gmsh_element_database[etype].gorder bgroup_num = phys_group.boundary_group_num bgroup = mesh.boundary_groups[phys_group.name] num_face_nodes = gbasis.get_num_basis_coeff(1) # Add face info to table _, _ = add_face_info_to_table(node0_to_faces_info, num_face_nodes, node_IDs, True, bgroup_num, -1, num_bfaces_per_bgroup[bgroup_num]) # Increment number of boundary faces num_bfaces_per_bgroup[bgroup_num] += 1 elif phys_group.boundary_group_num == -1: # This is a volume element # Get info gorder = gmsh_element_database[etype].gorder gbasis = gmsh_element_database[etype].gbasis num_nodes = gbasis.get_num_basis_coeff(gorder) # Sanity check if num_nodes != gmsh_element_database[etype].num_nodes: raise Exception("Number of nodes doesn't match up") # Convert from Gmsh node ordering to quail node ordering and # store new_node_IDs = node_IDs[gmsh_element_database[etype].node_order] mesh.elem_to_node_IDs[num_elems] = new_node_IDs # Increment elem counter num_elems += 1 else: raise ValueError
def get_num_bfs_paths(graph, source, dest, filter_type, operator, value): """ Given a graph, source, dest, operator, and value, return the number of paths between a source and terminal as long as they follow their path [filter_type = (vertex count/total cost)] is [operator = (less than/greater than/equal to)] a value. * assumes the graph is a correct defaultdict generated by graph_utils.py's "make_graph" Args: graph: the standard graph defaultdict that represents vertices and edges (see graph.py) source: a string, representing the source vertex in the graph dest: a string, representing the terminal vertex in the graph filter_type: a string in ['towns', 'distance'], used in relation with.. operator: a string in ['less_than', 'more_than', 'equal_to'], used in relation with.. value: an int, greater than or equal to 0. Returns: The number of paths between a source and terminal as long as they follow that their path [filter_type = (vertex count/total cost)] is [operator = (less than/greater than/equal to)] a value. Raises: DoesNotExistError: If the source or dest strings don't exist in the graph ValueError: If the operator filter_type is not in ['towns', 'distance'] ValueError: If the operator param is not in ['less_than', 'more_than', 'equal_to'] ValueError: If the value param is not a non-negative integer """ ''' ERROR HANDLING ''' # make sure the vertices exist in the graph for vertex in [source, dest]: if vertex not in [v for (v, tuple) in graph.iteritems()]: raise errors.DoesNotExistError( '"{0}" does not exist as a connected vertex in the graph'. format(vertex)) accepted_filter_type = ['towns', 'distance'] if filter_type not in accepted_filter_type: raise ValueError( "Unknown filter type, accepted filter types are: {0}".format( accepted_filter_type)) accepted_operators = ['less_than', 'more_than', 'equal_to'] if operator not in accepted_operators: raise ValueError( "Unknown operator, accepted operators are: {0}".format( accepted_operators)) try: if float(value) != int(value) or int(value) < 0: raise ValueError value = int(value) except ValueError: raise ValueError( "The value passed must be an integer, greater than or equal to 0") ''' FIND ALL PATHS BETWEEN SOURCE AND TERMINAL BETWEEN S AND T ''' bfs_paths = get_bfs_paths(graph, source, dest) ''' OUT OF THE ABOVE PATHS, FIND HOW MANY ADHERE TO THE FILTERTYPE, OPERATOR AND VALUE GIVEN ''' if filter_type == 'towns': bfs_paths = [bfs_path[0] for bfs_path in bfs_paths] return len([ bfs_path for bfs_path in bfs_paths if (operator == 'less_than' and len(bfs_path) - 1 < value) or ( operator == 'more_than' and len(bfs_path) - 1 > value) or ( operator == 'equal_to' and len(bfs_path) - 1 == value) ]) elif filter_type == 'distance': bfs_paths = [bfs_path[1] for bfs_path in bfs_paths] return len([ bfs_path for bfs_path in bfs_paths if (operator == 'less_than' and bfs_path < value) or ( operator == 'more_than' and bfs_path > value) or ( operator == 'equal_to' and bfs_path == value) ])