예제 #1
0
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'
예제 #2
0
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
예제 #3
0
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
예제 #4
0
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
예제 #5
0
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()
예제 #6
0
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()
예제 #7
0
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
예제 #8
0
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)
        ])