1 from typing 
import NamedTuple
 
    5 from cslam.mac.mac 
import MAC
 
    6 from cslam.mac.utils 
import Edge, weight_graph_lap_from_edge_list
 
   10     """ Inter-robot loop closure edge 
   13     robot0_keyframe_id: int
 
   15     robot1_keyframe_id: int
 
   19         """ Overload the equal operator in order to ignore the weights 
   22             other (EdgeInterRobot): Other edge to compare 
   24         return ((self.
robot0_idrobot0_id == other.robot0_id) 
and 
   28                     (self.
robot0_idrobot0_id == other.robot1_id) 
and 
   30                     (self.
robot1_idrobot1_id == other.robot0_id) 
and 
   43             "frontend.enable_sparsification": 
True,
 
   44             "evaluation.enable_sparsification_comparison": 
False,
 
   49             robot_id (int, optional): ID of the robot 
   50             max_nb_robots (int, optional): number of robots. Defaults to 1. 
   51             max_iters (int, optional): maximum number of iterations. Defaults to 20. 
   52             fixed_weight (float, optional): weight of fixed measurements. Defaults to 1.0. 
   77         """Get a unique key for an edge 
   80             edge (EdgeInterRobot): inter-robot edge 
   83             tuple(int, int, int, int): unique key 
   85         if edge.robot0_id < edge.robot1_id:
 
   86             return (edge.robot0_id, edge.robot0_keyframe_id, edge.robot1_id,
 
   87                     edge.robot1_keyframe_id)
 
   89             return (edge.robot1_id, edge.robot1_keyframe_id, edge.robot0_id,
 
   90                     edge.robot0_keyframe_id)
 
   93         """Replace the weight of an edge 
   96             edge (EdgeInterRobot): inter-robot edge 
   97             weight (float): new weight 
  100             EdgeInterRobot: new edge with new weight 
  103         if type(edge) 
is EdgeInterRobot:
 
  105                                   edge.robot1_id, edge.robot1_keyframe_id,
 
  107         elif type(edge) 
is Edge:
 
  108             return Edge(edge.i, edge.j, weight)
 
  111         """The number of poses should be the maximal edge id known 
  114             edge (EdgeInterRobot): loop closure edge 
  116         self.
nb_posesnb_poses[edge.robot0_id] = max(self.
nb_posesnb_poses[edge.robot0_id],
 
  117                                             edge.robot0_keyframe_id + 1)
 
  118         self.
nb_posesnb_poses[edge.robot1_id] = max(self.
nb_posesnb_poses[edge.robot1_id],
 
  119                                             edge.robot1_keyframe_id + 1)
 
  122         """Maintains a bool for each neighbors to know if we have at least a known link. 
  123         In cases where no initial fixed edge exists, we perform the greedy selection 
  126             fixed_edge (EdgeInterRobot): fixed edge in the graph 
  128         if fixed_edge.robot0_id != fixed_edge.robot1_id:
 
  136             fixed_edges (list(EdgeInterRobot)): edges that are already computed 
  137             candidate_edges (list(EdgeInterRobot)): candidate edges to compute 
  146         for e 
in candidate_edges:
 
  149         for e 
in candidate_edges:
 
  153         """Add an already computed edge to the graph 
  156             edge (EdgeInterRobot): inter-robot edge 
  164         """Add a candidate edge to the graph 
  167             edge (EdgeInterRobot): inter-robot edge 
  179         """Remove candidate edge from the graph 
  182             edges (list(EdgeInterRobot)): inter-robot edges 
  193         """Move candidate edges to fixed.  
  194         Use when candidates are successfully converted into measurements 
  197             edges (list(EdgeInterRobot)): inter-robot edges 
  199         for i 
in range(len(edges)):
 
  206         """Greedy weight initialization 
  209             nb_candidates_to_choose (int): number of edges to choose 
  210             edges (list(Edge)): candidate_edges 
  212         weights = [e.weight 
for e 
in edges]
 
  213         nb_edges = len(weights)
 
  214         w_init = np.zeros(nb_edges)
 
  215         indices = np.argpartition(
 
  216             weights, -nb_candidates_to_choose)[-nb_candidates_to_choose:]
 
  217         w_init[indices] = 1.0
 
  222         """Greedy weight initialization 
  223             Greedy initialization with for the first nb_candidates_to_choose-nb_random 
  227             nb_candidates_to_choose (int): number of edges to choose 
  228             nb_random (int): number of edges to choose randomly 
  229             edges (list(Edge)): candidate_edges 
  231         nb_greedy = nb_candidates_to_choose - nb_random
 
  233         nb_edges = len(edges)
 
  236         max_trials = 2 * nb_random
 
  237         while i < nb_random 
and trial < max_trials:
 
  238             j = int(np.random.rand() * nb_edges)
 
  243         if trial >= max_trials:
 
  248         """Random weight initialization 
  251             nb_candidates_to_choose (int): number of edges to choose 
  253         for e 
in range(len(edges)):
 
  254             edges[e] = self.
replace_weightreplace_weight(edges[e], np.random.rand())
 
  258                                            edges, is_robot_included):
 
  259         """Greedy weight initialization with connection bias 
  260         Prioritize edges that connect unconnected robots. 
  262         nb_candidate_chosen = 0
 
  263         edges_copy = edges.copy()
 
  264         edges_ids_to_select = []
 
  265         rids = [r 
for r 
in is_robot_included.keys() 
if is_robot_included[r]]
 
  270                 for i 
in range(len(edges_copy)):
 
  271                     if edges_copy[i].robot0_id == rid 
or edges_copy[
 
  273                         if edges_copy[i].weight > max_weight:
 
  274                             max_weight = edges_copy[i].weight
 
  276                 if max_edge 
is not None:
 
  277                     edges_ids_to_select.append(max_edge)
 
  279                         edges_copy[max_edge], weight=0.0)
 
  280                     nb_candidate_chosen += 1
 
  282         w_init = np.zeros(len(edges))
 
  283         if nb_candidates_to_choose - nb_candidate_chosen > 0:
 
  285                 nb_candidates_to_choose - nb_candidate_chosen,
 
  286                 self.
rekey_edgesrekey_edges(edges_copy, is_robot_included))
 
  287         for i 
in edges_ids_to_select:
 
  292         """Compute rekey offsets 
  295             is_robot_included dict(int, bool): Indicates if the robot  
  296                                 is connected and in communication range 
  305         previous_nb_poses = 0
 
  307             if is_robot_included[id]:
 
  308                 self.
offsetsoffsets[id] = previous_offset + previous_nb_poses
 
  309                 previous_offset = self.
offsetsoffsets[id]
 
  310                 previous_nb_poses = self.
nb_posesnb_poses[id]
 
  313         """Modify keys (nodes ID) from robot_id+keyframe_id to node_id 
  314         Result example: 3 robots with 10 nodes eachs 
  315         robot 0 nodes id = 1 to 9 
  316         robot 1 nodes id = 10 to 19 
  317         robot 2 nodes id = 20 to 29 
  320             edges (dict(EdgeInterRobot)): inter-robot edges 
  321             is_robot_included dict(int, bool): Indicates if the robot  
  322                                 is connected and in communication range 
  325             list(Edge): edges with keys for MAC problem 
  330             if is_robot_included[e.robot0_id] 
and is_robot_included[
 
  332                 i = self.
offsetsoffsets[e.robot0_id] + e.robot0_keyframe_id
 
  333                 j = self.
offsetsoffsets[e.robot1_id] + e.robot1_keyframe_id
 
  334                 rekeyed_edges.append(Edge(i, j, e.weight))
 
  343             if is_robot_included[e.robot0_id] 
and is_robot_included[
 
  345                 included_edges.append(e)
 
  346         return included_edges
 
  349         """Add odometry edges 
  350         We can infer the odometry edges directly from the number of poses, 
  351         without communication. 
  354             list(Edge): odometry edges 
  357         for i 
in range(len(self.
nb_posesnb_poses)):
 
  358             for k 
in range(self.
nb_posesnb_poses[i] - 1):
 
  365         """Recover IDs from before rekey_edges() 
  368             edges (list(Edge)): rekeyed edges 
  369             is_robot_included (dict(int, bool)): indicates if a robot is included 
  372             list(EdgeInterRobot): edges 
  374         recovered_inter_robot_edges = []
 
  375         for c 
in range(len(edges)):
 
  380                     if is_robot_included[o] 
and edges[c].i >= self.
offsetsoffsets[o]:
 
  382                     if is_robot_included[o] 
and edges[c].j >= self.
offsetsoffsets[o]:
 
  384             robot0_keyframe_id = edges[c].i - self.
offsetsoffsets[robot0_id]
 
  385             robot1_keyframe_id = edges[c].j - self.
offsetsoffsets[robot1_id]
 
  386             recovered_inter_robot_edges.append(
 
  388                                robot1_keyframe_id, edges[c].weight))
 
  389         return recovered_inter_robot_edges
 
  392         """Check if the current graph of potential matches is connected 
  395             is_other_robot_considered: dict(int, bool): indicates which  
  396                             other robots are are to be included  
  399             dict(int, bool): dict indicating if each robot is connected 
  401         is_robot_connected = {}
 
  404                 is_robot_connected[i] = 
True 
  406                 is_robot_connected[i] = 
False 
  408             if is_other_robot_considered[edge.robot0_id]:
 
  409                 is_robot_connected[edge.robot0_id] = 
True 
  410             if is_other_robot_considered[edge.robot1_id]:
 
  411                 is_robot_connected[edge.robot1_id] = 
True 
  413             if is_other_robot_considered[edge.robot0_id]:
 
  414                 is_robot_connected[edge.robot0_id] = 
True 
  415             if is_other_robot_considered[edge.robot1_id]:
 
  416                 is_robot_connected[edge.robot1_id] = 
True 
  417         return is_robot_connected
 
  420         """Check if we have an initial fixed measurement with each robot included 
  421         If not, greedy selection should be used 
  424             is_robot_included dict(bool): indicates if each robot is included 
  429         initial_fixed_measurements_exists = 
True 
  430         for rid 
in is_robot_included:
 
  431             if is_robot_included[rid] 
and (
 
  433                 initial_fixed_measurements_exists = 
False 
  434         return initial_fixed_measurements_exists
 
  437                        nb_candidates_to_choose):
 
  438         """Run the maximalization of algebraic connectivity 
  441             fixed_edges list(EdgeInterRobot): fixed edges 
  442             candidate_edges list(EdgeInterRobot): candidate edges 
  443             w_init (np.array): One-hot selection vector 
  444             nb_candidates_to_choose (int): budger 
  446         mac = MAC(fixed_edges, candidate_edges, self.
total_nb_posestotal_nb_poses)
 
  448         result = w_init.copy()
 
  450         while trial < nb_candidates_to_choose:
 
  452                 result, _, _ = mac.fw_subset(w_init,
 
  453                                              nb_candidates_to_choose,
 
  464                     nb_candidates_to_choose, trial, candidate_edges)
 
  469                           nb_candidates_to_choose,
 
  470                           is_other_robot_considered,
 
  471                           greedy_initialization=True):
 
  472         """Solve algebraic connectivity maximization 
  475             nb_candidates_to_choose (int): number of candidates to choose, 
  476                             related to a computation/communication budget 
  477             is_other_robot_considered: dict(int, bool): indicates which  
  478                             other robots are in communication range  
  479             greedy_initialization: perform greedy initialization based on similarity 
  482             list(EdgeInterRobot): selected edges 
  486             is_other_robot_considered)
 
  492         rekeyed_fixed_edges.extend(self.
fill_odometryfill_odometry())
 
  493         rekeyed_candidate_edges = self.
rekey_edgesrekey_edges(
 
  496         if nb_candidates_to_choose > len(rekeyed_candidate_edges):
 
  497             nb_candidates_to_choose = len(rekeyed_candidate_edges)
 
  499         if len(rekeyed_candidate_edges) > 0:
 
  502             for n 
in range(len(self.
nb_posesnb_poses)):
 
  506             if greedy_initialization:
 
  508                                                     rekeyed_candidate_edges)
 
  511                                                     rekeyed_candidate_edges)
 
  517                                              rekeyed_candidate_edges, w_init,
 
  518                                              nb_candidates_to_choose)
 
  521                     nb_candidates_to_choose,
 
  526             if self.
paramsparams[
"evaluation.enable_sparsification_comparison"]:
 
  528                                                     is_robot_included, w_init,
 
  532                 rekeyed_candidate_edges[i]
 
  533                 for i 
in np.nonzero(result.astype(int))[0]
 
  538                 selected_edges, is_robot_included)
 
  541             return inter_robot_edges
 
  546                                        is_robot_included, greedy_result,
 
  551             rekeyed_candidate_edges[i]
 
  552             for i 
in np.nonzero(greedy_result.astype(int))[0]
 
  553         ], is_robot_included)
 
  555             rekeyed_candidate_edges[i]
 
  556             for i 
in np.nonzero(mac_result.astype(int))[0]
 
  557         ], is_robot_included)
 
  563             match (EdgeInterRobot): potential match 
  565         key = (match.robot0_id, match.robot0_keyframe_id, match.robot1_id,
 
  566                match.robot1_keyframe_id)
 
def add_match(self, match)
 
def check_initial_fixed_measurements_exists(self, is_robot_included)
 
def set_graph(self, fixed_edges, candidate_edges)
 
def check_graph_disconnections(self, is_other_robot_considered)
 
already_considered_matches
 
def add_fixed_edge(self, edge)
 
def greedy_initialization(self, nb_candidates_to_choose, edges)
 
def remove_candidate_edges(self, edges, failed=False)
 
def update_initial_fixed_edge_exists(self, fixed_edge)
 
def pseudo_greedy_initialization(self, nb_candidates_to_choose, nb_random, edges)
 
def __init__(self, robot_id=0, max_nb_robots=1, max_iters=20, fixed_weight=1.0, extra_params={ "frontend.enable_sparsification":True, "evaluation.enable_sparsification_comparison":False, })
 
def run_mac_solver(self, fixed_edges, candidate_edges, w_init, nb_candidates_to_choose)
 
def connection_biased_greedy_selection(self, nb_candidates_to_choose, edges, is_robot_included)
 
def recover_inter_robot_edges(self, edges, is_robot_included)
 
def replace_weight(self, edge, weight)
 
def select_candidates(self, nb_candidates_to_choose, is_other_robot_considered, greedy_initialization=True)
 
def sparsification_comparison_logs(self, rekeyed_candidate_edges, is_robot_included, greedy_result, mac_result)
 
def update_nb_poses(self, edge)
 
def random_initialization(self, nb_candidates_to_choose, edges)
 
def get_included_edges(self, edges, is_robot_included)
 
def add_candidate_edge(self, edge)
 
def rekey_edges(self, edges, is_robot_included)
 
initial_fixed_edge_exists
 
def candidate_edges_to_fixed(self, edges)
 
def compute_offsets(self, is_robot_included)