def generate_attributes_json(attrs): json_attrs = {} if attrs is not None: for k, value in attrs.items(): if isinstance(value, IntegerSet): if value.is_universal: json_attrs[k] = ["IntegerSet"] else: raise ReGraphError( "Non universal IntegerSet is not allowed as " "an attribute value (not implemented)") elif isinstance(value, RegexSet): if value.is_universal: json_attrs[k] = ["StringSet"] else: raise ReGraphError( "Non universal RegexSet is not allowed as " "an attribute value (not implemented)") elif isinstance(value, FiniteSet): json_attrs[k] = list(value.fset) elif isinstance(value, UniversalSet): json_attrs[k] = ["StringSet"] else: raise ValueError("Unknown type of attribute '{}': '{}'".format( k, type(value))) return json_attrs
def from_json(json_data): if not isinstance(json_data["pos_list"], list): raise ReGraphError("pos_list field should contain a list") for el in json_data["pos_list"]: if not test_number(el): raise ReGraphError("{} is not a number".format(el)) return convert(AtFinSet(set(sympify(json_data["pos_list"]))))
def from_json(json_data): if not isinstance(json_data["neg_list"], list): raise ReGraphError("neg_list field should contain a list") for el in json_data["neg_list"]: if test_number(el): raise ReGraphError("{} is a number".format(el)) return convert(AtNegStringSet(set(json_data["neg_list"])))
def set_attributes(var_name, attrs=None, update=False): """Generate a subquery to set the attributes for some variable.""" query = "" if not attrs: query += ( "SET {} = apoc.map.clean(properties({}), \n".format( var_name, var_name) + "\tfilter(x IN keys({}) WHERE NOT x IN [] AND x <> 'id'), [])". format(var_name)) for k, value in attrs.items(): if isinstance(value, IntegerSet): if value.is_universal: query += "\tSET {}.{} = ['IntegerSet']\n".format(var_name, k) else: raise ReGraphError( "Non universal IntegerSet is not allowed as " "an attribute value (not implemented)") elif isinstance(value, RegexSet): if value.is_universal: query += "\tSET {}.{} = ['StringSet']\n".format(var_name, k) else: raise ReGraphError("Non universal RegexSet is not allowed as " "an attribute value (not implemented)") elif isinstance(value, FiniteSet): elements = [] for el in value: if type(el) == str: elements.append("'{}'".format(el.replace("'", "\\'"))) else: elements.append("{}".format(el)) if value not in RESERVED_SET_NAMES: query += "SET {}.{}=[{}]\n".format( var_name, k, ", ".join(el for el in elements)) else: query += "SET {}.{}={}\n".format( var_name, k, ", ".join(el for el in elements)) else: raise ValueError("Unknown type of attribute '{}': '{}'".format( k, type(value))) if update is True: # remove all the attributes not mentioned in 'attrs' query += ( "SET {} = apoc.map.clean(properties({}), \n".format( var_name, var_name) + "\tfilter(x IN keys({}) WHERE NOT x IN [{}] AND x <> 'id'), [])". format(var_name, ", ".join("'{}'".format(k) for k in attrs.keys()))) return query
def copy_node(self, node_id, copy_id=None): """Copy node. Create a copy of a node in a graph. A new id for the copy is generated by regraph.primitives.unique_node_id. Parameters ---------- node_id : hashable Node to copy. Returns ------- new_name Id of the copy node. """ if copy_id is None: copy_id = self.generate_new_node_id(node_id) if copy_id in self.nodes(): raise ReGraphError( "Cannot create a copy of '{}' with id '{}', ".format( node_id, copy_id) + "node '{}' already exists in the graph".format(copy_id)) attrs = self.get_node(node_id) self.add_node(copy_id, attrs) return copy_id
def add_edge(self, s, t, attrs=None, **attr): """Add an edge to a graph. Parameters ---------- graph : networkx.(Di)Graph s : hashable, source node id. t : hashable, target node id. attrs : dict Edge attributes. """ if attrs is None: attrs = attr else: try: attrs.update(attr) except AttributeError: raise ReGraphError( "The attr_dict argument must be a dictionary.") new_attrs = safe_deepcopy_dict(attrs) if s not in self.nodes(): raise GraphError("Node '{}' does not exist!".format(s)) if t not in self.nodes(): raise GraphError("Node '{}' does not exist!".format(t)) normalize_attrs(new_attrs) if (s, t) in self.edges(): raise GraphError("Edge '{}'->'{}' already exists!".format(s, t)) self._graph.add_edge(s, t, **new_attrs)
def load(cls, filename): """Load a graph from a JSON file. Create a `networkx.(Di)Graph` object from a JSON representation stored in a file. Parameters ---------- filename : str Name of the file to load the json serialization of the graph Returns ------- Graph object Raises ------ ReGraphError If was not able to load the file """ if os.path.isfile(filename): with open(filename, "r+") as f: j_data = json.loads(f.read()) return cls.from_json(j_data) else: raise ReGraphError( "Error loading graph: file '{}' does not exist!".format( filename))
def load_graph(filename, directed=True): """Load a graph from a JSON file. Create a `networkx.(Di)Graph` object from a JSON representation stored in a file. Parameters ---------- filename : str Name of the file to load the json serialization of the graph directed : bool, optional `True` if the graph to load is directed, `False` otherwise. Default value `True`. Returns ------- nx.(Di)Graph object Raises ------ ReGraphError If was not able to load the file """ if os.path.isfile(filename): with open(filename, "r+") as f: j_data = json.loads(f.read()) return graph_from_json(j_data, directed) else: raise ReGraphError("Error loading graph: file '%s' does not exist!" % filename)
def add_edges_from(graph, edge_list): """Add edges from an edge list. Parameters ---------- graph : networkx.(Di)Graph edge_list : iterable Iterable containing a collection of edges, optionally, with their attributes Raises ------ ReGraphError If an element of the collection is neither a tuple of size 2 (containing a source and a target of an edge), not a tuple of size 3 (containing a source, a target and attributes of an edge). Examples -------- >>> import networkx as nx >>> from regraph.primitives import add_nodes_from, add_edges_from >>> G = nx.Graph() >>> add_nodes_from(G, [1, 2, 3]) >>> add_edges_from(G, [(1, 2), (2, 3, {"a": 1})]) """ for e in edge_list: if len(e) == 2: add_edge(graph, e[0], e[1]) elif len(e) == 3: add_edge(graph, e[0], e[1], e[2]) else: raise ReGraphError( "Was expecting 2 or 3 elements per tuple, got %s." % str(len(e)))
def get_unique_map_to_pullback_complement_full(a_p, p_c, a_prime_a, a_prime_z, z_c): """Find morphism z->p using the UP of PBC.""" # Preliminary checks if not is_monic(a_p): raise ReGraphError("Morphism 'a_p' is required to be a mono " "to use the UP of the pullback complement") z_p = {} for z_element, c_element in z_c.items(): a_prime_elements = keys_by_value(a_prime_z, z_element) p_elements1 = set() # candidate p elements for a_prime_element in a_prime_elements: p_elements1.add(a_p[a_prime_a[a_prime_element]]) # resolve ambiguity going the other way p_elements2 = keys_by_value(p_c, c_element) if len(p_elements1) == 0: if len(p_elements2) == 1: z_p[z_element] = list(p_elements2)[0] else: raise ValueError("Something is wrong") else: intersection = p_elements1.intersection(p_elements2) if len(intersection) == 1: z_p[z_element] = list(intersection)[0] else: raise ValueError("Something is wrong") return z_p
def merge_attributes(attr1, attr2, method="union"): """Merge two dictionaries of attributes.""" if method == "union": return attrs_union(attr1, attr2) elif method == "intersection": return attrs_intersection(attr1, attr2) else: raise ReGraphError("Merging method %s is not defined!" % method)
def union_mappings(map1, map2): new_mapping = copy.deepcopy(map1) for (source, target) in map2.items(): if source in new_mapping: if new_mapping[source] != target: raise ReGraphError("merging uncompatible mappings") else: new_mapping[source] = target return new_mapping
def graph_from_json(j_data, directed=True): """Create a graph from a python dictionary.""" loaded_nodes = [] if "nodes" in j_data.keys(): j_nodes = j_data["nodes"] for node in j_nodes: if "id" in node.keys(): node_id = node["id"] else: raise ReGraphError( "Error loading graph: node id is not specified!") attrs = None if "attrs" in node.keys(): attrs = json_dict_to_attrs(node["attrs"]) loaded_nodes.append((node_id, attrs)) else: raise ReGraphError( "Error loading graph: no nodes specified!") loaded_edges = [] if "edges" in j_data.keys(): j_edges = j_data["edges"] for edge in j_edges: if "from" in edge.keys(): s_node = edge["from"] else: raise ReGraphError( "Error loading graph: edge source is not specified!") if "to" in edge.keys(): t_node = edge["to"] else: raise ReGraphError( "Error loading graph: edge target is not specified!") if "attrs" in edge.keys(): attrs = json_dict_to_attrs(edge["attrs"]) loaded_edges.append((s_node, t_node, attrs)) else: loaded_edges.append((s_node, t_node)) if directed: graph = nx.DiGraph() else: graph = nx.Graph() add_nodes_from(graph, loaded_nodes) add_edges_from(graph, loaded_edges) return graph
def identity(a, b): """Return identity homomorphism from a to b.""" dic = {} for n in a.nodes(): if n in b.nodes(): dic[n] = n else: raise ReGraphError("Cannot construct morphism by names: " "node '%s' not found in the second graph!" % n) return dic
def load_nodes_from_json(j_data): """Load nodes from json-like dict.""" loaded_nodes = [] if "nodes" in j_data.keys(): j_nodes = j_data["nodes"] for node in j_nodes: if "id" in node.keys(): node_id = node["id"] else: raise ReGraphError( "Error loading graph: node id is not specified!") attrs = None if "attrs" in node.keys(): attrs = json_dict_to_attrs(node["attrs"]) loaded_nodes.append((node_id, attrs)) else: raise ReGraphError( "Error loading graph: no nodes specified!") return loaded_nodes
def add_edge(graph, s, t, attrs=None, **attr): """Add an edge to a graph. Parameters ---------- graph : networkx.(Di)Graph s : hashable, source node id. t : hashable, target node id. attrs : dict Edge attributes. Raises ------ ReGraphError If `attrs` is not a dictionary GraphError If either one of the nodes does not exist in the graph or an edge between `s` and `t` already exists. """ # Set up attribute dict (from Networkx to preserve the signature). if attrs is None: attrs = attr else: try: attrs.update(attr) except AttributeError: raise ReGraphError( "The attr_dict argument must be a dictionary." ) new_attrs = deepcopy(attrs) if s not in graph.nodes(): raise GraphError("Node '%s' does not exist!" % s) if t not in graph.nodes(): raise GraphError("Node '%s' does not exist!" % t) normalize_attrs(new_attrs) if graph.is_directed(): if (s, t) in graph.edges(): raise GraphError( "Edge '%s'->'%s' already exists!" % (s, t) ) graph.add_edge(s, t, new_attrs) else: if (s, t) in graph.edges() or (t, s) in graph.edges(): raise GraphError( "Edge '%s'->'%s' already exists!" % (s, t) ) graph.add_edge(s, t) graph.edge[s][t] = new_attrs graph.edge[t][s] = new_attrs
def __init__(self, incoming_graph_data=None, **attr): """Initialize NetworkX graph.""" super().__init__() if incoming_graph_data: if isinstance(incoming_graph_data, nx.DiGraph): self._graph = incoming_graph_data else: raise ReGraphError( "The incoming_graph_data argument must be nx.DiGraph.") else: self._graph = nx.DiGraph()
def multi_pullback_pushout(d, graphs, pullback_filter=None): """graphs: list of graphs and typings by d [(g1, t1), (g2, t2), ...] """ if graphs == []: raise ReGraphError("multi pullback_pushout with empty list") tmp_graph = graphs[0][0] tmp_typing = graphs[0][1] for (graph, typing) in graphs[1:]: (tmp_graph, _, _, tmp_typing) = pullback_pushout(tmp_graph, graph, d, tmp_typing, typing, pullback_filter) return (tmp_graph, tmp_typing)
def load_edges_from_json(j_data): """Load edges from json-like dict.""" loaded_edges = [] if "edges" in j_data.keys(): j_edges = j_data["edges"] for edge in j_edges: if "from" in edge.keys(): s_node = edge["from"] else: raise ReGraphError( "Error loading graph: edge source is not specified!") if "to" in edge.keys(): t_node = edge["to"] else: raise ReGraphError( "Error loading graph: edge target is not specified!") if "attrs" in edge.keys(): attrs = json_dict_to_attrs(edge["attrs"]) loaded_edges.append((s_node, t_node, attrs)) else: loaded_edges.append((s_node, t_node)) return loaded_edges
def generate_attributes(attrs): """Generate a string converting attrs to Cypher compatible format.""" if attrs is None: return "" else: attrs_items = [] for k, value in attrs.items(): if isinstance(value, IntegerSet): if value.is_universal: attrs_items.append("{}: ['IntegerSet']\n".format(k)) else: raise ReGraphError( "Non universal IntegerSet is not allowed as " "an attribute value (not implemented)") elif isinstance(value, RegexSet): if value.is_universal: attrs_items.append("{}: ['StringSet']\n".format(k)) else: raise ReGraphError( "Non universal RegexSet is not allowed as " "an attribute value (not implemented)") elif isinstance(value, FiniteSet): elements = [] for el in value: if type(el) == str: elements.append("'{}'".format(el.replace("'", "\\'"))) else: elements.append("{}".format(el)) attrs_items.append("{}: [{}]".format( k, ", ".join(el for el in elements))) elif isinstance(value, UniversalSet): attrs_items.append("{}: ['StringSet']\n".format(k)) else: raise ValueError("Unknown type of attribute '{}': '{}'".format( k, type(value))) return ", ".join(i for i in attrs_items)
def relabel_node(self, node_id, new_id): """Relabel a node in the graph. Parameters ---------- node_id : hashable Id of the node to relabel. new_id : hashable New label of a node. """ if new_id in self.nodes(): raise ReGraphError("Cannot relabel '{}' to '{}', '{}' ".format( node_id, new_id, new_id) + "already exists in the graph") self.clone_node(node_id, new_id) self.remove_node(node_id)
def get_unique_map_to_pullback_complement(a_p, p_c, a_prime_a, a_prime_z, z_c): """Find morphism z->p using the UP of PBC.""" # Preliminary checks if not is_monic(a_p): raise ReGraphError("Morphism 'a_p' is required to be a mono " "to use the UP of the pullback complement") z_p = {} for z_element, c_element in z_c.items(): a_prime_elements = keys_by_value(a_prime_z, z_element) p_elements1 = set() # candidate p elements for a_prime_element in a_prime_elements: p_elements1.add(a_p[a_prime_a[a_prime_element]]) # resolve ambiguity going the other way p_elements2 = keys_by_value(p_c, c_element) if len(p_elements1) == 0: if len(p_elements2) == 1: z_p[z_element] = list(p_elements2)[0] else: raise ValueError( "Cannot apply the universal property, " + "check if the conditions to apply it are satisfied, " + "problem: element '{}' from Z ".format(z_element) + "corresponds to more than one element " + "from P (i.e. corresponds to {}) and A'->A->P doesn't ". format(p_elements2) + "resolve the conflict") else: intersection = p_elements1.intersection(p_elements2) if len(intersection) == 1: z_p[z_element] = list(intersection)[0] elif len(intersection) == 0: raise ValueError( "Cannot apply the universal property, " + "check if the conditions to apply it are satisfied, " + "problem: element '{}' from Z ".format(z_element) + "corresponds to '{}' ".format(p_elements1) + "from P following A'->A->P ".format(intersection) + "to '{}' following (P->C)^{{-1}} composed with (Z -> C)". format(p_elements2)) else: raise ValueError( "Cannot apply the universal property, " + "check if the conditions to apply it are satisfied, " + "problem: element '{}' from Z ".format(z_element) + "doesn't corresponds to exactly one element " + "from P (i.e. corresponds to {}) in both A'->A->P ".format( intersection) + "and (P->C)^{{-1}} composed with (Z -> C)") return z_p
def format_typing(typing): if typing is None: typing = dict() new_typing = dict() for key, value in typing.items(): if type(value) == dict: new_typing[key] = copy.deepcopy(value) else: try: if len(value) == 2: new_typing[key] = copy.deepcopy(value) elif len(value) == 1: new_typing[key] = copy.deepcopy(value[0]) except: raise ReGraphError("Typing format is not valid!") return new_typing
def get_unique_map(a, b, c, d, a_b, b_d, c_d): """Get a map a->c that makes a square commute.""" a_c = dict() for node in b.nodes(): a_keys = keys_by_value(a_b, node) if len(a_keys) > 0: # node stayed in the rule if node in b_d.keys(): d_node = b_d[node] c_keys = keys_by_value(c_d, d_node) if len(a_keys) != len(c_keys): raise ReGraphError("Map is not unique!") else: for i, a_key in enumerate(a_keys): a_c[a_key] = c_keys[i] return a_c
def add_edges_from(self, edge_list): """Add edges from an edge list. Parameters ---------- edge_list : iterable Iterable containing a collection of edges, optionally, with their attributes """ for e in edge_list: if len(e) == 2: self.add_edge(e[0], e[1]) elif len(e) == 3: self.add_edge(e[0], e[1], e[2]) else: raise ReGraphError( "Was expecting 2 or 3 elements per tuple, got %s." % str(len(e)))
def load(cls, driver=None, uri=None, user=None, password=None, filename=None, clear=False): """Load a Neo4j graph from a JSON file. Create a `networkx.(Di)Graph` object from a JSON representation stored in a file. Parameters ---------- filename : str Name of the file to load the json serialization of the graph directed : bool, optional `True` if the graph to load is directed, `False` otherwise. Default value `True`. Returns ------- nx.(Di)Graph object Raises ------ ReGraphError If was not able to load the file """ if os.path.isfile(filename): with open(filename, "r+") as f: j_data = json.loads(f.read()) return cls.from_json(driver=driver, uri=uri, user=user, password=password, j_data=j_data, clear=clear) else: raise ReGraphError( "Error loading graph: file '%s' does not exist!" % filename)
def reduce_union(sets): """sympy.set does not reduce complements in a union""" if isinstance(sets, Union): new_args = [] for sset in sets._args: if not sset.is_Complement: new_args.append(sset) if sset.is_Complement: if len(sset.args) != 2: raise ReGraphError("complement without 2 args") other_sets = [s for s in sets._args if s != sset] new_sset = Complement(sset.args[0], Complement(sset.args[1], *other_sets, evaluate=True), evaluate=True) new_args.append(new_sset) return Union(*new_args, evaluate=True) else: return sets
def relabel_nodes(graph, mapping): """Relabel graph nodes inplace given a mapping. Similar to networkx.relabel.relabel_nodes: https://networkx.github.io/documentation/development/_modules/networkx/relabel.html Parameters ---------- graph : networkx.(Di)Graph mapping: dict A dictionary with keys being old node ids and their values being new id's of the respective nodes. Raises ------ ReGraphError If new id's do not define a set of distinct node id's. """ unique_names = set(mapping.values()) if len(unique_names) != len(graph.nodes()): raise ReGraphError( "Attempt to relabel nodes failed: the IDs are not unique!") temp_names = {} # Relabeling of the nodes: if at some point new ID conflicts # with already existing ID - assign temp ID for key, value in mapping.items(): if key != value: if value not in graph.nodes(): clone_node(graph, key, value) remove_node(graph, key) else: new_name = clone_node(graph, key) temp_names[new_name] = value # Relabeling the nodes with the temp ID to their new IDs for key, value in temp_names: if key != value: clone_node(graph, key, value) remove_node(graph, key) return
def get_unique_map_from_pushout(p, a_p, b_p, a_z, b_z): """Find a unique map to pushout.""" p_z = dict() for value in p: z_values = set() a_values = set(keys_by_value(a_p, value)) for a_value in a_values: if a_value in a_z.keys(): z_values.add(a_z[a_value]) b_values = set(keys_by_value(b_p, value)) for b_value in b_values: if b_value in b_z.keys(): z_values.add(b_z[b_value]) if len(z_values) > 0: if len(z_values) > 1: raise ReGraphError("Cannot construct a unique map!") p_z[value] = z_values.pop() return p_z
def to_atset(value): """Convert an attribute value to AtSet object.""" if isinstance(value, str): symset = safe_sympify(value) # check that there are no symbols return AtSet(convert(AtSymSet(symset)), AtEmptySet()) elif isinstance(value, list) or isinstance(value, set): str_vals = [] num_vals = [] for val in value: if test_number(val): num_vals.append(val) else: str_vals.append(val) res = AtSet(convert(AtFinSet(set(sympify(num_vals)))), convert(AtPosStringSet(set(str_vals)))) return res elif isinstance(value, dict): return AtSet.from_json(value) elif isinstance(value, AtSet): return value else: raise ReGraphError("value {} should be a list, set, string or dict " "representation of AtSet".format(value))