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)