def is_perf_matching(g: Graph, m: List[Edge]) -> bool: """Is m a perfect matching in the graph g?""" if g.vertex_count() % 2 != 0 or len(m) != g.vertex_count() // 2: return False vts = set(reduce(lambda vs, e: vs + [e.u, e.v], m, [])) return len(vts) == g.vertex_count() and all([g.has_vertex(v) for v in vts])
def _cities_denmark(): """ Constructs a complete graph with vertices corresponding to Danish cities. An edge between two cities is weighted by the great-circle distance between them. Returns the graph and the city names as a pair. Vertex v corresponds to city at index v. """ names = [] positions = [] with open(cities_path) as reader: for line in reader.readlines(): name, pos = line.split(",") lat, lon = pos.split("/") names.append(name) positions.append((float(lat), float(lon))) g = Graph(len(names)) for u in range(len(names)): (lat1, lon1) = positions[u] for v in range(u + 1, len(names)): (lat2, lon2) = positions[v] g.add_edge(u, v, haversine(lat1, lon1, lat2, lon2)) return g, names
def brute_perf_matchings(g: Graph) -> int: """Computes the number of perfect matchings in g using brute force.""" n = g.vertex_count() if n % 2 != 0: return 0 return len([ c for c in combinations(g.edges(), n // 2) if gh.is_perf_matching(g, c) ])
def is_hamcycle(g: Graph, tour: List[int]) -> bool: """Is the given tour a hamiltonian cycle in g?""" unique = len(set(tour)) == len(tour) - 1 all_visited = all([v in tour for v in g.vertices()]) is_cycle = tour[0] == tour[-1] legal_edges = all( [g.edge_exists(tour[i], tour[i + 1]) for i in range(len(tour) - 1)]) return unique and all_visited and is_cycle and legal_edges
def make_path(n: int) -> Graph: """Constructs a path with n vertices (n-1 edges)""" assert (n >= 0) g = Graph(n) for i in range(n - 1): g.add_edge(i, i + 1) return g
def brute_cheapest_hamcycle(g: Graph) -> float: """ Computes the weight of the cheapest hamiltonian cycle in g. Returns inf if no hamiltonian cycle exists. """ tours = [ p + (p[0], ) for p in permutations(g.vertices(), g.vertex_count()) ] ham_cycles = [t for t in tours if gh.is_hamcycle(g, t)] if len(ham_cycles) == 0: return math.inf return min([gh.tour_cost(g, t) for t in ham_cycles])
def make_random(n: int, p: float) -> Graph: """Constructs a random graph where each edge is sampled with probability p""" assert (n >= 0 and 0 <= p <= 1) g = Graph(n) for (u, v) in combinations(range(n), 2): if random() < p: g.add_edge(u, v) return g
def make_clique(n: int) -> Graph: """Constructs a clique with n vertices""" assert (n >= 0) g = Graph(n) if n < 2: return g for (u, v) in combinations(range(n), 2): g.add_edge(u, v) return g
def test_vc_approx(vc_approx: Callable[[Graph], Iterable[int]]) -> None: """Tests the implementation of a 2-approximation for vertex cover""" print(f"Testing: {vc_approx.__name__}...") g = Graph.from_file(graph_path) vc_size = 70 vc = set(vc_approx(g)) if not is_vc(g, vc): print("VC-Approx test failed: Not a vertex cover") elif (len(vc) / vc_size) > 2: print("VC-Approx test failed: |vc| > 2 * |optimal vc|") else: print("VC-Approx test passed!")
def make_cycle(n: int) -> Graph: """ Constructs of cycle with n vertices. A cycle with 0 vertices is the empty graph. A cycle with 1 vertex is a loop. A cycle with 2 vertices is not permitted because of parallel edges. """ assert (n >= 0 and n != 2) if n == 0: return Graph() g = make_path(n) g.add_edge(n - 1, 0) return g
def mst(graph: Graph) -> Graph: """ Computes a minimum spanning tree of the given graph using Prim's algorithm. The MST is returned as a graph g. """ start_vtx = next(graph.vertices()) tree = Graph(graph.vertex_count()) in_tree = {v:v==start_vtx for v in graph.vertices()} edges = graph.incident(start_vtx) heapq.heapify(edges) while len(edges) > 0: u,v,w = heapq.heappop(edges) if in_tree[u] and in_tree[v]: continue new_vtx = u if in_tree[v] else v tree.add_edge(u,v,w) in_tree[new_vtx] = True for e in graph.incident(new_vtx): heapq.heappush(edges, e) return tree
def make_grid(rows: int, cols: int) -> Graph: """Constructs a grid graph with the given rows and columns""" assert (rows >= 0 <= cols) g = Graph(rows * cols) for r in range(rows): for c in range(cols): v = r * cols + c if c > 0: g.add_edge(v, v - 1) if r > 0: g.add_edge(v, v - cols) return g
def brute_isets(g: Graph) -> List[Tuple[int]]: """Uses brute force to construct all independent sets of g""" return [s for s in subsets(g.vertices()) if gh.is_iset(g, s)]
def brute_vc(g: Graph) -> int: """Computes the size of the minimum vertex cover of g using brute force""" return min([len(sub) for sub in subsets(g.vertices()) if gh.is_vc(g, sub)])
def brute_hamcycles(g: Graph): """Constructs all hamiltonian cycles of g using brute force""" tours = (p + (p[0], ) for p in permutations(g.vertices(), g.vertex_count())) return (t for t in tours if gh.is_hamcycle(g, t))
def tour_cost(g: Graph, tour: List[int]) -> float: """ Returns the total cost of the given tour in g. """ return sum( [g.edge_weight(tour[i], tour[i + 1]) for i in range(len(tour) - 1)])
def is_vc(g: Graph, vc: Iterable[int]) -> bool: """Is vc a vertex cover of the graph g?""" return all([e.u in vc or e.v in vc for e in g.edges()])
import os from advalg.graph import Graph from typing import Callable dirname = os.path.dirname(__file__) graph_path = os.path.join(dirname, 'data/vc_graph_small.txt') tests_fpt = [ ("10-path", make_path(10), 5), ("15-path", make_path(15), 7), ("10-cycle", make_cycle(10), 5), ("15-cycle", make_cycle(15), 8), ("24-cycle", make_cycle(24), 12), ("10-clique", make_clique(10), 9), ("15-clique", make_clique(15), 14), ("vc_graph_small", Graph.from_file(graph_path), 12) ] tests_sat = [ ("10-path", make_path(10), 5), ("15-path", make_path(15), 7), ("10-cycle", make_cycle(10), 5), ("15-cycle", make_cycle(15), 8), ("10-clique", make_clique(10), 9), ] def run_tests(vc, cases): for name, g, size in cases: n = g.vertex_count() res = next(k for k in range(n+1) if vc(g, k)) if res != size:
def is_iset(g: Graph, s: Iterable[int]) -> bool: """Is s an independent set of graph g?""" return not any([e.u in s and e.v in s for e in g.edges()])