I have this code.  Constructor  WordNet should throw a ValueError if the input is an invalid graph

class Digraph:
    def __init__(self):
        self.edges = {}

    def add_vertex(self, v):
        if v not in self.edges:
            self.edges[v] = set()

    def add_edge(self, v, w):
        self.add_vertex(v)
        self.add_vertex(w)
        self.edges[v].add(w)

    def adj(self, v):
        return self.edges.get(v, set())

    def vertices(self):
        return self.edges.keys()


def parse_graph(file_path: str) -> Digraph:
    graph = Digraph()

    with open(file_path, 'r') as f:
        # num_v = int(f.readline().strip())
        # _ = f.readline().strip()

        for line in f:
            if line.strip():
                edges = list(map(int, line.strip().split()))
                for i in range(1, len(edges)):
                    graph.add_edge(edges[0], edges[i])

    return graph


class SAP:
    def __init__(self, graph):
        self.graph = graph

    def bfs(self, sources_v, sources_w):
        dist_v = {v: float('inf') for v in self.graph.vertices()}
        dist_w = {v: float('inf') for v in self.graph.vertices()}
        queue = []

        for s in sources_v:
            if s in dist_v:
                dist_v[s] = 0
                queue.append((s, 'v'))
        for s in sources_w:
            if s in dist_w:
                dist_w[s] = 0
                queue.append((s, 'w'))

        shortest = float('inf')
        ancestor = -1

        while queue:
            v, source = queue.pop(0)
            current_dist = dist_v if source == 'v' else dist_w
            opposite_dist = dist_w if source == 'v' else dist_v

            if opposite_dist[v] != float('inf'):
                total_dist = current_dist[v] + opposite_dist[v]
                if total_dist < shortest:
                    shortest = total_dist
                    ancestor = v

            for w in self.graph.adj(v):
                if current_dist[w] == float('inf'):
                    current_dist[w] = current_dist[v] + 1
                    queue.append((w, source))

        return shortest, ancestor

    def length(self, v, w):
        shortest, _ = self.bfs([v], [w])
        return -1 if shortest == float('inf') else shortest

    def ancestor(self, v, w):
        _, ancestor = self.bfs([v], [w])
        return ancestor

    def length_in_list(self, v_list, w_list):
        shortest, _ = self.bfs(v_list, w_list)
        return -1 if shortest == float('inf') else shortest

    def ancestor_in_list(self, v_list, w_list):
        _, ancestor = self.bfs(v_list, w_list)
        return ancestor


class WordNet:
    def __init__(self, synsets_file, hypernyms_file):
        self.synsets = {}
        self.nouns_map = {}
        self.graph = Digraph()

        with open(synsets_file, 'r') as file:
            for line in file:
                fields = line.strip().split(',')
                synset_id = int(fields[0])
                synset = fields[1]
                # description = fields[2]

                self.synsets[synset_id] = synset
                self.graph.add_vertex(synset_id)

                for noun in synset.split():
                    if noun not in self.nouns_map:
                        self.nouns_map[noun] = set()
                    self.nouns_map[noun].add(synset_id)

        with open(hypernyms_file, 'r') as file:
            for line in file:
                fields = line.strip().split(',')
                synset_id = int(fields[0])
                hypernyms = map(int, fields[1:])
                for hypernym_id in hypernyms:
                    self.graph.add_edge(synset_id, hypernym_id)

            # Check if the graph is a valid DAG and throws ValueError for invalid graph
            if not self.is_dag():
                raise ValueError("The graph is not a DAG")

            # Check if the graph has a single root
            if not self.has_single_root():
                raise ValueError("The graph does not have a single root")

            self.sap = SAP(self.graph)

    def is_dag(self):
            visited = set()
            rec_stack = set()

            def dfs(v):
                visited.add(v)
                rec_stack.add(v)
                for neighbor in self.graph.adj(v):
                    if neighbor not in visited:
                        if dfs(neighbor):
                            return True
                    elif neighbor in rec_stack:
                        return True
                rec_stack.remove(v)
                return False

            for vertex in self.graph.vertices():
                if vertex not in visited:
                    if dfs(vertex):
                        return False

            return True

    def has_single_root(self):
            reversed_graph = self.graph.reverse()
            roots = [v for v in self.graph.vertices() if not reversed_graph.adj(v)]
            return len(roots) == 1

    def nouns(self):
        return list(self.nouns_map.keys())

    def is_noun(self, word):
        return word in self.nouns_map

    def distance(self, noun_a, noun_b):
        if noun_a not in self.nouns_map or noun_b not in self.nouns_map:
            raise ValueError("One or both nouns are not in WordNet")

        synsets_a = self.nouns_map[noun_a]
        synsets_b = self.nouns_map[noun_b]

        return self.sap.length_in_list(list(synsets_a), list(synsets_b))

    def sap_f(self, noun_a, noun_b):
        if noun_a not in self.nouns_map or noun_b not in self.nouns_map:
            raise ValueError("One or both nouns are not in WordNet")

        synsets_a = self.nouns_map[noun_a]
        synsets_b = self.nouns_map[noun_b]

        ancestor_id = self.sap.ancestor_in_list(list(synsets_a), list(synsets_b))
        return self.synsets[ancestor_id] if ancestor_id != -1 else None


class Outcast:
    def __init__(self, wordnet):
        self.wordnet = wordnet

    def outcast(self, nouns):
        max_distance = -1
        outcast = None

        for noun in nouns:
            if not self.wordnet.is_noun(noun):
                raise ValueError(f"{noun} is not in WordNet")

            distance_sum = sum(self.wordnet.distance(noun, other) for other in nouns if other != noun)

            if distance_sum > max_distance:
                max_distance = distance_sum
                outcast = noun

        return outcast


def main():
    wordnet = WordNet("synsets.txt", "hypernyms.txt")
    outcast = Outcast(wordnet)
    nouns = ["hood", "1530s", "neighborhood", "decongestant"]
    print(f"Outcast: {outcast.outcast(nouns)}")

    print(f"Nouns: {wordnet.nouns}")

    check = wordnet.is_noun("23531542352352")
    print(f"Is noun: {check}")

    similarity = wordnet.distance("car", "vehicle")
    print(f"Similarity: {similarity}")

    common_ancestor = wordnet.sap_f("dog", "wolf")
    print(f"Common ancestor: {common_ancestor}")

    graph = parse_graph("digraph1.txt")
    sap = SAP(graph)
    length = sap.length(2, 3)
    ancestor = sap.ancestor(2, 3)
    print(f"Shortest path: {length} and ancestor: {ancestor}")


main()