def add_implicit_iclass(g, each_type_separate=True):
    """
    Adds an implicit graph otherwise a copy of the original graph is returned.
    
    :param g: a graph with a non-empty 'entities' list
    :return: a list of suggested graphs
    >>> add_implicit_iclass({'edgeSet': [], 'entities': []}) ==\
    [{'entities': [], 'edgeSet': [{'type': 'iclass', 'kbID': "P106v"}]}, {'entities': [], 'edgeSet': [{'type': 'iclass', 'kbID': "P31v"}]}]
    True
    >>> add_implicit_iclass({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'NNP'}]})[0] ==\
    {'entities': [{'tokens': ['Portman'], 'linkings': [('Q37876', 'Natalie Portman')], 'type': 'NNP'}], 'edgeSet': [{'type': 'iclass', 'kbID': 'P106v'}]}
    True
    >>> add_implicit_iclass({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "city")], 'tokens':["city"], 'type':'NN'}]})[0] ==\
    {'entities': [{'tokens': ['city'], 'linkings': [('Q37876', 'city')], 'type': 'NN'}], 'edgeSet': [{'type': 'iclass', 'kbID': "P106v"}]}
    True
    >>> add_implicit_iclass({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "city")], 'tokens':["city"], 'type':'NN'}]}, each_type_separate=False)[0] ==\
    {'entities': [{'tokens': ['city'], 'linkings': [('Q37876', 'city')], 'type': 'NN'}], 'edgeSet': [{'type': 'iclass'}]}
    True
    """
    if any(edge.get("type") == 'iclass' for edge in g.get('edgeSet', [])):
        return [g]
    if each_type_separate:
        new_graphs = []
        for rel in CLASS_RELATIONS:
            new_g = graph.copy_graph(g, with_iclass=True)
            new_g['edgeSet'].append({'type': "iclass", 'kbID': rel})
            new_graphs.append(new_g)
        return new_graphs
    else:
        new_g = graph.copy_graph(g, with_iclass=True)
        new_g['edgeSet'].append({'type': "iclass"})
        return [new_g]
Example #2
0
def find_groundings(g):
    """
    Retrieve possible groundings for a given graph.

    :param g: the graph to ground
    :return: a list of graph groundings.
    >>> len(find_groundings({'edgeSet': [{'right': ['Percy', 'Jackson'], 'rightkbID': 'Q3899725', 'kbID': 'P674v', 'type': 'direct'}, {'rightkbID': 'Q571', 'right': ['book']}]}))
    1
    >>> len(find_groundings({'edgeSet': [{'right': ['Percy', 'Jackson'], 'rightkbID': 'Q3899725'}, {'rightkbID': 'Q571', 'right': ['book']}]}))
    1
    >>> find_groundings({'edgeSet': [{'canonical_right': 'country', 'right': ['country'], 'type': 'class', 'rightkbID':'Q6256'}]})
    [{'r0c': 'P31v'}]
    >>> find_groundings({'edgeSet': [{'canonical_right': 'country', 'right': ['country'], 'type': 'class', 'rightkbID':'Q6256'}, {'right': ['grand', 'bahama', 'island'], 'rightkbID': 'Q866345', 'canonical_right': 'Grand Bahama'}]}) ==\
    [{'r0c': 'P31v', 'r1r': 'P17v'}]
    True
    >>> find_groundings({'edgeSet': [{'rightkbID': 'Q5620660'}], "tokens": ['Who', 'played', 'Gus', 'Fring', 'in', 'Breaking', 'Bad', '?']}) ==\
    [{'r0d': 'P674v'}, {'r0r': 'P106v'}, {'r0r': 'P1441v'}, {'r0r': 'P170v'}, {'r0r': 'P21v'}, {'r0r': 'P31v'}, {'r0v': 'P161v'}, {'r0v': 'P453q'}]
    True
    >>> find_groundings({'edgeSet': [{'canonical_right': 'human', 'kbID': 'P31v', 'rightkbID': 'Q5', 'type': 'class'}, \
    {'rightkbID': 'Q5620660'}], 'tokens': ['Who', 'played', 'Gus', 'Fring', 'in', 'Breaking', 'Bad', '?']}) ==\
    [{'r1r': 'P170v'}, {'r1v': 'P161v'}, {'r1v': 'P453q'}]
    True
    >>> find_groundings({'edgeSet': [{'type': 'v-structure', 'right': ['Gus', 'Fring'], 'rightkbID': 'Q5620660', 'propertyName': 'cast member', 'canonical_right': 'Gus Fring', 'kbID': 'P161v'}, {'right': ['Breaking', 'Bad'], 'rightkbID': 'Q1079', 'canonical_right': 'Breaking Bad'}]})
    [{'r1r': 'P161v'}]
    """
    query_results = []
    num_edges_to_ground = sum(
        1 for e in g.get('edgeSet', [])
        if 'type' not in e or (e.get("type") != "class" and 'kbID' not in e))
    num_class_edges_to_ground = sum(
        1 for e in g.get('edgeSet', [])
        if e.get("type") == "class" and 'kbID' not in e)
    if num_edges_to_ground == 0 and num_class_edges_to_ground == 0:
        return [{}]
    if num_edges_to_ground <= 1 and not any(
            'hopUp' in e or 'hopDown' in e for e in g.get('edgeSet', [])
            if not ('type' in e and 'kbID' in e)):
        query_results += wdaccess.query_graph_groundings(g)
    else:
        edges_without_type = sum(1 for e in g.get('edgeSet', [])
                                 if 'type' not in e)
        edge_type_combinations = list(
            itertools.product(*[['direct', 'reverse']] * edges_without_type))
        for type_combination in edge_type_combinations:
            t = graph.copy_graph(g)
            for i, edge in enumerate(
                [e for e in t.get('edgeSet', []) if 'type' not in e]):
                edge['type'] = type_combination[i]
            query_results += wdaccess.query_graph_groundings(t)
    if generation_p['v.structure'] and num_edges_to_ground == 1 and any(
            w in set(g.get('tokens', [])) for w in v_structure_markers):
        t = graph.copy_graph(g)
        edge = [
            e for e in t.get('edgeSet', [])
            if not ('type' in e and 'kbID' in e)
        ][0]
        edge['type'] = 'v-structure'
        query_results += wdaccess.query_graph_groundings(t)
    return query_results
def add_temporal_relation(g):
    """
    Adds a temporal argmax relation in the graph, that is only the latest/earliest entity is returned as the answer.

    :param g: a graph with a non-empty edgeSet
    :return: a list of suggested graphs
    >>> add_temporal_relation({'edgeSet': [{'right':[2]}, {'right':[8]}], 'entities': [], 'tokens':['who', 'president']}) == \
     [{'edgeSet': [{'right':[2]}, {'right':[8]}, {'type':'time', 'argmax':'time'}], 'entities': [], 'tokens':['who', 'president']}]
    True
    >>> add_temporal_relation({'edgeSet': [{'right':[2]}, {'right':[8], 'argmin':'time'}], 'entities': []})
    []
    >>> add_temporal_relation({'edgeSet': [{'right':[2]}, {'type':'time'}], 'entities': []})
    []
    >>> add_temporal_relation({'edgeSet': [{'kbID': 'P161v','type': 'direct'}],'tokens': ['where','was','<e>','assassinated','?']})
    []
    """
    if len(g.get('edgeSet', [])) == 0 or graph.graph_has_temporal(g):
        return []
    new_graphs = []
    consider_types = set()
    if any(t in argmax_time_markers for t in g.get('tokens', [])):
        consider_types.add('argmax')
    if any(t in argmin_time_markers for t in g.get('tokens', [])):
        consider_types.add('argmin')
    for t in consider_types.intersection(ARG_TYPES):
        new_g = graph.copy_graph(g)
        new_edge = {'type': 'time', t: 'time', 'kbID': "P585v"}
        new_g['edgeSet'].append(new_edge)
        new_graphs.append(new_g)
    return new_graphs
def add_entity_and_relation(g):
    """
    Takes a graph with a non-empty list of free entities and adds a new relations with the one of the free named entities,
    thus removing it from the list.

    :param g: a graph with a non-empty 'entities' list
    :return: a list of suggested graphs
    >>> add_entity_and_relation({'edgeSet': [], 'entities': []})
    []
    >>> add_entity_and_relation({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "Natalie Portman"), ("Q872356", "Portman")], 'tokens':["Portman"], 'type':'PERSON'}]}) ==\
    [{'edgeSet': [{'right': ['Portman'], 'rightkbID': 'Q37876', 'canonical_right': 'Natalie Portman'}], 'entities': []}, {'edgeSet': [{'right': ['Portman'], 'rightkbID': 'Q872356', 'canonical_right': 'Portman'}], 'entities': []}]
    True
    >>> add_entity_and_relation({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "city")], 'tokens':["city"], 'type':'NN'}, {'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'PERSON'}]}) == \
    [{'entities': [{'linkings':[("Q37876", "city")], 'tokens':["city"], 'type':'NN'}], 'edgeSet': [{'rightkbID': 'Q37876', 'canonical_right': 'Natalie Portman', 'right': ['Portman']}]}]
    True
    >>> add_entity_and_relation({'edgeSet': [], 'entities': [{"linkings": [(None, ["2012"])] ,"tokens": ['2012'], "type": 'CD'}, {'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'PERSON'}]}) == \
    [{'entities': [{'linkings': [(None, ['2012'])], 'type': 'CD', 'tokens': ['2012']}], 'edgeSet': [{'rightkbID': 'Q37876', 'canonical_right': 'Natalie Portman', 'right': ['Portman']}]}]
    True
    >>> add_entity_and_relation({'edgeSet': [], 'entities': [{"linkings": [(None, ["2012"])] ,"tokens": ['2012'], "type": 'CD'}]})
    []
    >>> add_entity_and_relation({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "city")], 'tokens':["city"], 'type':'NN'}]}) ==\
    [{'entities': [], 'edgeSet': [{'right': ['city'], 'rightkbID': 'Q37876', 'canonical_right': 'city'}]}]
    True
    >>> add_entity_and_relation({'edgeSet': [{'rightkbID': 'Q37876', 'canonical_right': 'Natalie Portman', 'right': ['Portman']}], 'entities': [{'linkings':[("Q37876", "city")], 'tokens':["city"], 'type':'NN'}]})
    []
    >>> add_entity_and_relation({"tokens": ["Who", "played", "Gus", "Fring", "on", "Breaking", "Bad", "?"], "entities": [{"tokens": ["Breaking", "Bad"], "type": "NNP", "linkings": [["Q1079", "Breaking Bad"]]}], "edgeSet": [{"type": "v-structure", "rightkbID": "Q5620660", "right": ["Gus", "Fring"], "propertyName": "cast member", "canonical_right": "Gus Fring", "kbID": "P161v"}]})
    """
    if len(g.get('entities', [])) == 0:
        return []
    entities = copy.copy(g.get('entities', []))
    new_graphs = []
    # Consider only named entities
    entities_to_consider = [
        e for e in entities if e.get("type") not in {'CD', 'NN'}
    ]
    # If there are no named entities, add one relation with common entity
    if len(entities_to_consider) == 0 and len(g.get('edgeSet', [])) == 0:
        entities_to_consider = [e for e in entities if e.get("type") in {'NN'}]
        skipped = [e for e in entities if e.get("type") in {'CD'}]
    else:
        skipped = [e for e in entities if e.get("type") in {'CD', 'NN'}]
    while entities_to_consider:
        entity = entities_to_consider.pop(0)
        if len(entity.get("linkings", [])) > 0:
            linkings = entity['linkings']
            for kbID, label in linkings:
                new_g = graph.copy_graph(g)
                new_g['entities'] = skipped[:] + entities_to_consider[:]
                new_g['edgeSet'].append({
                    'right': entity.get("tokens", []),
                    'rightkbID': kbID,
                    'canonical_right': label
                })
                new_graphs.append(new_g)
    return new_graphs
 def encode_graphs(self, instance):
     _, graph_set = instance
     graphs_encoded = deque()
     for g in graph_set:
         g = graph.graph_format_update(g)
         if not self._p.get("encode.iclass", False):
             g = graph.copy_graph(g, with_iclass=False)
         edges_encoded = []
         for edge in g.get('edgeSet', []):
             property_label_tokens = self._get_edge_str_representation(edge).split()
             edge_encoded = self._encode_tokens(property_label_tokens)
             edges_encoded.append(edge_encoded)
         graphs_encoded.append(edges_encoded)
     return graphs_encoded
Example #6
0
def apply_topics(g, topics):
    """
    Apply topics to the implicit edges or create new if needed.
    
    :param g: input graph
    :param topics: topics dictionary
    :return: graphs with implicit class edges
    >>> apply_topics({'edgeSet': [{'kbID': 'P1066v', 'rightkbID': 'Q37571', 'type': 'reverse'}]}, {'P31v': ['human'], 'P106v': ['lawyer', 'politician', 'political writer']}) ==\
    {'edgeSet': [{'kbID': 'P1066v', 'rightkbID': 'Q37571', 'type': 'reverse'}, {'kbID': 'P31v', 'type': 'iclass', 'canonical_right': ['human']}, {'kbID': 'P106v', 'type': 'iclass', 'canonical_right': ['lawyer', 'politician', 'political writer']}], 'entities': []}
    True
    """
    grounded = graph.copy_graph(g)
    for kbID, values in topics.items():
        new_edge = {'type': 'iclass'}
        new_edge['kbID'] = kbID
        new_edge['canonical_right'] = values
        grounded['edgeSet'].append(new_edge)
    return grounded
Example #7
0
def apply_grounding(g, grounding):
    """
    Given a grounding obtained from WikiData apply it to the graph.
    Note: that the variable names returned by WikiData are important as they encode some grounding features.

    :param g: a single ungrounded graph
    :param grounding: a dictionary representing the grounding of relations and variables
    :return: a grounded graph
    >>> apply_grounding({'edgeSet':[{}]}, {'r0d':'P31v'}) == {'edgeSet': [{'type': 'direct', 'kbID': 'P31v', }], 'entities': []}
    True
    >>> apply_grounding({'edgeSet':[{}]}, {'r0v':'P31v'}) == {'edgeSet': [{'type': 'v-structure', 'kbID': 'P31v'}], 'entities': []}
    True
    >>> apply_grounding({'edgeSet':[{"hopUp":None}]}, {'r0v':'P31v', 'hop0v':'P131v'}) == {'edgeSet': [{'type': 'v-structure', 'kbID': 'P31v', 'hopUp':'P131v'}], 'entities': []}
    True
    >>> apply_grounding({'edgeSet': [{'type': 'v-structure', 'kbID': 'P31v', 'hopUp':'P131v'}], 'tokens': []}, {}) == {'edgeSet': [{'type': 'v-structure', 'kbID': 'P31v', 'hopUp':'P131v'}], 'entities': [], 'tokens': []}
    True
    >>> apply_grounding({'edgeSet':[{}, {}]}, {'r1d':'P39v', 'r0v':'P31v', 'e20': 'Q18'}) == {'edgeSet': [{'type': 'v-structure', 'kbID': 'P31v', 'rightkbID': 'Q18'}, {'type': 'direct', 'kbID': 'P39v'}], 'entities': []}
    True
    >>> apply_grounding({'edgeSet':[]}, {}) == {'entities': [], 'edgeSet': []}
    True
    """
    grounded = graph.copy_graph(g)
    for i, edge in enumerate(grounded.get('edgeSet', [])):
        if "e2" + str(i) in grounding:
            edge['rightkbID'] = grounding["e2" + str(i)]
        if "hop{}v".format(i) in grounding:
            if 'hopUp' in edge:
                edge['hopUp'] = grounding["hop{}v".format(i)]
            else:
                edge['hopDown'] = grounding["hop{}v".format(i)]
        if "r{}d".format(i) in grounding:
            edge['kbID'] = grounding["r{}d".format(i)]
            edge['type'] = 'direct'
        elif "r{}r".format(i) in grounding:
            edge['kbID'] = grounding["r{}r".format(i)]
            edge['type'] = 'reverse'
        elif "r{}v".format(i) in grounding:
            edge['kbID'] = grounding["r{}v".format(i)]
            edge['type'] = 'v-structure'
        elif "r{}c".format(i) in grounding:
            edge['kbID'] = grounding["r{}c".format(i)]
            edge['type'] = 'class'

    return grounded
Example #8
0
def manual_groundings(g):
    """
    
    :param g: 
    :return:    
    >>> manual_groundings({'edgeSet': [{'canonical_right': 'Thailand','rightkbID': 'Q869'}], 'tokens': ['what', 'religion', 'in', 'thailand', '?']}) ==\
    [{'tokens': ['what', 'religion', 'in', 'thailand', '?'], 'edgeSet': [{'rightkbID': 'Q869', 'type': 'reverse', 'kbID': 'P140v', 'hopUp': 'P27v', 'canonical_right': 'Thailand'}], 'entities': []}]
    True
    """
    grounded_graphs = []
    if any(w in set(g.get('tokens', [])) for w in religion_markers):
        t = graph.copy_graph(g)
        edge = [
            e for e in t.get('edgeSet', []) if 'type' not in e or (
                e.get("type") != "class" and 'kbID' not in e)
        ]
        if len(edge) == 1:
            edge = edge[0]
            edge['type'] = 'reverse'
            edge['hopUp'] = 'P27v'
            edge['kbID'] = 'P140v'
            grounded_graphs.append(t)
    return grounded_graphs
def last_relation_numeric(g):
    """
    Adds a numeric restriction to the last relation in the graph.

    :param g: a graph with a non-empty edgeSet
    :return: a list of suggested graphs
    >>> last_relation_numeric({'edgeSet': [{'right':[2], 'type':'direct'}, {'right':[8], 'type':'direct'}], 'entities': [{'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'PERSON'}, {'linkings': [(None, ['2012'])], 'type': 'CD', 'tokens': ['2012']}]}) == \
    [{'edgeSet': [{'right':[2], 'type':'direct'}, {'right':[8], 'type':'direct', 'num': ['2012']}], 'entities': [{'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'PERSON'}]}]
    True
    >>> last_relation_numeric({'edgeSet': [{'right':[2]}, {'right':[8], 'argmin':'time'}], 'entities': [{'linkings': [(None, ['2012'])], 'type': 'CD', 'tokens': ['2012']}]})
    []
    >>> last_relation_numeric({'edgeSet': [{'right':[2]}, {'right':[8], 'num':'2009'}], 'entities': [{'linkings': [(None, ['2012'])], 'type': 'CD', 'tokens': ['2012']}]})
    []
    >>> last_relation_numeric({'edgeSet': [{'right':[2]}], 'entities': []})
    []
    >>> last_relation_numeric({'edgeSet': [{'right':[2]}], 'entities': [{'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'PERSON'}]})
    []
    """
    if len(g.get('edgeSet', [])) == 0 or graph.graph_has_temporal(g):
        return []
    if len(g.get('entities', [])) == 0 or not any(
            e.get("type") == 'CD' for e in g['entities'] if len(e) > 1):
        return []
    entities = copy.copy(g.get('entities', []))
    cd_entities = [e.get("tokens") for e in entities if e.get("type") == 'CD']
    if len(cd_entities) == 0:
        return []
    cd_entity = cd_entities[0]
    entities = [e for e in entities if e.get("tokens") != cd_entity]
    new_g = graph.copy_graph(g)
    new_g['entities'] = entities
    edge_to_modify = graph.get_graph_last_edge(
        new_g, filter_out_types={'iclass', 'v-structure', 'class'})
    if len(edge_to_modify) > 0:
        edge_to_modify['num'] = cd_entity
        return [new_g]
    return []
def last_relation_hop(g):
    """
    Takes a graph with an existing relation and an intermediate variable by performing a hop-up for the second entity.

    :param g: a graph with an non-empty edgeSet
    :return: a list of suggested graphs
    >>> last_relation_hop({'edgeSet': [], 'entities': [[4, 5, 6]]})
    []
    >>> last_relation_hop({'edgeSet': [{'right':[4,5,6]}], 'entities': []}) ==\
     [{'edgeSet': [{'right':[4,5,6], 'hopUp': None}], 'entities': []}, {'edgeSet': [{'right':[4,5,6], 'hopDown': None}], 'entities': []}]
    True
    >>> last_relation_hop({'edgeSet': [{'right':[4,5,6], 'hopUp': None}], 'entities': []})
    []
    >>> last_relation_hop({'edgeSet': [{'right':[4,5,6], 'kbID': "P31v", "type": "direct"}], 'entities': []})
    []
    >>> last_relation_hop({'edgeSet': [{'right':["Bahama"], "rightkbID":"Q6754", 'type':'direct'}], 'entities': []}) ==\
     [{'edgeSet': [{'right':["Bahama"], "rightkbID":"Q6754", 'hopUp': None, 'type':'direct'}], 'entities': []}, {'edgeSet': [{'right':["Bahama"], "rightkbID":"Q6754", 'hopDown': None, 'type':'direct'}], 'entities': []}]
    True
    >>> last_relation_hop({'edgeSet': [{'right':[4,5,6], 'argmax':'time'}], 'entities': []})
    []
    >>> last_relation_hop({'edgeSet': [{'right':[4,5,6], 'num':['2012']}], 'entities': []})
    []
    """
    if len(g.get('edgeSet', [])) == 0 or any(
            hop in g['edgeSet'][-1]
            for hop in {'hopUp', 'hopDown'}) or graph.graph_has_temporal(g):
        return []
    new_graphs = []
    for hop in HOP_TYPES:
        new_g = graph.copy_graph(g)
        edge_to_modify = graph.get_graph_last_edge(
            new_g, filter_out_types={'iclass', 'class', 'v-structure', 'time'})
        if len(edge_to_modify) > 0 and 'kbID' not in edge_to_modify:
            edge_to_modify[hop] = None
            new_graphs.append(new_g)
    return new_graphs
def add_class(g, keep_skipped=False):
    """
    Takes a graph with a non-empty list of free entities and adds a new class relation with the one of the free entities,
    thus removing it from the list.

    :param g: a graph with a non-empty 'entities' list
    :return: a list of suggested graphs
    >>> add_class({'edgeSet': [], 'entities': []})
    []
    >>> add_class({'edgeSet': [], 'entities': [], "tokens": "where is London ?".split()}) ==\
    [{'edgeSet': [{'type': 'class', 'rightkbID': 'Q618123', 'kbID': "P31v", 'canonical_right': 'geographical object'}], 'entities': [], 'tokens': ['where', 'is', 'London', '?']}]
    True
    >>> add_class({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'NNP'}]})
    []
    >>> add_class({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "album")], 'tokens':["album"], 'type':'NN'}, {'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'NNP'}]}) == \
    [{'entities': [{'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'NNP'}], 'edgeSet': [{'canonical_right': 'album', 'type': 'class', 'rightkbID': 'Q37876', 'right': ['album']}]}]
    True
    >>> add_class({'edgeSet': [], 'entities': [{"linkings": [(None, ["2012"])] ,"tokens": ['2012'], "type": 'CD'}, {'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'PERSON'}, {'linkings':[("Q37876", "city")], 'tokens':["city"], 'type':'NN'}]}, keep_skipped=True) == \
    [{'entities': [{'type': 'CD', 'tokens': ['2012'], 'linkings': [(None, ['2012'])]}, {'type': 'PERSON', 'tokens': ['Portman'], 'linkings': [('Q37876', 'Natalie Portman')]}], 'edgeSet': [{'canonical_right': 'city', 'type': 'class', 'rightkbID': 'Q37876', 'right': ['city']}]}]
    True
    >>> add_class({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "city")], 'tokens':["city"], 'type':'NN'}]})
    []
    >>> len(add_class({'edgeSet': [], 'entities': [{'linkings':[("Q37876", "album"), ("Q390076", "album")], 'tokens':["album"], 'type':'NN'}, {'linkings':[("Q37876", "Natalie Portman")], 'tokens':["Portman"], 'type':'NNP'}]}))
    1
    >>> add_class({'edgeSet': [], 'entities': [{'linkings': [('Q866345', 'Grand Bahama')], 'type': 'NNP', 'tokens': ['grand', 'bahama', 'island']}, {'linkings': [('Q23442', 'island')], 'type': 'NNP', 'tokens': ['grand', 'bahama', 'island']}, {'linkings': [('Q6256', 'country')], 'type': 'NN', 'tokens': ['country']}], 'tokens': ['what', 'country', 'is', 'the', 'grand', 'bahama', 'island', 'in', '?']}, keep_skipped=True) ==\
    [{'edgeSet': [{'right': ['country'], 'rightkbID': 'Q6256', 'canonical_right': 'country', 'type': 'class'}], 'entities': [{'linkings': [('Q866345', 'Grand Bahama')], 'type': 'NNP', 'tokens': ['grand', 'bahama', 'island']}, {'linkings': [('Q23442', 'island')], 'type': 'NNP', 'tokens': ['grand', 'bahama', 'island']}], 'tokens': ['what', 'country', 'is', 'the', 'grand', 'bahama', 'island', 'in', '?']}]
    True
    """
    # If there is already another class defined, don't add anything
    if any(edge.get("type") == 'class' for edge in g.get('edgeSet', [])):
        return []
    # If the type is temporal, don't add classes
    if graph.get_question_type(g) == "temporal":
        return []
    if graph.get_question_type(g) == "person":
        new_g = graph.copy_graph(g)
        new_g['edgeSet'].append({
            'rightkbID': "Q5",
            'kbID': "P31v",
            'canonical_right': "human",
            'type': "class"
        })
        return [new_g]
    if graph.get_question_type(g) == "location":
        new_g = graph.copy_graph(g)
        new_g['edgeSet'].append({
            'rightkbID': "Q618123",
            'kbID': "P31v",
            'canonical_right': "geographical object",
            'type': "class"
        })
        return [new_g]
    # If there are no common entities, don't add anything
    if len(g.get('entities', [])) == 0 or not any(
            e.get("type") == 'NN' for e in g['entities'] if len(e) > 1):
        return []
    # If there is only one entity left and it is a common entity and there are no other edges, don't add anything
    if len(g.get('entities', [])) == 1 and all(
            e.get("type") == 'NN'
            for e in g.get('entities', [])) and len(g.get('edgeSet', [])) == 0:
        return []
    entities = copy.copy(g.get('entities', []))
    skipped = []
    new_graphs = []
    while entities:
        entity = entities.pop(0)
        if entity.get("type") == 'NN':
            if len(entity.get("linkings", [])) > 0:
                linkings = entity['linkings']
                if len(linkings) > 0:
                    kbID, label = linkings[0]
                    new_g = graph.copy_graph(g)
                    new_g['entities'] = (skipped[:]
                                         if keep_skipped else []) + entities[:]
                    new_g['edgeSet'].append({
                        'right': entity.get("tokens", []),
                        'rightkbID': kbID,
                        'canonical_right': label,
                        'type': "class"
                    })
                    new_graphs.append(new_g)
        else:
            skipped.append(entity)
    return new_graphs