def s2(g: gt.Graph, weight_prop: gt.EdgePropertyMap, labels, budget=20): L = set() n = g.num_vertices() known_labels = -np.ones(n) * np.inf W = gt.topology.shortest_distance(g, weights=weight_prop).get_2d_array( range(n)) #original distance map x = np.random.choice(list(set(range(n)).difference(L))) while budget > 0: known_labels[x] = labels[x] L.add(x) if len(L) == n: break budget -= 1 to_remove = [] for e in g.get_out_edges(x): if known_labels[e[1]] >= 0 and known_labels[ e[1]] != known_labels[x]: to_remove.append(e) for e in to_remove: g.remove_edge(g.edge(e[0], e[1])) #mid_point = mssp(g, weight_prop, L, known_labels) if False: x = int(mid_point) else: x = np.random.choice(list(set(range(n)).difference(L))) return label_propagation(W, known_labels, np.unique(labels))
def create_true_graph(self): final_workflow = Graph() final_workflow.add_vertex(self.network_size) i = 0 while max([ shortest_distance(final_workflow, x, (self.network_size - 1)) for x in final_workflow.get_vertices() ]) > 100: i += 1 if i > 10000: raise RuntimeError('generating graph took too long') valid_source_nodes = [ index for index, in_degree in enumerate( final_workflow.get_in_degrees( final_workflow.get_vertices())) if ((in_degree > 0 or index < self.input_nodes) and index < (self.network_size - 1)) ] valid_to_nodes = [ index for index in final_workflow.get_vertices() if (index >= self.input_nodes) ] new_edge = final_workflow.add_edge( self.np_random.choice(valid_source_nodes), self.np_random.choice(valid_to_nodes)) if not is_DAG(final_workflow): final_workflow.remove_edge(new_edge) observation = adjacency(final_workflow).toarray().astype(int) if not self.observation_space.contains(observation): final_workflow.remove_edge(new_edge) return final_workflow
def ring(num_vtx=100, k=2, p=0.0): g = Graph(directed=False) vtx = list(g.add_vertex(num_vtx)) # connect neighbors for i in vtx: for j in xrange(1, k + 1): dest = g.vertex((g.vertex_index[i] - j) % num_vtx) if g.edge(i, dest) is None: g.add_edge(i, dest) # redirect edges # old_edges = list(g.edges()) old_edges = [(x.source(), x.target()) for x in g.edges()] for i in old_edges: n = random.random() if n < p: # redirect edge; choose random vertex as new destination vtx_tmp = vtx[:] vtx_tmp.remove(i[1]) if i[0] in vtx_tmp: vtx_tmp.remove(i[0]) dest = random.choice(vtx_tmp) while g.edge(i[0], dest) is not None: vtx_tmp.remove(dest) dest = random.choice(vtx_tmp) g.remove_edge(g.edge(i[0], i[1])) g.add_edge(i[0], dest) return g
def _filter_short_branch(self, filter=False, short=30): """ filter out very short branches: do this maybe not right for some models, for models with flat part, it is right I will test how this effect the final matching results need to delete nodes, switch with the last one then delete last """ if filter == False: self.verts = self.verts_init self.edges = self.edges_init else: init_graph = Graph(directed=False) init_graph.add_vertex(len(self.verts_init)) for edge in self.edges_init: init_graph.add_edge(init_graph.vertex(edge[0]), init_graph.vertex(edge[1])) terminal_node = [] for v in init_graph.vertices(): if v.out_degree() == 1: terminal_node.append(v) visitor = DepthVisitor() short_nodes = [] for tn in terminal_node: search.dfs_search(init_graph, tn, visitor) tmp_node = visitor.get_short_branch(min_length=short) visitor.reset() for n in tmp_node: short_nodes.append(n) ## get edges on the short paths short_nodes = list(set(short_nodes)) short_edges = [] temp_verts = self.verts_init[:] v_num = len(self.verts_init) if len(short_nodes): for v in reversed(sorted(short_nodes)): for ve in init_graph.vertex(v).out_edges(): short_edges.append(ve) ## delete edges first, then vertex short_edges = list(set(short_edges)) for e in short_edges: init_graph.remove_edge(e) print 'deleting vertex', for v in reversed(sorted(short_nodes)): print v, temp_verts[int(v)] = temp_verts[v_num-1] init_graph.remove_vertex(v, fast=True) v_num -= 1 print '\ndeleting related edges' # already done above, just info user else: print 'no short branches' ######## new vertices and edges ######## self.verts = temp_verts[:v_num] self.edges = [] for e in init_graph.edges(): self.edges.append([int(e.source()), int(e.target())])
def shortest_path_cover_logn_apx(g: gt.Graph, weight: gt.EdgePropertyMap): started_with_directed = g.is_directed() if not g.is_directed(): reversed_edges = np.fliplr(g.get_edges()) g.set_directed(True) g.add_edge_list(reversed_edges) weight.a[-reversed_edges.shape[0]:] = weight.a[:reversed_edges. shape[0]] if weight.value_type() not in [ "bool", "int", "int16_t", "int32_t", "int64_t" ]: #min = np.min(weight.a) #min_second = np.min(weight.a[weight.a > min]) eps = 1 #min_second - min scaled_weight = (np.ceil(weight.a / eps) * (g.num_vertices() + 1)).astype(np.int) # ints >= 1 else: scaled_weight = weight.a * (g.num_vertices() + 1) summed_edge_weight = np.sum(scaled_weight) adjusted_weight = g.new_edge_property("long", vals=scaled_weight - 1) paths = [] covered_vertices = set() while len(covered_vertices) != g.num_vertices(): curr_paths = shortest_path_visiting_most_nodes(g, adjusted_weight, covered_vertices, summed_edge_weight) for path in curr_paths: paths.append(path) #if len(path) <= 2 switch to fast mode and just add single edges/vertices until done. path_vertices = set(path) for v in path_vertices.difference(covered_vertices): for w in g.get_in_neighbors(v): adjusted_weight[g.edge(w, v)] += 1 #.a[list()] -= 1 if adjusted_weight[g.edge( w, v)] % (g.num_vertices() + 1) != 0: exit(5) new_covered = path_vertices.difference(covered_vertices) covered_vertices = covered_vertices.union(path_vertices) print(len(new_covered), len(path), len(covered_vertices), path) if not started_with_directed: g.set_directed(False) for e in reversed_edges: g.remove_edge(g.edge(e[0], e[1])) return paths
def s2(g: graph_tool.Graph, weight_prop: graph_tool.EdgePropertyMap, labels, budget=20, use_adjacency=False): L = set() n = g.num_vertices() known_labels = -np.ones(n) * np.inf W = graph_tool.topology.shortest_distance( g, weights=weight_prop).get_2d_array(range(n)) #original distance map x = np.random.choice(list(set(range(n)).difference(L))) while budget > 0: known_labels[x] = labels[x] L.add(x) if len(L) == n: break budget -= 1 to_remove = [] for e in g.get_out_edges(x): if known_labels[e[1]] > -np.inf and known_labels[ e[1]] != known_labels[x]: to_remove.append(e) for e in to_remove: g.remove_edge(g.edge(e[0], e[1])) mid_point = mssp(g, weight_prop, L, known_labels) if mid_point is not None: x = int(mid_point) else: x = np.random.choice(list(set(range(n)).difference(L))) prediction = label_propagation(W, known_labels, labels, use_adjacency=use_adjacency) print("accuracy", np.sum(prediction == labels) / labels.size)
class ob_viz(QWidget): def __init__(self, bg_color): QWidget.__init__(self) self.background_color = bg_color self.c = 0 # K = 0.5 # how many iterations the realignment is supposed to take self.step = 15 self.rwr_c = 0 # dumper([qt_coords]) dumper(['obv viz init']) # self.show() # with open("/tmp/eaf3.csv", "a") as fo: # wr = csv.writer(fo) # wr.writerow([self.c, "runs4"]) # dumper([self.c, "runs4"]) # self.node_names [g_id[i] for i in g.vertices()] def init2(self, emacs_var_dict): self.emacs_var_dict = emacs_var_dict self.link_str = self.emacs_var_dict['links'] self.g = Graph() self.label_ep = self.g.new_edge_property("string") self.links = self.link_str.split(";") link_tpls = [i.split(" -- ") for i in self.links] dumper([str(i) for i in link_tpls]) self.g_id = self.g.add_edge_list(link_tpls, hashed=True, string_vals=True, eprops=[self.label_ep]) self.adj = np.array([(int(i.source()), int(i.target())) for i in self.g.edges()]) self.node_names = [self.g_id[i] for i in self.g.vertices()] self.vd = {} for i in self.g.vertices(): self.vd[self.g_id[i]] = int(i) # self.pos_vp = sfdp_layout(self.g, K=0.5) self.pos_vp = fruchterman_reingold_layout(self.g) self.base_pos_ar = self.pos_vp.get_2d_array((0, 1)).T self.qt_coords = self.nolz_pos_ar(self.base_pos_ar) dumper([str(self.qt_coords)]) # dumper([link_str]) def update_graph(self, emacs_var_dict): """set new links and nodes""" new_link_str = emacs_var_dict['links'] new_links = new_link_str.split(";") new_link_tpls = [i.split(" -- ") for i in new_links] links_to_add = list(set(new_links) - set(self.links)) links_to_del = list(set(self.links) - set(new_links)) # setting new stuff self.links = new_links new_nodes = [] for tpl in new_link_tpls: new_nodes.append(tpl[0]) new_nodes.append(tpl[1]) new_nodes_unique = list(set(new_nodes)) nodes_to_del = list(set(self.node_names) - set(new_nodes_unique)) nodes_to_add = list(set(new_nodes_unique) - set(self.node_names)) dumper([ "nodes_to_add: ", nodes_to_add, "nodes_to_del: ", nodes_to_del, "links_to_add: ", links_to_add, "links_to_del: ", links_to_del ]) # first add nodes + index them, but not there yet (first links) for n in nodes_to_add: dumper(['adding node']) v = self.g.add_vertex() # how to new nodes pos to parents? separate loop afterwards self.vd[n] = int(v) self.g_id[v] = n del_node_ids = [self.vd[i] for i in nodes_to_del] self.g.remove_vertex(del_node_ids) # have to reindex after deletion self.vd = {} for i in self.g.vertices(): self.vd[self.g_id[i]] = int(i) dumper(['node deleted']) # nodes_to_del_id = # dumper(['old nodes deleted, add new links']) for l in links_to_add: tpl = l.split(" -- ") n0, n1 = tpl[0], tpl[1] self.g.add_edge(self.vd[n0], self.vd[n1]) # dumper(['new links added, delete old links']) for l in links_to_del: tpl = l.split(" -- ") n0 = tpl[0] n1 = tpl[1] dumper([list(self.vd.keys())]) # only remove edge when neither of nodes removed if n0 in self.vd.keys() and n1 in self.vd.keys(): self.g.remove_edge(self.g.edge(self.vd[n0], self.vd[n1])) # dumper(['graph modifications done']) # set positions of new nodes to parent nodes for n in nodes_to_add: v = self.g.vertex(self.vd[n]) v_prnt = list(v.all_neighbors())[0] self.pos_vp[v] = self.pos_vp[v_prnt] # dumper(['node positions adjusted']) self.adj = np.array([(int(i.source()), int(i.target())) for i in self.g.edges()]) self.node_names = [self.g_id[i] for i in self.g.vertices()] # dumper(['storage objects updated']) # dumper(["nbr_edges new: ", str(len([i for i in self.g.edges()]))]) # dumper(['nodes_to_add'] + nodes_to_add) # seems to work dumper(['to here']) self.recalculate_layout() dumper(['to here2']) def recalculate_layout(self): """calculate new change_array, set rwr_c counter""" dumper(['recalculating starting']) self.base_pos_ar = self.pos_vp.get_2d_array((0, 1)).T # set_dict = {'p': 2, 'max_level': 20, 'adaptive_cooling': False, # 'gamma': 1, 'theta': 1, 'cooling_step': 0.3, 'C': 0.6, 'mu_p': 1.2} # self.goal_vp = sfdp_layout(self.g, K=0.5, pos=self.pos_vp, **set_dict) self.goal_vp = fruchterman_reingold_layout(self.g, pos=self.pos_vp) goal_ar = self.goal_vp.get_2d_array([0, 1]).T self.chng_ar = (goal_ar - self.base_pos_ar) / self.step self.rwr_c = self.step dumper(["base_pos_ar: ", self.base_pos_ar]) dumper(["goal_ar: ", goal_ar]) dumper(["chng_ar: ", self.chng_ar]) dumper(['recalculating done']) def redraw_layout(self): """actually do the drawing, run multiple (step (rwr_c)) times""" self.cur_pos_ar = np.round( self.base_pos_ar + self.chng_ar * (self.step - self.rwr_c), 3) self.qt_coords = self.nolz_pos_ar(self.cur_pos_ar) self.rwr_c -= 1 self.update() # dumper(['redrawing']) # def draw_arrow(qp, p1x, p1y, p2x, p2y): def draw_arrow(self, qp, p1x, p1y, p2x, p2y, node_width): """draw arrow from p1 to rad units before p2""" # get arrow angle, counterclockwise from center -> east line # dumper(['painting time']) angle = degrees(atan2((p1y - p2y), (p1x - p2x))) # calculate attach point arw_goal_x = p2x + node_width * cos(radians(angle)) arw_goal_y = p2y + node_width * sin(radians(angle)) # calculate start point: idk how trig works but does start_px = p1x - node_width * cos(radians(angle)) start_py = p1y - node_width * sin(radians(angle)) # arrow stuff: +/- 30 deg ar1 = angle + 25 ar2 = angle - 25 arw_len = 10 # need to focus on vector from p2 to p1 ar1_x = arw_goal_x + arw_len * cos(radians(ar1)) ar1_y = arw_goal_y + arw_len * sin(radians(ar1)) ar2_x = arw_goal_x + arw_len * cos(radians(ar2)) ar2_y = arw_goal_y + arw_len * sin(radians(ar2)) # qp.drawLine(p1x, p1y, p2x, p2y) # qp.drawLine(p1x, p1y, arw_goal_x, arw_goal_y) qp.drawLine(start_px, start_py, arw_goal_x, arw_goal_y) qp.drawLine(ar1_x, ar1_y, arw_goal_x, arw_goal_y) qp.drawLine(ar2_x, ar2_y, arw_goal_x, arw_goal_y) def paintEvent(self, event): # dumper(['start painting']) node_width = 10 qp = QPainter(self) edges = [(self.qt_coords[i[0]], self.qt_coords[i[1]]) for i in self.adj] # dumper([str(i) for i in edges]) qp.setPen(QPen(Qt.green, 2, Qt.SolidLine)) # [qp.drawLine(e[0][0], e[0][1], e[1][0], e[1][1]) for e in edges] [ self.draw_arrow(qp, e[0][0], e[0][1], e[1][0], e[1][1], (node_width / 2) + 5) for e in edges ] qp.setPen(QColor(168, 34, 3)) # qp.setPen(Qt.green) qp.setFont(QFont('Decorative', 10)) [ qp.drawText(t[0][0] + node_width, t[0][1], t[1]) for t in zip(self.qt_coords, self.node_names) ] # dumper(['done painting']) qp.setPen(QPen(Qt.black, 3, Qt.SolidLine)) # qp.setBrush(QBrush(Qt.green, Qt.SolidPattern)) dumper(['painting nodes']) for i in zip(self.qt_coords, self.node_names): if self.emacs_var_dict['cur_node'] == i[1]: qp.setPen(QPen(Qt.black, 4, Qt.SolidLine)) qp.drawEllipse(i[0][0] - (node_width / 2), i[0][1] - (node_width / 2), node_width, node_width) qp.setPen(QPen(Qt.black, 3, Qt.SolidLine)) else: qp.drawEllipse(i[0][0] - (node_width / 2), i[0][1] - (node_width / 2), node_width, node_width) # qp.drawEllipse(self.c, self.c, 7, 7) # qp.end() def nolz_pos_ar(self, pos_ar_org): """normalize pos ar to window limits""" # pos_ar_org = goal_ar size = self.size() limits = [[20, size.width() - 50], [20, size.height() - 20]] x_max = max(pos_ar_org[:, 0]) x_min = min(pos_ar_org[:, 0]) y_max = max(pos_ar_org[:, 1]) y_min = min(pos_ar_org[:, 1]) # need linear maping function again pos_ar2 = pos_ar_org pos_ar2[:, 0] = (((pos_ar2[:, 0] - x_min) / (x_max - x_min)) * (limits[0][1] - limits[0][0])) + limits[0][0] pos_ar2[:, 1] = (((pos_ar2[:, 1] - y_min) / (y_max - y_min)) * (limits[1][1] - limits[1][0])) + limits[1][0] return (pos_ar2)
class graphRL(gym.Env): """ will have fixed action space, but not all actions are valid within each state step function should have a function that tests if the chosen action is valid the observation returned will be the graph_tool graph, but the state will just be the adjacency matrix (? maybe, currently have obs space as the matrix) maybe step function just alters the given graph """ metadata = {'render.modes': ['human', 'graph', 'interactive']} def __init__(self, network_size=10, input_nodes=3): self.network_size = network_size self.input_nodes = input_nodes self.graph = Graph() self.graph.set_fast_edge_removal(True) self.graph.add_vertex(self.network_size) self.action_space = spaces.Tuple((spaces.Discrete(self.network_size), spaces.Discrete(self.network_size))) self.observation_space = spaces.MultiDiscrete( np.full((self.network_size, self.network_size), 2)) self.time_step = 0 self.observation = adjacency(self.graph).toarray().astype(int) self.seed_value = self.seed() self.true_graph = self.create_true_graph() self.reset() def create_true_graph(self): final_workflow = Graph() final_workflow.add_vertex(self.network_size) i = 0 while max([ shortest_distance(final_workflow, x, (self.network_size - 1)) for x in final_workflow.get_vertices() ]) > 100: i += 1 if i > 10000: raise RuntimeError('generating graph took too long') valid_source_nodes = [ index for index, in_degree in enumerate( final_workflow.get_in_degrees( final_workflow.get_vertices())) if ((in_degree > 0 or index < self.input_nodes) and index < (self.network_size - 1)) ] valid_to_nodes = [ index for index in final_workflow.get_vertices() if (index >= self.input_nodes) ] new_edge = final_workflow.add_edge( self.np_random.choice(valid_source_nodes), self.np_random.choice(valid_to_nodes)) if not is_DAG(final_workflow): final_workflow.remove_edge(new_edge) observation = adjacency(final_workflow).toarray().astype(int) if not self.observation_space.contains(observation): final_workflow.remove_edge(new_edge) return final_workflow def render(self, mode='human'): if mode == 'graph': # return graphtools graph object return self.graph elif mode == 'interactive': interactive_window(self.graph) elif mode == 'human': filename = "./renders/render" + str(self.time_step) + ".png" graph_draw(self.graph, vertex_text=self.graph.vertex_index, vertex_font_size=18, output_size=(1000, 1000), output=filename) def render_truth(self, mode='human'): if mode == 'graph': # return graphtools graph object return self.true_graph elif mode == 'interactive': interactive_window(self.true_graph) elif mode == 'human': filename = "./renders/TrueGraphSeed" + str( self.seed_value) + ".png" graph_draw(self.true_graph, vertex_text=self.true_graph.vertex_index, vertex_font_size=18, output_size=(1000, 1000), output=filename) def seed(self, seed=None): self.np_random, seed = seeding.np_random(seed) return [seed] def step(self, action): done = 0 reward = 0 assert self.action_space.contains(action) valid_source_nodes = [ index for index, in_degree in enumerate( self.graph.get_in_degrees(self.graph.get_vertices())) if ((in_degree > 0 or index < self.input_nodes) and index < (self.network_size - 1)) ] if action[0] not in valid_source_nodes: raise ValueError('this action does not have a valid from node') new_edge = self.graph.add_edge(action[0], action[1]) if not is_DAG(self.graph): self.graph.remove_edge(new_edge) raise ValueError('this action violates the DAG property') self.observation = adjacency(self.graph).toarray().astype(int) if not self.observation_space.contains(self.observation): print(self.observation) self.graph.remove_edge(new_edge) self.observation = adjacency(self.graph).toarray().astype(int) raise ValueError('this action makes a duplicate edge') if isomorphism(self.graph, self.true_graph): reward = 1 done = 1 self.time_step += 1 return self.observation, reward, done, {"time_step": self.time_step} def reset(self): self.graph.clear_edges() self.time_step = 0 self.observation = adjacency(self.graph).toarray() return self.observation