def check_homomorphism(source, target, dictionary, total=True): """Check if the homomorphism is valid. Valid homomorphism preserves edges, and attributes if requires. """ # check if there is mapping for all the nodes of source graph if total: check_totality(source.nodes(), dictionary) if not set(dictionary.values()).issubset(target.nodes()): raise InvalidHomomorphism( "Some of the image nodes in mapping %s do not " "exist in target graph (target graph nodes %s) " "namely %s" % (dictionary.values(), target.nodes(), set(dictionary.values()) - set(target.nodes()))) # check connectivity for s, t in source.edges(): try: if (s in dictionary.keys() and t in dictionary.keys() and not (dictionary[s], dictionary[t]) in target.edges()): if not target.is_directed(): if not (dictionary[t], dictionary[s]) in target.edges(): raise InvalidHomomorphism( "Connectivity is not preserved!" " Was expecting an edge '%s' and '%s'" % (dictionary[t], dictionary[s])) else: raise InvalidHomomorphism( "Connectivity is not preserved!" " Was expecting an edge between '%s' and '%s'" % (dictionary[s], dictionary[t])) except KeyError: pass for s, t in dictionary.items(): # check sets of attributes of nodes (here homomorphism = set # inclusion) if not valid_attributes(source.node[s], target.node[t]): raise InvalidHomomorphism("Attributes of nodes source:'%s' %s and " "target:'%s' %s do not match!" % (s, source.node[s], t, target.node[t])) # check sets of attributes of edges (homomorphism = set inclusion) for s1, s2 in source.edges(): try: if (s1 in dictionary.keys() and s2 in dictionary.keys() and not valid_attributes( source.edge[s1][s2], target.edge[dictionary[s1]][dictionary[s2]])): raise InvalidHomomorphism( "Attributes of edges (%s)-(%s) (%s) and " "(%s)-(%s) (%s) do not match!" % (s1, s2, source.edge[s1][s2], dictionary[s1], dictionary[s2], target.edge[dictionary[s1]][dictionary[s2]])) except KeyError: pass return True
def check_homomorphism(source, target, dictionary, total=True): """Check if the homomorphism is valid. Valid homomorphism preserves edges, and attributes if requires. """ # check if there is mapping for all the nodes of source graph if total: check_totality(source.nodes(), dictionary) if not set(dictionary.values()).issubset(target.nodes()): raise InvalidHomomorphism("The image nodes {} do not exist ".format( set(dictionary.values()) - set(target.nodes())) + "in the target graph (existing nodes '{}') ". format(target.nodes()) + "in dictionary '{}'".format(dictionary)) # check connectivity for s, t in source.edges(): try: if (s in dictionary.keys() and t in dictionary.keys() and not (dictionary[s], dictionary[t]) in target.edges()): raise InvalidHomomorphism( "Connectivity is not preserved!" " Was expecting an edge between '{}' and '{}'".format( dictionary[s], dictionary[t])) except KeyError: pass for s, t in dictionary.items(): # check sets of attributes of nodes (here homomorphism = set # inclusion) if not valid_attributes(source.get_node(s), target.get_node(t)): raise InvalidHomomorphism( "Attributes of nodes source: '{}' {} and ".format( s, source.get_node(s)) + "target: '{}' {} do not match!".format(t, target.get_node(t))) # check sets of attributes of edges (homomorphism = set inclusion) for s1, s2 in source.edges(): try: if (s1 in dictionary.keys() and s2 in dictionary.keys() and not valid_attributes( source.get_edge(s1, s2), target.get_edge(dictionary[s1], dictionary[s2]))): raise InvalidHomomorphism( "Attributes of edges ({})-({}) ({}) and ".format( s1, s2, source.get_edge(s1, s2)) + "({})-({}) ({}) do not match!".format( dictionary[s1], dictionary[s2], target.get_edge(dictionary[s1], dictionary[s2]))) except KeyError: pass return True
def check_totality(elements, dictionary): """Check that a mapping is total.""" if set(elements) != set(dictionary.keys()): raise InvalidHomomorphism( "Invalid homomorphism: Mapping is not " "covering all the nodes of source graph! " "domain: {}, domain of definition: {}".format( set(elements), set(dictionary.keys())))
def check_consistency(tx, source, target): """Check if the adding of a homomorphism is consistent.""" query = ( "// match all typing pairs between '{}' and '{}'\n".format( source, target) + "MATCH (s:{})-[:typing]->(t:{})\n".format( source, target) + "WITH s, t\n" ) query += ( "// match all the predecessors of 's' and successors of 't'\n" "MATCH (pred)-[:typing*0..]->(s), (t)-[:typing*0..]->(suc) \n" "WHERE NOT pred = s AND NOT suc = t\n" + "WITH s, t, collect(DISTINCT pred) as pred_list, " + "collect(DISTINCT suc) as suc_list\n" ) query += ( "// select all the pairs 'pred' 'suc' with a path between\n" "UNWIND pred_list as pred\n" + "UNWIND suc_list as suc\n" + "OPTIONAL MATCH (pred)-[r:typing*]->(suc)\n" + "WHERE NONE(rel in r WHERE rel.tmp = 'True')\n" "WITH s, t, r, labels(pred)[1] as pred_label, labels(suc)[1] as suc_label\n" + "WHERE r IS NOT NULL\n" + "WITH DISTINCT s, t, pred_label, suc_label\n" ) query += ( "// return the pairs 's' 't' where there should be a typing edge\n" "OPTIONAL MATCH (s)-[new_typing:typing]->(t)\n" + "WHERE new_typing.tmp IS NOT NULL\n" + "WITH pred_label, suc_label, s.id as s_id, t.id as t_id, new_typing\n" + "WHERE new_typing IS NULL\n" + "RETURN pred_label, suc_label, s_id, t_id\n" ) result = tx.run(query) missing_typing = [] for record in result: missing_typing.append((record['pred_label'], record['suc_label'])) if len(missing_typing) != 0: raise InvalidHomomorphism( "Homomorphism does not commute with existing paths:\n" + ",\n".join(["\t- from {} to {}".format( s, t) for s, t in missing_typing]) + "." ) return True
def pullback_complement(a, b, d, a_b, b_d, inplace=False): """Find the final pullback complement from a->b->d. Makes changes to d inplace. """ check_homomorphism(a, b, a_b, total=True) check_homomorphism(b, d, b_d, total=True) if not is_monic(b_d): raise InvalidHomomorphism("Second homomorphism is not monic, " "cannot find final pullback complement!") if inplace is True: c = d else: c = NXGraph() c.add_nodes_from(d.nodes(data=True)) c.add_edges_from(d.edges(data=True)) a_c = dict() c_d = id_of(c.nodes()) # Remove/clone nodes for b_node in b.nodes(): a_keys = keys_by_value(a_b, b_node) # Remove nodes if len(a_keys) == 0: c.remove_node(b_d[b_node]) del c_d[b_d[b_node]] # Keep nodes elif len(a_keys) == 1: a_c[a_keys[0]] = b_d[b_node] # Clone nodes else: i = 1 for k in a_keys: if i == 1: a_c[k] = b_d[b_node] c_d[b_d[b_node]] = b_d[b_node] else: new_name = c.clone_node(b_d[b_node]) a_c[k] = new_name c_d[new_name] = b_d[b_node] i += 1 # Remove edges for (b_n1, b_n2) in b.edges(): a_keys_1 = keys_by_value(a_b, b_n1) a_keys_2 = keys_by_value(a_b, b_n2) if len(a_keys_1) > 0 and len(a_keys_2) > 0: for k1 in a_keys_1: for k2 in a_keys_2: if (k1, k2) not in a.edges() and\ (a_c[k1], a_c[k2]) in c.edges(): c.remove_edge(a_c[k1], a_c[k2]) # Remove node attrs for a_node in a.nodes(): attrs_to_remove = dict_sub(b.get_node(a_b[a_node]), a.get_node(a_node)) c.remove_node_attrs(a_c[a_node], attrs_to_remove) # removed_node_attrs[a_c[a_node]] = attrs_to_remove # Remove edge attrs for (n1, n2) in a.edges(): attrs_to_remove = dict_sub(b.get_edge(a_b[n1], a_b[n2]), a.get_edge(n1, n2)) c.remove_edge_attrs(a_c[n1], a_c[n2], attrs_to_remove) # removed_edge_attrs[(a_c[n1], a_c[n2])] = attrs_to_remove return (c, a_c, c_d)
def check_homomorphism(tx, domain, codomain, total=True): """Check if the homomorphism is valid. Parameters ---------- tx Variable of a cypher transaction domain : str Label of the graph at the domain of the homomorphism codmain : str Label of the graph at the codomain of the homomorphism Raises ------ InvalidHomomorphism This error is raised in the following cases: * a node at the domain does not have exactly 1 image in the codoamin * an edge at the domain does not have an image in the codomain * a property does not match between a node and its image * a property does not match between an edge and its image """ # Check if all the nodes of the domain have exactly 1 image query1 = ( "MATCH (n:{})\n".format(domain) + "OPTIONAL MATCH (n)-[:typing]->(m:{})\n".format(codomain) + "WITH n, collect(m) as images\n" + "WHERE size(images) <> 1\n" + "RETURN n.id as ids, size(images) as nb_of_img\n" ) result = tx.run(query1) nodes = [] for record in result: nodes.append((record['ids'], record['nb_of_img'])) if len(nodes) != 0: raise InvalidHomomorphism( "Wrong number of images!\n" + "\n".join( ["The node '{}' of the graph {} have {} image(s) in the graph {}.".format( n, domain, str(nb), codomain) for n, nb in nodes])) # Check if all the edges of the domain have an image query2 = ( "MATCH (n:{})-[:edge]->(m:{})\n".format( domain, domain) + "MATCH (n)-[:typing]->(x:{}), (y:{})<-[:typing]-(m)\n".format( codomain, codomain) + "OPTIONAL MATCH (x)-[r:edge]->(y)\n" + "WITH x.id as x_id, y.id as y_id, r\n" + "WHERE r IS NULL\n" + "WITH x_id, y_id, collect(r) as rs\n" + "RETURN x_id, y_id\n" ) result = tx.run(query2) xy_ids = [] for record in result: xy_ids.append((record['x_id'], record['y_id'])) if len(xy_ids) != 0: raise InvalidHomomorphism( "Edges are not preserved in the homomorphism from '{}' to '{}': ".format( domain, codomain) + "Was expecting edges {}".format( ", ".join( "'{}'->'{}'".format(x, y) for x, y in xy_ids)) ) # "CASE WHEN size(apoc.text.regexGroups(m_props, 'IntegerSet\\[(\\d+|minf)-(\\d+|inf)\\]') AS value" # Check if all the attributes of a node of the domain are in its image query3 = ( "MATCH (n:{})-[:typing]->(m:{})\n".format( domain, codomain) + "WITH properties(n) as n_props, properties(m) as m_props, " + "n.id as n_id, m.id as m_id\n" + "WITH REDUCE(invalid = 0, k in filter(k in keys(n_props) WHERE k <> 'id' AND k <> 'count') |\n" + "\tinvalid + CASE\n" + "\t\tWHEN NOT k IN keys(m_props) THEN 1\n" + "\t\tELSE REDUCE(invalid_values = 0, v in n_props[k] |\n" + "\t\t\tinvalid_values + CASE m_props[k]\n" + "\t\t\t\tWHEN ['IntegerSet'] THEN CASE WHEN toInt(v) IS NULL THEN 1 ELSE 0 END\n" + "\t\t\t\tWHEN ['StringSet'] THEN CASE WHEN toString(v) <> v THEN 1 ELSE 0 END\n" + "\t\t\t\tWHEN ['BooleanSet'] THEN CASE WHEN v=true OR v=false THEN 0 ELSE 1 END\n" + "\t\t\t\tELSE CASE WHEN NOT v IN m_props[k] THEN 1 ELSE 0 END END)\n" + "\t\tEND) AS invalid, n_id, m_id\n" + "WHERE invalid <> 0\n" + "RETURN n_id, m_id, invalid\n" ) result = tx.run(query3) invalid_typings = [] for record in result: invalid_typings.append((record['n_id'], record['m_id'])) if len(invalid_typings) != 0: raise InvalidHomomorphism( "Node attributes are not preserved in the homomorphism from '{}' to '{}': ".format( domain, codomain) + "\n".join(["Attributes of nodes source: '{}' ".format(n) + "and target: '{}' do not match!".format(m) for n, m in invalid_typings])) # Check if all the attributes of an edge of the domain are in its image query4 = ( "MATCH (n:{})-[rel_orig:edge]->(m:{})\n".format( domain, domain) + "MATCH (n)-[:typing]->(x:{}), (y:{})<-[:typing]-(m)\n".format( codomain, codomain) + "MATCH (x)-[rel_img:edge]->(y)\n" + "WITH n.id as n_id, m.id as m_id, x.id as x_id, y.id as y_id, " + "properties(rel_orig) as rel_orig_props, " + "properties(rel_img) as rel_img_props\n" + "WITH REDUCE(invalid = 0, k in keys(rel_orig_props) |\n" + "\tinvalid + CASE\n" + "\t\tWHEN NOT k IN keys(rel_img_props) THEN 1\n" + "\t\tELSE REDUCE(invalid_values = 0, v in rel_orig_props[k] |\n" + "\t\t\tinvalid_values + CASE rel_img_props[k]\n" + "\t\t\t\tWHEN ['IntegerSet'] THEN CASE WHEN toInt(v) IS NULL THEN 1 ELSE 0 END\n" + "\t\t\t\tWHEN ['StringSet'] THEN CASE WHEN toString(v) <> v THEN 1 ELSE 0 END\n" + "\t\t\t\tWHEN ['BooleanSet'] THEN CASE WHEN v=true OR v=false THEN 0 ELSE 1 END\n" + "\t\t\t\tELSE CASE WHEN NOT v IN rel_img_props[k] THEN 1 ELSE 0 END END)\n" + "\t\tEND) AS invalid, n_id, m_id, x_id, y_id\n" + "WHERE invalid <> 0\n" + "RETURN n_id, m_id, x_id, y_id, invalid\n" ) result = tx.run(query4) invalid_edges = [] for record in result: invalid_edges.append((record['n_id'], record['m_id'], record['x_id'], record['y_id'])) if len(invalid_edges) != 0: raise InvalidHomomorphism( "Edge attributes are not preserved!\n" + "\n".join(["Attributes of edges '{}'->'{}' ".format(n, m) + "and '{}'->'{}' do not match!".format(x, y) for n, m, x, y in invalid_edges]) ) return True