def graph_equals( g1: nx.DiGraph, g2: nx.DiGraph, weight_column_name: Text = 'weight') -> bool: """Checks if two graphs are equal. If weight_column_name is None, then it does not check weight values. Args: g1: First graph to be compared. g2: Second graph to be compared. weight_column_name: The name of weight column. Returns: Boolean whether g1 equals g2 or not. Raises: None. """ if g1.nodes() != g2.nodes(): return False if g1.edges() != g2.edges(): return False if weight_column_name: for edge in g1.edges(): w1 = g1.get_edge_data(edge[0], edge[1])[weight_column_name] w2 = g2.get_edge_data(edge[0], edge[1])[weight_column_name] if w1 != w2: return False return True
def equivalent_singlegraphs(g1_single: nx.DiGraph, g2_single: nx.DiGraph) -> bool: return all([ g1_single.get_edge_data(*e) == g2_single.get_edge_data(*e) for e in g1_single.edges() ] + [ g1_single.get_edge_data(*e) == g2_single.get_edge_data(*e) for e in g2_single.edges() ]) & (g1_single.nodes() == g2_single.nodes())
def is_gather_scatter(alert_sub_g: nx.DiGraph, is_ordered: bool = True): alert_id = alert_sub_g.graph["alert_id"] num_accts = alert_sub_g.number_of_nodes() out_degrees = alert_sub_g.out_degree() in_degrees = alert_sub_g.in_degree() orig_accts = [ n for n, d in out_degrees.items() if d == 1 and in_degrees[n] == 0 ] bene_accts = [ n for n, d in in_degrees.items() if d == 1 and out_degrees[n] == 0 ] num_orig = len(orig_accts) num_bene = len(bene_accts) hub_accts = [ n for n, d in out_degrees.items() if d == num_bene and in_degrees[n] == num_orig ] if len(hub_accts) != 1 or (num_orig + num_bene + 1) != num_accts: logging.info("Alert %s is not a gather-scatter pattern" % alert_id) return False # Mismatched the number of accounts hub = hub_accts[0] last_gather_date = datetime.strptime("1970-01-01", "%Y-%m-%d") total_gather_amount = 0.0 for orig in orig_accts: attr = alert_sub_g.get_edge_data(orig, hub) if attr is None: logging.info( "Alert %s is not a gather-scatter pattern: gather edge %s -> %s not found" % (alert_id, orig, hub)) return False # No gather edges found date = attr["date"] amount = attr["amount"] last_gather_date = max(last_gather_date, date) total_gather_amount += amount if is_ordered: max_scatter_amount = total_gather_amount / num_bene for bene in bene_accts: attr = alert_sub_g.get_edge_data(hub, bene) if attr is None: return False date = attr["date"] amount = attr["amount"] if date < last_gather_date: logging.info( "Alert %s gather-scatter transactions are chronologically unordered " % alert_id) return False elif max_scatter_amount <= amount: logging.info( "Alert %s gather-scatter transaction amounts are unordered" % alert_id) return False return True
def __increase_flow_in_path(self, path, graph: nx.DiGraph, n): for i in reversed(range(1, len(path))): if graph.get_edge_data(path[i], path[i - 1]) is not None: current_weight = float( graph.get_edge_data(path[i], path[i - 1])["weight"]) + float(n) graph.add_edge(path[i], path[i - 1], weight=str(current_weight))
def add_data_from_single_image_graph_to_cluster_graph(line_graph: nx.DiGraph, graph: nx.MultiDiGraph): for node in line_graph.adj: for neighbour in nx.neighbors(line_graph, node): edge = line_graph.get_edge_data(node, neighbour) for neighbour2 in nx.neighbors(line_graph, neighbour): edge2 = line_graph.get_edge_data(neighbour, neighbour2) graph.add_edge(edge['cluster'], edge2['cluster'], key=None, nodes_from=str(node) + "_" + str(neighbour), nodes_to=str(neighbour) + "_" + str(neighbour2))
def get_bags(g: nx.DiGraph, node: str): count = 1 for n in g.neighbors(node): # print(node, n, g.get_edge_data(node, n)['amount']) count += g.get_edge_data(node, n)['amount'] * get_bags(g, n) return count
class GraphConversionManager(ConversionManager): def __init__(self): super().__init__() self.conversion_graph = DiGraph() def get_conversion_path(self, start_space_name, target_space_name): if start_space_name not in self.conversion_graph: raise UndefinedColorSpaceError(start_space_name) if target_space_name not in self.conversion_graph: raise UndefinedColorSpaceError(target_space_name) try: # Retrieve node sequence that leads from start_space_name to # target_space_name. path = shortest_path(self.conversion_graph, start_space_name, target_space_name) except NetworkXNoPath: raise UndefinedConversionError( start_space_name, target_space_name, ) # Look up edges between nodes and retrieve the conversion function # for each edge. cf = 'conversion_function' return [self.conversion_graph.get_edge_data(node_a, node_b)[cf] for node_a, node_b in zip(path[:-1], path[1:])] def add_type_conversion(self, start_space_name, target_space_name, conversion_function): super().add_type_conversion(start_space_name, target_space_name, conversion_function) self.conversion_graph.add_edge( start_space_name, target_space_name, {'conversion_function': conversion_function})
def count_descends(graph: networkx.DiGraph, node: str) -> int: total = 0 for child, _ in graph.in_edges(node): count = 1 + count_descends(graph, child) multiplier = graph.get_edge_data(child, node)['count'] total += multiplier * count return total
def _transitive_closure(def_: Definition, graph: networkx.DiGraph, result: networkx.DiGraph, visited: Optional[Set[Definition]] = None): if def_ in self._transitive_closures.keys(): return self._transitive_closures[def_] predecessors = list(graph.predecessors(def_)) result.add_node(def_) result.add_edges_from( list( map(lambda e: (*e, graph.get_edge_data(*e)), map(lambda p: (p, def_), predecessors)))) visited = visited or set() visited.add(def_) predecessors_to_visit = set(predecessors) - set(visited) closure = reduce( lambda acc, definition: _transitive_closure( definition, graph, acc, visited), predecessors_to_visit, result) self._transitive_closures[def_] = closure return closure
def _transitive_closure(def_: Definition, graph: networkx.DiGraph, result: networkx.DiGraph, visited: Optional[Set[Definition]] = None): """ Returns a joint graph that comprises the transitive closure of all defs that `def_` depends on and the current graph `result`. `result` is updated. """ if def_ in self._transitive_closures.keys(): closure = self._transitive_closures[def_] # merge closure into result result.add_edges_from(closure.edges()) return result predecessors = list(graph.predecessors(def_)) result.add_node(def_) result.add_edges_from( list( map(lambda e: (*e, graph.get_edge_data(*e)), map(lambda p: (p, def_), predecessors)))) visited = visited or set() visited.add(def_) predecessors_to_visit = set(predecessors) - set(visited) closure = reduce( lambda acc, def0: _transitive_closure( def0, graph, acc, visited), predecessors_to_visit, result) self._transitive_closures[def_] = closure return closure
def get_call_track(cfg: DiGraph, node): """ Returns a list containing all the nodes that are placed between the given one and the first CALL edge found going upward :param cfg: :param node: :return: """ track = [] unvisited_edges = set() visited_edges = [] for edge in cfg.in_edges(node[0]): track.append(edge[0]) for edge_t in cfg.in_edges(edge[0]): unvisited_edges.add(edge_t) while len(unvisited_edges) > 0: curr_edge = unvisited_edges.pop() if curr_edge[0] not in track: track.append(curr_edge[0]) if cfg.get_edge_data(curr_edge[0], curr_edge[1])['kind'] == Transition.CALL: return track for edge in cfg.in_edges(curr_edge[0]): if edge not in visited_edges: unvisited_edges.add(edge) visited_edges.append(curr_edge) if node in track: track.remove(node) return track
def add_path_step(line_code: int, current_node, from_node_label: str, step_count: int, line_graph: nx.DiGraph, graph: nx.MultiDiGraph, taboo_list: Set[int]): if step_count == 5: return for neighbour in nx.neighbors(line_graph, current_node): if neighbour in taboo_list: continue edge = line_graph.get_edge_data(current_node, neighbour) cluster_number = edge['cluster'] to_node_label = str(step_count + 1) + '_' + str(cluster_number) existing_edges = graph.get_edge_data(from_node_label, to_node_label, default=None) if not edge_exists(existing_edges, line_code, current_node, neighbour): graph.add_edge(from_node_label, to_node_label, line_code=line_code, from_node=current_node, to_node=neighbour, cluster=cluster_number) taboo_list.add(neighbour) add_path_step(line_code, neighbour, to_node_label, step_count + 1, line_graph, graph, taboo_list)
def _add_field(graph: nx.DiGraph, parent_node: str, field: FieldDescriptor) -> None: field_node: str = _get_node_name(field) field_type: str = _get_type_ascription(field) if graph.nodes.get(field_node) is None: graph.add_node(field_node, node_type=field_type) if graph.get_edge_data(parent_node, field_node) is None: graph.add_edge(parent_node, field_node, fields=[]) graph[parent_node][field_node]["fields"].append(field)
def calculate_happiness(seating_arrangement: Tuple[str], preference_DiGraph: nx.DiGraph) -> int: """Calculate the happiness of a given seating arrangement based on the preference DiGraph.""" happiness_score = 0 for left, right in zip(seating_arrangement, seating_arrangement[1:]): happiness_score += preference_DiGraph.get_edge_data(left, right)["happiness"] happiness_score += preference_DiGraph.get_edge_data(right, left)["happiness"] # Wrap tails left, right = seating_arrangement[0], seating_arrangement[-1] happiness_score += preference_DiGraph.get_edge_data(left, right)["happiness"] happiness_score += preference_DiGraph.get_edge_data(right, left)["happiness"] return happiness_score
def walk_succ(graph: nx.DiGraph, node): successors = list(graph.successors(node)) total = 0 for succ in successors: succ_weight = graph.get_edge_data(node, succ)["weight"] total += succ_weight * (1 + walk_succ(graph, succ)) return total
def add_or_update_edge( graph: nx.DiGraph, edge: tuple, method_name: str, method_specified: str, score: float ): edge_exists = graph.has_edge(*edge) edge_rev_exists = graph.has_edge(edge[1], edge[0]) if not edge_exists and not edge_rev_exists: id0 = graph.nodes[edge[0]]['identifiers']['node_network_id'] id1 = graph.nodes[edge[1]]['identifiers']['node_network_id'] graph.add_edge( *edge, edges=[{ 'source': edge[0], 'target': edge[1], 'predicted': True, 'prediction_score':score, 'applied_methods': {method_name: [method_specified]} }], edge_color=COLOR_PREDICTED_ONLY, identifiers={ id0: graph.nodes[edge[0]]['identifiers']['node_id'], id1: graph.nodes[edge[1]]['identifiers']['node_id'], 'original_network_id': graph.nodes[edge[0]]['identifiers']['original_network_id'], 'predicted_network_id': graph.nodes[edge[0]]['identifiers']['predicted_network_id'] } ) else: actual_edge = edge if edge_exists else (edge[1], edge[0]) single_edges = graph.get_edge_data(*actual_edge) add_edge = True reverse_edge_predicted = False for single_edge in single_edges['edges']: if single_edge['source'] == edge[0] and single_edge['target'] == edge[1]: if single_edge['predicted'] is True: single_edge['prediction_score'] += score if method_name in single_edge['applied_methods']: single_edge['applied_methods'][method_name].append(method_specified) else: single_edge['applied_methods'][method_name] = [method_specified] add_edge = False if single_edge['source'] == edge[1] and single_edge['target'] == edge[0] \ and single_edge['predicted'] is True: reverse_edge_predicted = True if add_edge is True: single_edges['edges'].append({ 'source': edge[0], 'target': edge[1], 'predicted': True, 'prediction_score': score, 'applied_methods': {method_name: [method_specified]} }) if reverse_edge_predicted is False: single_edges['edge_color'] = COLOR_MIXED
def __decrease_flow_in_path(self, path, graph: nx.DiGraph, n): for i in range(len(path) - 1): current_weight = float( graph.get_edge_data(path[i], path[i + 1])["weight"]) - float(n) if current_weight == 0: graph.remove_edge(path[i], path[i + 1]) else: graph.add_edge(path[i], path[i + 1], weight=str(current_weight))
def assemble_record_from_cycle( cls, cycle: List[str], graph: nx.DiGraph, annotate_sources: bool = False, annotate_junctions: bool = False, ) -> SeqRecord: source_indices = [] i = 0 records = [graph.nodes[n]["record"] for n in cycle] stored_features = cls._collect_features(records) record = SeqRecord(Seq("")) c1 = cycle[-1:] + cycle[:-1] c2 = cycle c3 = cycle[1:] + cycle[:1] for n1, n2, n3 in zip(c1, c2, c3): r1, r2, r3 = [graph.nodes[n]["record"] for n in [n1, n2, n3]] edata12 = graph.get_edge_data(n1, n2) edata23 = graph.get_edge_data(n2, n3) s12 = edata12["match"]["top_strand_slice"] s23 = edata23["match"]["top_strand_slice"] o12 = r2[s12[0] : s12[1]] o23 = r3[s23[0] : s23[1]] trunc_r2 = slice_with_features(r2, slice(len(o12), -len(o23))) if annotate_junctions: annotate(o12, "junction: tm {}".format(format_float(edata12["tm"]))) record += o12 record += trunc_r2 source_indices.append((i, i + len(r2))) i = i + len(r2) - len(o23) for i, (a, b) in enumerate(source_indices): if annotate_sources: annotate( record, "source: fragment {} ({})".format(i, r2.name), a, b, cyclic=True, ) cls._restore_features(stored_features, record, cyclic=True) clean_features(record) return record
def generate_all_cs_dataset(filtered_graph: DiGraph): relations = [] for node in filtered_graph.nodes(): for neighbor in filtered_graph.neighbors(node): relation = filtered_graph.get_edge_data(node, neighbor)['label'] rs = ''.join(c if c.islower() else ' ' + c for c in relation).lower()[1:] relations.append(node + ' ' + rs + ' ' + neighbor) with open('datasets/all_cs_217k.txt', 'w') as outfile: for relation in relations: print(relation, file=outfile)
def solveMaxFlow(G: nx.DiGraph, source: int, sink: int) -> dict: """ Solves the maximum flow problem. :param G: directed graph :param source: Source node in G :param sink: Sink node in G :return: Dict of edges and flow values """ maxFlow = Model('MaxFlow') # Variable X = dict() for a in G.edges(): X[a] = maxFlow.addVar(vtype=GRB.CONTINUOUS, lb=0, ub=G.get_edge_data(*a)['capacity'], name=f'X_{a}') B = maxFlow.addVar(vtype=GRB.CONTINUOUS, lb=0, name='B') # Objective function maxFlow.setObjective(B, sense=GRB.MAXIMIZE) # Constraints for v in G.nodes(): if v == source: maxFlow.addConstr( quicksum(X[a] for a in G.out_edges(v)) - quicksum(X[a] for a in G.in_edges(v)), GRB.EQUAL, B) elif v == sink: maxFlow.addConstr( quicksum(X[a] for a in G.out_edges(v)) - quicksum(X[a] for a in G.in_edges(v)), GRB.EQUAL, -B) else: maxFlow.addConstr( quicksum(X[a] for a in G.out_edges(v)) - quicksum(X[a] for a in G.in_edges(v)), GRB.EQUAL, 0) # Solve model maxFlow.update() maxFlow.optimize() if maxFlow.status == GRB.OPTIMAL: flows = dict() for a in G.edges(): if maxFlow.getVarByName(f'X_{a}').x > 0: flows[a] = maxFlow.getVarByName(f'X_{a}').x return flows else: return dict()
def assert_graph_equals( g1: nx.DiGraph, g2: nx.DiGraph, weight_column_name: Text = 'weight') -> None: """Checks if two graphs are equal. If weight_column_name is None, then it does not check weight values. Args: g1: First graph to be compared. g2: Second graph to be compared. weight_column_name: The name of weight column. Returns: None. Raises: AssertionError: If the two graphs are not equal. It also prints a message why they do not match for easier debugging purposes. """ if g1.nodes() != g2.nodes(): raise AssertionError( 'Two graphs do not have the same nodes: {} != {}'.format( g1.nodes(), g2.nodes())) if g1.edges() != g2.edges(): raise AssertionError( 'Two graphs do not have the same edges: {} != {}'.format( g1.edges(), g2.edges())) if weight_column_name: for edge in g1.edges(): w1 = g1.get_edge_data(edge[0], edge[1])[weight_column_name] w2 = g2.get_edge_data(edge[0], edge[1])[weight_column_name] if w1 != w2: raise AssertionError( 'Two graphs do not have the same weight at {}: {} != {}' .format(edge, w1, w2))
def filter_non_commonsense(graph: DiGraph, commonsense_relations: List[str]) -> DiGraph: print('Filtering commonsense relations...') # Remove edges that are not listed as commonsense edges_to_remove = [] for x, y in graph.edges: data = graph.get_edge_data(x, y) if data['label'] not in commonsense_relations or data['weight'] == -1: edges_to_remove.append((x, y)) new_graph = copy(graph) new_graph.remove_edges_from(edges_to_remove) # Remove possible leftover island nodes islands = list(isolates(new_graph)) new_graph.remove_nodes_from(islands) return new_graph
def draw_ComputerSetDiGraph_matplotlib(spsg: nx.DiGraph, ax, pos=None, **kwargs): if pos is None: pos = nx.spring_layout(spsg) # pos = nx.circular_layout(spsg) nx.draw(spsg, labels={n: node_2_string(n) for n in spsg.nodes()}, ax=ax, node_size=2000, node_shape="s", pos=pos, **kwargs) for e in spsg.edges(): print(spsg.get_edge_data(*e)) edge_labels = { e: compset_2_string(spsg.get_edge_data(*e)["computers"]) for e in spsg.edges() } nx.draw_networkx_edge_labels(spsg, ax=ax, edge_labels=edge_labels, pos=pos)
def solveShortestPath(G: nx.DiGraph, source, sink) -> list: """ Solves the shortest path problem. :param G: directed graph :param source: Source node in G :param sink: Sink node in G :return: List of edges """ shortestPath = Model('ShortestPath') # Variable X = dict() for a in G.edges(): X[a] = shortestPath.addVar(vtype=GRB.BINARY, name=f'X_{a}') # Objective function shortestPath.setObjective(quicksum(X[a] * G.get_edge_data(*a)['weight'] for a in G.edges()), GRB.MINIMIZE) # Constraints for v in G.nodes(): if v == source: shortestPath.addConstr(quicksum(X[a] for a in G.out_edges(v)) - quicksum(X[a] for a in G.in_edges(v)), GRB.EQUAL, 1) elif v == sink: shortestPath.addConstr(quicksum(X[a] for a in G.out_edges(v)) - quicksum(X[a] for a in G.in_edges(v)), GRB.EQUAL, -1) else: shortestPath.addConstr(quicksum(X[a] for a in G.out_edges(v)) - quicksum(X[a] for a in G.in_edges(v)), GRB.EQUAL, 0) # Solve model shortestPath.update() shortestPath.optimize() if shortestPath.status == GRB.OPTIMAL: SPedges = list() for a in G.edges(): if round(shortestPath.getVarByName(f'X_{a}').x, 0) == 1.0: SPedges.append(a) return SPedges else: return list()
def generate_multiple_choice_dataset(choice_matrix_path: str, filtered_graph: DiGraph, n_choices=7, name=''): incorrect_choices_dict = {} with open(choice_matrix_path, 'r') as matrix_file: for line in matrix_file.readlines()[1:]: tokens = line.split(',') incorrect_choices_dict[tokens[0]] = [ token for token in tokens[1:] if token != '' and token != '\n' and token in commonsense_relations ] question_tuples = [] id_number = 0 counter = collections.Counter() for e1 in filtered_graph.nodes(): for e2 in filtered_graph.neighbors(e1): counter.update([(e1, e2)]) correct_relation = filtered_graph.get_edge_data(e1, e2)['label'] incorrect_relations = incorrect_choices_dict[correct_relation] question_tuples.append( (id_number, e1, e2, correct_relation, incorrect_relations)) id_number += 1 if max(counter.values()) > 1: raise RuntimeError('Some pair of entities appear more than once.') r.shuffle(question_tuples) with open(name + '-data.jsonl', 'w') as data_file: with open(name + '-labels.lst', 'w') as labels_file: for id_number, e1, e2, correct_relation, incorrect_relations in question_tuples: final_choices: List = r.sample( incorrect_relations, n_choices - 1) + [correct_relation] r.shuffle(final_choices) correct_index = final_choices.index(correct_relation) choice_strings = [ '\"sol' + str(i + 1) + '\": \"' + relation_to_string[final_choices[i]] + '\"' for i in range(n_choices) ] line_to_print = '{\"id\": \"' + str(id_number) + '\", \"e1\": \"' + e1.replace('_',' ') + \ '\", \"e2\": \"' + e2.replace('_',' ') + '\", ' + ', '.join(choice_strings) +'}' print(line_to_print, file=data_file) print(str(correct_index), file=labels_file)
def _transitive_closure(def_: Definition, graph: networkx.DiGraph, result: networkx.DiGraph): if def_ in self._transitive_closures.keys(): return self._transitive_closures[def_] predecessors = list(graph.predecessors(def_)) result.add_node(def_) result.add_edges_from( list( map(lambda e: (*e, graph.get_edge_data(*e)), map(lambda p: (p, def_), predecessors)))) closure = reduce( lambda acc, definition: _transitive_closure( definition, graph, acc), predecessors, result) self._transitive_closures[def_] = closure return closure
def _validate_edge_constraints(self, node_isomorphism_map: dict, graph: nx.DiGraph, constraints: dict): """ Validate all edge constraints on a subgraph. Arguments: node_isomorphism_map (dict[nodename:str, nodeID:str]): A mapping of node names to node IDs (where name comes from the motif and the ID comes from the haystack graph). graph (nx.DiGraph): The haystack graph constraints (dict[(motif_u, motif_v), dict[operator, value]]): Map of constraints on the MOTIF node names. Returns: bool: If the isomorphism satisfies the edge constraints For example, if constraints = { ("A", "B"): {"weight": { "==": 10 }} } And node_isomorphism_map = { "A": "x", "B": "y", } And haystack contains the edge (x, y) with attribute weight=10, then this function will return True. """ for (motif_U, motif_V), constraint_list in constraints.items(): # Get graph nodes (from this isomorphism) graph_u = node_isomorphism_map[motif_U] graph_v = node_isomorphism_map[motif_V] # Check edge in graph for constraints edge_attrs = graph.get_edge_data(graph_u, graph_v) if not _edge_satisfies_constraints(edge_attrs, constraint_list): # Fail fast return False return True
def _build_paths(g: nx.DiGraph, node: Any, data_lookup: Callable[[Dict[K, V]], V], accumulator: Callable[[V, V], V]): r"""Utility for accumulating the set of metadata along every path from a node. Example: -{f:'a'}-> 2 -{f:'b'}-> 3 / 1 \ -{f:'d'}-> 4 -{f:'e'}-> 5 $ _build_paths(g, node=1, data_lookup=lambda d: d['f'], accumulator a, b: a+b) > { 2: 'a', 3: 'ba', 4: 'd', 5: 'de' } Args: g: The DiGraph in question. node: The source node data_lookup: The lambda which can accept a networkx-style metadata dictionary and extract a particular key (and, optionally, transform it). accumulator: The lambda which can accept two bits of metadata and join them in some arbitrary way. Returns: A map where the keys are nodes which appear below |node| in the graph, and where the values are accumulated results of the list of metadata items extracted by |data_lookup| from each edge along the path from |node| down to that node. """ result = {} for succ in g.successors(node): if succ == node: continue result[succ] = data_lookup(g.get_edge_data(node, succ)) for subnode, d in _build_paths(g, succ, data_lookup, accumulator).items(): result[subnode] = accumulator(d, result[succ]) return result
def find_adapter_chain(adapter_graph: nx.DiGraph) -> tuple[int, int]: """Find a valid chain of adapters & determine how many 1 & 3 joltage deltas are present.""" # NOTE: This approach is super overkill for this problem, but I wanted to stick with the graph # # Since our edges represent a valid adapter connection, we're looking for a Hamiltonian path # here: a path that visits every node exactly once. For this problem, since adapter joltage has # to be ascending, we can check a topological sort and see if they're all connected. n_one_delta = 0 n_three_delta = 0 for check_path in nx.all_topological_sorts(adapter_graph): # First check that the topological sort has yielded a valid path. if not nx.is_path(adapter_graph, check_path): continue # Let's assume there's only one valid path for source, dest in zip(check_path, check_path[1:]): delta = adapter_graph.get_edge_data(source, dest)["delta"] if delta == 1: n_one_delta += 1 elif delta == 3: n_three_delta += 1 return n_one_delta, n_three_delta
def getWeight(graph: nx.DiGraph, orig: str, dest: str, multiplier: float) -> float: """Get the weight of the edge from orig to dest in the graph. This weight is expected to be proportional to the movement between nodes. If the edge doesn't have a weight, 1.0 is assumed and the returned weight is adjusted by the multiplier and any delta_adjustment on the edge. :param graph: A graph with each region as a node and the weights corresponding to the commutes between regions. Edges must contain weight and delta_adjustment attributes (assumed 1.0) :param orig: The vertex people are coming from. :param dest: The vertex people are going to. :param multiplier: Value that will dampen or heighten movements between nodes. :return: The final weight value """ edge = graph.get_edge_data(orig, dest) # The data loaders force weight and delta_adjustment to always be present weight = edge["weight"] delta_adjustment = edge["delta_adjustment"] delta = weight - (weight * multiplier) # The delta_adjustment is applied on the delta. It can either completely cancel any changes (factor = 0.0) or # enable it fully (factor = 1.0). If the movement multiplier doesn't make any changes to the node's movements (ie. # multiplier = 1.0), then the delta_adjustment will have no effect. return weight - (delta * delta_adjustment)
class TransformTree: ''' A feature complete if not particularly optimized implementation of a transform graph. Few allowances are made for thread safety, caching, or enforcing graph structure. ''' def __init__(self, base_frame='world'): self._transforms = DiGraph() self._parents = {} self._paths = {} self._is_changed = False self.base_frame = base_frame def update(self, frame_to, frame_from = None, **kwargs): ''' Update a transform in the tree. Arguments --------- frame_from: hashable object, usually a string (eg 'world'). If left as None it will be set to self.base_frame frame_to: hashable object, usually a string (eg 'mesh_0') Additional kwargs (can be used in combinations) --------- matrix: (4,4) array quaternion: (4) quatenion axis: (3) array angle: float, radians translation: (3) array ''' if frame_from is None: frame_from = self.base_frame matrix = np.eye(4) if 'matrix' in kwargs: # a matrix takes precedence over other options matrix = kwargs['matrix'] elif 'quaternion' in kwargs: matrix = quaternion_matrix(kwargs['quaternion']) elif ('axis' in kwargs) and ('angle' in kwargs): matrix = rotation_matrix(kwargs['angle'], kwargs['axis']) else: raise ValueError('Couldn\'t update transform!') if 'translation' in kwargs: # translation can be used in conjunction with any of the methods of # specifying transforms. In the case a matrix and translation are passed, # we add the translations together rather than picking one. matrix[0:3,3] += kwargs['translation'] if self._transforms.has_edge(frame_from, frame_to): self._transforms.edge[frame_from][frame_to]['matrix'] = matrix self._transforms.edge[frame_from][frame_to]['time'] = time.time() else: # since the connectivity has changed, throw out previously computed # paths through the transform graph so queries compute new shortest paths # we could only throw out transforms that are connected to the new edge, # but this is less bookeeping at the expensive of being slower. self._paths = {} self._transforms.add_edge(frame_from, frame_to, matrix = matrix, time = time.time()) self._is_changed = True def get(self, frame_to, frame_from = None): ''' Get the transform from one frame to another, assuming they are connected in the transform tree. If the frames are not connected a NetworkXNoPath error will be raised. Arguments --------- frame_from: hashable object, usually a string (eg 'world'). If left as None it will be set to self.base_frame frame_to: hashable object, usually a string (eg 'mesh_0') Returns --------- transform: (4,4) homogenous transformation matrix ''' if frame_from is None: frame_from = self.base_frame transform = np.eye(4) path, inverted = self._get_path(frame_from, frame_to) for i in range(len(path) - 1): matrix = self._transforms.get_edge_data(path[i], path[i+1])['matrix'] transform = np.dot(transform, matrix) if inverted: transform = np.linalg.inv(transform) return transform def clear(self): self._transforms = DiGraph() self._paths = {} def _get_path(self, frame_from, frame_to): ''' Find a path between two frames, either from cached paths or from the transform graph. Arguments --------- frame_from: a frame key, usually a string example: 'world' frame_to: a frame key, usually a string example: 'mesh_0' Returns ---------- path: (n) list of frame keys example: ['mesh_finger', 'mesh_hand', 'world'] inverted: boolean flag, whether the path is traversing stored matrices forwards or backwards. ''' try: return self._paths[(frame_from, frame_to)] except KeyError: return self._generate_path(frame_from, frame_to) def _generate_path(self, frame_from, frame_to): ''' Generate a path between two frames. Arguments --------- frame_from: a frame key, usually a string example: 'world' frame_to: a frame key, usually a string example: 'mesh_0' Returns ---------- path: (n) list of frame keys example: ['mesh_finger', 'mesh_hand', 'world'] inverted: boolean flag, whether the path is traversing stored matrices forwards or backwards. ''' try: path = shortest_path(self._transforms, frame_from, frame_to) inverted = False except NetworkXNoPath: path = shortest_path(self._transforms, frame_to, frame_from) inverted = True self._paths[(frame_from, frame_to)] = (path, inverted) return path, inverted