HybridCity10000.py
Go to the documentation of this file.
1 """
2 GTSAM Copyright 2010-2019, Georgia Tech Research Corporation,
3 Atlanta, Georgia 30332-0415
4 All Rights Reserved
5 
6 See LICENSE for the license information
7 
8 Script for running hybrid estimator on the City10000 dataset.
9 
10 Author: Varun Agrawal
11 """
12 
13 import argparse
14 import time
15 
16 import numpy as np
17 from gtsam.symbol_shorthand import L, M, X
18 from matplotlib import pyplot as plt
19 
20 import gtsam
21 from gtsam import (BetweenFactorPose2, HybridNonlinearFactor,
22  HybridNonlinearFactorGraph, HybridSmoother, HybridValues,
23  Pose2, PriorFactorPose2, Values)
24 
25 
27  """Parse command line arguments"""
28  parser = argparse.ArgumentParser()
29  parser.add_argument("--data_file",
30  help="The path to the City10000 data file",
31  default="T1_city10000_04.txt")
32  parser.add_argument(
33  "--max_loop_count",
34  "-l",
35  type=int,
36  default=10000,
37  help="The maximum number of loops to run over the dataset")
38  parser.add_argument(
39  "--update_frequency",
40  "-u",
41  type=int,
42  default=3,
43  help="After how many steps to run the smoother update.")
44  parser.add_argument(
45  "--max_num_hypotheses",
46  "-m",
47  type=int,
48  default=10,
49  help="The maximum number of hypotheses to keep at any time.")
50  parser.add_argument(
51  "--plot_hypotheses",
52  "-p",
53  action="store_true",
54  help="Plot all hypotheses. NOTE: This is exponential, use with caution."
55  )
56  return parser.parse_args()
57 
58 
59 # Noise models
60 open_loop_model = gtsam.noiseModel.Diagonal.Sigmas(np.ones(3) * 10)
61 open_loop_constant = open_loop_model.negLogConstant()
62 
64  np.asarray([0.0001, 0.0001, 0.0001]))
65 
67  np.asarray([1.0 / 20.0, 1.0 / 20.0, 1.0 / 100.0]))
68 pose_noise_constant = pose_noise_model.negLogConstant()
69 
70 
72  """Class representing the City10000 dataset."""
73 
74  def __init__(self, filename):
75  self.filename_ = filename
76  try:
77  self.f_ = open(self.filename_, 'r')
78  except OSError:
79  print(f"Failed to open file: {self.filename_}")
80 
81  def __del__(self):
82  self.f_.close()
83 
84  def read_line(self, line: str, delimiter: str = " "):
85  """Read a `line` from the dataset, separated by the `delimiter`."""
86  return line.split(delimiter)
87 
88  def parse_line(self,
89  line: str) -> tuple[list[Pose2], tuple[int, int], bool]:
90  """Parse line from file"""
91  parts = self.read_line(line)
92 
93  key_s = int(parts[1])
94  key_t = int(parts[3])
95 
96  is_ambiguous_loop = bool(int(parts[4]))
97 
98  num_measurements = int(parts[5])
99  pose_array = [Pose2()] * num_measurements
100 
101  for i in range(num_measurements):
102  x = float(parts[6 + 3 * i])
103  y = float(parts[7 + 3 * i])
104  rad = float(parts[8 + 3 * i])
105  pose_array[i] = Pose2(x, y, rad)
106 
107  return pose_array, (key_s, key_t), is_ambiguous_loop
108 
109  def next(self):
110  """Read and parse the next line."""
111  line = self.f_.readline()
112  if line:
113  return self.parse_line(line)
114  else:
115  return None, None, None
116 
117 
118 def plot_all_results(ground_truth,
119  all_results,
120  iters=0,
121  estimate_color=(0.1, 0.1, 0.9, 0.4),
122  estimate_label="Hybrid Factor Graphs",
123  text="",
124  filename="city10000_results.svg"):
125  """Plot the City10000 estimates against the ground truth.
126 
127  Args:
128  ground_truth: The ground truth trajectory as xy values.
129  all_results (List[Tuple(np.ndarray, str)]): All the estimates trajectory as xy values,
130  as well as assginment strings.
131  estimate_color (tuple, optional): The color to use for the graph of estimates.
132  Defaults to (0.1, 0.1, 0.9, 0.4).
133  estimate_label (str, optional): Label for the estimates, used in the legend.
134  Defaults to "Hybrid Factor Graphs".
135  """
136  if len(all_results) == 1:
137  fig, axes = plt.subplots(1, 1)
138  axes = [axes]
139  else:
140  fig, axes = plt.subplots(int(np.ceil(len(all_results) / 2)), 2)
141  axes = axes.flatten()
142 
143  for i, (estimates, s, prob) in enumerate(all_results):
144  ax = axes[i]
145  ax.axis('equal')
146  ax.axis((-75.0, 100.0, -75.0, 75.0))
147 
148  gt = ground_truth[:estimates.shape[0]]
149  ax.plot(gt[:, 0],
150  gt[:, 1],
151  '--',
152  linewidth=1,
153  color=(0.1, 0.7, 0.1, 0.5),
154  label="Ground Truth")
155  ax.plot(estimates[:, 0],
156  estimates[:, 1],
157  '-',
158  linewidth=1,
159  color=estimate_color,
160  label=estimate_label)
161  # ax.legend()
162  ax.set_title(f"P={prob:.3f}\n{s}", fontdict={'fontsize': 10})
163 
164  fig.suptitle(f"After {iters} iterations")
165 
166  num_chunks = int(np.ceil(len(text) / 90))
167  text = "\n".join(text[i * 60:(i + 1) * 60] for i in range(num_chunks))
168  fig.text(0.5,
169  0.015,
170  s=text,
171  wrap=True,
172  horizontalalignment='center',
173  fontsize=12)
174 
175  fig.savefig(filename, format="svg")
176 
177 
179  """Experiment Class"""
180 
181  def __init__(self,
182  filename: str,
183  marginal_threshold: float = 0.9999,
184  max_loop_count: int = 150,
185  update_frequency: int = 3,
186  max_num_hypotheses: int = 10,
187  relinearization_frequency: int = 10,
188  plot_hypotheses: bool = False):
189  self.dataset_ = City10000Dataset(filename)
190  self.max_loop_count = max_loop_count
191  self.update_frequency = update_frequency
192  self.max_num_hypotheses = max_num_hypotheses
193  self.relinearization_frequency = relinearization_frequency
194 
195  self.smoother_ = HybridSmoother(marginal_threshold)
197  self.initial_ = Values()
198 
199  self.plot_hypotheses = plot_hypotheses
200 
201  def hybrid_loop_closure_factor(self, loop_counter, key_s, key_t,
202  measurement: Pose2):
203  """
204  Create a hybrid loop closure factor where
205  0 - loose noise model and 1 - loop noise model.
206  """
207  l = (L(loop_counter), 2)
208  f0 = BetweenFactorPose2(X(key_s), X(key_t), measurement,
209  open_loop_model)
210  f1 = BetweenFactorPose2(X(key_s), X(key_t), measurement,
211  pose_noise_model)
212  factors = [(f0, open_loop_constant), (f1, pose_noise_constant)]
213  mixture_factor = HybridNonlinearFactor(l, factors)
214  return mixture_factor
215 
216  def hybrid_odometry_factor(self, key_s, key_t, m,
217  pose_array) -> HybridNonlinearFactor:
218  """Create hybrid odometry factor with discrete measurement choices."""
219  f0 = BetweenFactorPose2(X(key_s), X(key_t), pose_array[0],
220  pose_noise_model)
221  f1 = BetweenFactorPose2(X(key_s), X(key_t), pose_array[1],
222  pose_noise_model)
223 
224  factors = [(f0, pose_noise_constant), (f1, pose_noise_constant)]
225  mixture_factor = HybridNonlinearFactor(m, factors)
226 
227  return mixture_factor
228 
229  def smoother_update(self, max_num_hypotheses) -> float:
230  """Perform smoother update and optimize the graph."""
231  print(f"Smoother update: {self.new_factors_.size()}")
232  before_update = time.time()
233  self.smoother_.update(self.new_factors_, self.initial_,
234  max_num_hypotheses)
235  self.new_factors_.resize(0)
236  after_update = time.time()
237  return after_update - before_update
238 
239  def reinitialize(self) -> float:
240  """Re-linearize, solve ALL, and re-initialize smoother."""
241  print(f"================= Re-Initialize: {self.smoother_.allFactors().size()}")
242  before_update = time.time()
243  self.smoother_.relinearize()
244  self.initial_ = self.smoother_.linearizationPoint()
245  after_update = time.time()
246  print(f"Took {after_update - before_update} seconds.")
247  return after_update - before_update
248 
249  def run(self):
250  """Run the main experiment with a given max_loop_count."""
251  # Initialize local variables
252  discrete_count = 0
253  index = 0
254  loop_count = 0
255  update_count = 0
256 
257  time_list = [] #list[(int, float)]
258 
259  # Set up initial prior
260  priorPose = Pose2(0, 0, 0)
261  self.initial_.insert(X(0), priorPose)
262  self.new_factors_.push_back(
263  PriorFactorPose2(X(0), priorPose, prior_noise_model))
264 
265  # Initial update
266  update_time = self.smoother_update(self.max_num_hypotheses)
267  smoother_update_times = [] # list[(int, float)]
268  smoother_update_times.append((index, update_time))
269 
270  # Flag to decide whether to run smoother update
271  number_of_hybrid_factors = 0
272 
273  # Start main loop
274  result = Values()
275  start_time = time.time()
276 
277  while index < self.max_loop_count:
278  pose_array, keys, is_ambiguous_loop = self.dataset_.next()
279  if pose_array is None:
280  break
281  key_s = keys[0]
282  key_t = keys[1]
283 
284  num_measurements = len(pose_array)
285 
286  # Take the first one as the initial estimate
287  # odom_pose = pose_array[np.random.choice(num_measurements)]
288  odom_pose = pose_array[0]
289  if key_s == key_t - 1:
290  # Odometry factor
291  if num_measurements > 1:
292  # Add hybrid factor
293  m = (M(discrete_count), num_measurements)
294  mixture_factor = self.hybrid_odometry_factor(
295  key_s, key_t, m, pose_array)
296  self.new_factors_.push_back(mixture_factor)
297 
298  discrete_count += 1
299  number_of_hybrid_factors += 1
300  print(f"mixture_factor: {key_s} {key_t}")
301  else:
302  self.new_factors_.push_back(
303  BetweenFactorPose2(X(key_s), X(key_t), odom_pose,
304  pose_noise_model))
305 
306  # Insert next pose initial guess
307  self.initial_.insert(
308  X(key_t),
309  self.initial_.atPose2(X(key_s)) * odom_pose)
310  else:
311  # Loop closure
312  if is_ambiguous_loop:
313  loop_factor = self.hybrid_loop_closure_factor(
314  loop_count, key_s, key_t, odom_pose)
315 
316  else:
317  loop_factor = BetweenFactorPose2(X(key_s), X(key_t),
318  odom_pose,
319  pose_noise_model)
320 
321  # print loop closure event keys:
322  print(f"Loop closure: {key_s} {key_t}")
323  self.new_factors_.push_back(loop_factor)
324  number_of_hybrid_factors += 1
325  loop_count += 1
326 
327  if number_of_hybrid_factors >= self.update_frequency:
328  update_time = self.smoother_update(self.max_num_hypotheses)
329  smoother_update_times.append((index, update_time))
330  number_of_hybrid_factors = 0
331  update_count += 1
332 
333  if update_count % self.relinearization_frequency == 0:
334  self.reinitialize()
335 
336  # Record timing for odometry edges only
337  if key_s == key_t - 1:
338  cur_time = time.time()
339  time_list.append(cur_time - start_time)
340 
341  # Print some status every 100 steps
342  if index % 100 == 0:
343  print(f"Index: {index}")
344 
345  if len(time_list) != 0:
346  print(f"Accumulate time: {time_list[-1]} seconds")
347 
348  index += 1
349 
350  # Final update
351  update_time = self.smoother_update(self.max_num_hypotheses)
352  smoother_update_times.append((index, update_time))
353 
354  # Final optimize
355  delta = self.smoother_.optimize()
356 
357  result.insert_or_assign(self.initial_.retract(delta.continuous()))
358 
359  print(f"Final error: {self.smoother_.hybridBayesNet().error(delta)}")
360 
361  end_time = time.time()
362  total_time = end_time - start_time
363  print(f"Total time: {total_time} seconds")
364 
365  # self.save_results(result, key_t + 1, time_list)
366 
367  if self.plot_hypotheses:
368  # Get all the discrete values
369  discrete_keys = gtsam.DiscreteKeys()
370  for key in delta.discrete().keys():
371  # TODO Get cardinality from DiscreteFactor
372  discrete_keys.push_back((key, 2))
373  print("plotting all hypotheses")
374  self.plot_all_hypotheses(discrete_keys, key_t + 1, index)
375 
376  def plot_all_hypotheses(self, discrete_keys, num_poses, num_iters=0):
377  """Plot all possible hypotheses."""
378 
379  # Get ground truth
380  gt = np.loadtxt(gtsam.findExampleDataFile("ISAM2_GT_city10000.txt"),
381  delimiter=" ")
382 
383  dkeys = gtsam.DiscreteKeys()
384  for i in range(discrete_keys.size()):
385  key, cardinality = discrete_keys.at(i)
386  if key not in self.smoother_.fixedValues().keys():
387  dkeys.push_back((key, cardinality))
388  fixed_values_str = " ".join(
389  f"{gtsam.DefaultKeyFormatter(k)}:{v}"
390  for k, v in self.smoother_.fixedValues().items())
391 
392  all_assignments = gtsam.cartesianProduct(dkeys)
393 
394  all_results = []
395  for assignment in all_assignments:
396  result = gtsam.Values()
397  gbn = self.smoother_.hybridBayesNet().choose(assignment)
398 
399  # Check to see if the GBN has any nullptrs, if it does it is null overall
400  is_invalid_gbn = False
401  for i in range(gbn.size()):
402  if gbn.at(i) is None:
403  is_invalid_gbn = True
404  break
405  if is_invalid_gbn:
406  continue
407 
408  delta = self.smoother_.hybridBayesNet().optimize(assignment)
409  result.insert_or_assign(self.initial_.retract(delta))
410 
411  poses = np.zeros((num_poses, 3))
412  for i in range(num_poses):
413  pose = result.atPose2(X(i))
414  poses[i] = np.asarray((pose.x(), pose.y(), pose.theta()))
415 
416  assignment_string = " ".join([
417  f"{gtsam.DefaultKeyFormatter(k)}={v}"
418  for k, v in assignment.items()
419  ])
420 
421  conditional = self.smoother_.hybridBayesNet().at(
422  self.smoother_.hybridBayesNet().size() - 1).asDiscrete()
423  discrete_values = self.smoother_.fixedValues()
424  for k, v in assignment.items():
425  discrete_values[k] = v
426 
427  if conditional is None:
428  probability = 1.0
429  else:
430  probability = conditional.evaluate(discrete_values)
431 
432  all_results.append((poses, assignment_string, probability))
433 
434  plot_all_results(gt,
435  all_results,
436  iters=num_iters,
437  text=fixed_values_str,
438  filename=f"city10000_results_{num_iters}.svg")
439 
440  def save_results(self, result, final_key, time_list):
441  """Save results to file."""
442  # Write results to file
443  self.write_result(result, final_key, "Hybrid_City10000.txt")
444 
445  # Write timing info to file
446  self.write_timing_info(time_list=time_list)
447 
448  def write_result(self, result, num_poses, filename="Hybrid_city10000.txt"):
449  """
450  Write the result of optimization to file.
451 
452  Args:
453  result (Values): he Values object with the final result.
454  num_poses (int): The number of poses to write to the file.
455  filename (str): The file name to save the result to.
456  """
457  with open(filename, 'w') as outfile:
458 
459  for i in range(num_poses):
460  out_pose = result.atPose2(X(i))
461  outfile.write(
462  f"{out_pose.x()} {out_pose.y()} {out_pose.theta()}\n")
463 
464  print(f"Output written to {filename}")
465 
467  time_list,
468  time_filename="Hybrid_City10000_time.txt"):
469  """Log all the timing information to a file"""
470 
471  with open(time_filename, 'w') as out_file_time:
472 
473  for acc_time in time_list:
474  out_file_time.write(f"{acc_time}\n")
475 
476  print(f"Output {time_filename} file.")
477 
478 
479 def main():
480  """Main runner"""
481  args = parse_arguments()
482 
483  experiment = Experiment(gtsam.findExampleDataFile(args.data_file),
484  max_loop_count=args.max_loop_count,
485  update_frequency=args.update_frequency,
486  max_num_hypotheses=args.max_num_hypotheses,
487  plot_hypotheses=args.plot_hypotheses)
488  experiment.run()
489 
490 
491 if __name__ == "__main__":
492  main()
gtsam.examples.DogLegOptimizerExample.int
int
Definition: DogLegOptimizerExample.py:111
gtsam.examples.HybridCity10000.City10000Dataset.__del__
def __del__(self)
Definition: HybridCity10000.py:81
relicense.update
def update(text)
Definition: relicense.py:46
gtsam.examples.HybridCity10000.plot_all_results
def plot_all_results(ground_truth, all_results, iters=0, estimate_color=(0.1, 0.1, 0.9, 0.4), estimate_label="Hybrid Factor Graphs", text="", filename="city10000_results.svg")
Definition: HybridCity10000.py:118
gtsam.examples.HybridCity10000.Experiment.hybrid_odometry_factor
HybridNonlinearFactor hybrid_odometry_factor(self, key_s, key_t, m, pose_array)
Definition: HybridCity10000.py:216
resize
v resize(3)
gtsam.examples.HybridCity10000.Experiment.plot_all_hypotheses
def plot_all_hypotheses(self, discrete_keys, num_poses, num_iters=0)
Definition: HybridCity10000.py:376
keys
const KeyVector keys
Definition: testRegularImplicitSchurFactor.cpp:40
gtsam::HybridNonlinearFactorGraph
Definition: HybridNonlinearFactorGraph.h:33
gtsam::optimize
Point3 optimize(const NonlinearFactorGraph &graph, const Values &values, Key landmarkKey)
Definition: triangulation.cpp:177
gtsam.examples.HybridCity10000.Experiment.save_results
def save_results(self, result, final_key, time_list)
Definition: HybridCity10000.py:440
gtsam.examples.HybridCity10000.Experiment.reinitialize
float reinitialize(self)
Definition: HybridCity10000.py:239
gtsam::utils.numerical_derivative.retract
def retract(a, np.ndarray xi)
Definition: numerical_derivative.py:44
gtsam::symbol_shorthand
Definition: inference/Symbol.h:147
gtsam::noiseModel::Diagonal::Sigmas
static shared_ptr Sigmas(const Vector &sigmas, bool smart=true)
Definition: NoiseModel.cpp:283
X
#define X
Definition: icosphere.cpp:20
gtsam::DiscreteKeys
DiscreteKeys is a set of keys that can be assembled using the & operator.
Definition: DiscreteKey.h:41
gtsam.examples.HybridCity10000.Experiment.relinearization_frequency
relinearization_frequency
Definition: HybridCity10000.py:186
gtsam.examples.HybridCity10000.Experiment.run
def run(self)
Definition: HybridCity10000.py:249
PriorFactorPose2
PriorFactor< Pose2 > PriorFactorPose2
Definition: serialization.cpp:37
size
Scalar Scalar int size
Definition: benchVecAdd.cpp:17
gtsam::range
Double_ range(const Point2_ &p, const Point2_ &q)
Definition: slam/expressions.h:30
gtsam.examples.HybridCity10000.Experiment.new_factors_
new_factors_
Definition: HybridCity10000.py:189
gtsam::print
void print(const Matrix &A, const string &s, ostream &stream)
Definition: Matrix.cpp:145
gtsam.examples.HybridCity10000.parse_arguments
def parse_arguments()
Definition: HybridCity10000.py:26
gtsam.examples.HybridCity10000.City10000Dataset.next
def next(self)
Definition: HybridCity10000.py:109
gtsam.examples.HybridCity10000.City10000Dataset.f_
f_
Definition: HybridCity10000.py:77
gtsam.examples.HybridCity10000.Experiment.hybrid_loop_closure_factor
def hybrid_loop_closure_factor(self, loop_counter, key_s, key_t, Pose2 measurement)
Definition: HybridCity10000.py:201
gtsam.examples.HybridCity10000.Experiment.write_result
def write_result(self, result, num_poses, filename="Hybrid_city10000.txt")
Definition: HybridCity10000.py:448
BetweenFactorPose2
BetweenFactor< Pose2 > BetweenFactorPose2
Definition: serialization.cpp:49
gtsam.examples.HybridCity10000.Experiment.smoother_update
float smoother_update(self, max_num_hypotheses)
Definition: HybridCity10000.py:229
L
MatrixXd L
Definition: LLT_example.cpp:6
gtsam.examples.HybridCity10000.Experiment.smoother_
smoother_
Definition: HybridCity10000.py:188
gtsam.examples.HybridCity10000.Experiment.plot_hypotheses
plot_hypotheses
Definition: HybridCity10000.py:192
gtsam.examples.HybridCity10000.Experiment.max_num_hypotheses
max_num_hypotheses
Definition: HybridCity10000.py:185
gtsam.examples.HybridCity10000.Experiment.max_loop_count
max_loop_count
Definition: HybridCity10000.py:183
gtsam::HybridSmoother
Definition: HybridSmoother.h:28
gtsam.examples.HybridCity10000.Experiment.update_frequency
update_frequency
Definition: HybridCity10000.py:184
gtsam::Values
Definition: Values.h:65
gtsam.examples.HybridCity10000.Experiment.write_timing_info
def write_timing_info(self, time_list, time_filename="Hybrid_City10000_time.txt")
Definition: HybridCity10000.py:466
gtsam.examples.HybridCity10000.Experiment.initial_
initial_
Definition: HybridCity10000.py:190
gtsam.examples.HybridCity10000.City10000Dataset.__init__
def __init__(self, filename)
Definition: HybridCity10000.py:74
gtsam.examples.HybridCity10000.City10000Dataset.parse_line
tuple[list[Pose2], tuple[int, int], bool] parse_line(self, str line)
Definition: HybridCity10000.py:88
gtsam.examples.HybridCity10000.Experiment
Definition: HybridCity10000.py:178
gtsam.examples.DogLegOptimizerExample.float
float
Definition: DogLegOptimizerExample.py:113
gtsam.examples.HybridCity10000.City10000Dataset.filename_
filename_
Definition: HybridCity10000.py:75
gtsam::findExampleDataFile
GTSAM_EXPORT std::string findExampleDataFile(const std::string &name)
Definition: dataset.cpp:70
gtsam.examples.HybridCity10000.Experiment.__init__
def __init__(self, str filename, float marginal_threshold=0.9999, int max_loop_count=150, int update_frequency=3, int max_num_hypotheses=10, int relinearization_frequency=10, bool plot_hypotheses=False)
Definition: HybridCity10000.py:181
len
size_t len(handle h)
Get the length of a Python object.
Definition: pytypes.h:2448
gtsam.examples.HybridCity10000.main
def main()
Definition: HybridCity10000.py:479
gtsam::cartesianProduct
std::vector< DiscreteValues > cartesianProduct(const DiscreteKeys &keys)
Free version of CartesianProduct.
Definition: DiscreteValues.h:187
gtsam.examples.HybridCity10000.Experiment.dataset_
dataset_
Definition: HybridCity10000.py:182
insert
A insert(1, 2)=0
choose
static const T & choose(int layout, const T &col, const T &row)
Definition: cxx11_tensor_block_access.cpp:27
gtsam::HybridNonlinearFactor
Implementation of a discrete-conditioned hybrid factor.
Definition: HybridNonlinearFactor.h:58
gtsam.examples.HybridCity10000.City10000Dataset.read_line
def read_line(self, str line, str delimiter=" ")
Definition: HybridCity10000.py:84
gtsam::Pose2
Definition: Pose2.h:39
M
Matrix< RealScalar, Dynamic, Dynamic > M
Definition: bench_gemm.cpp:51
gtsam.examples.HybridCity10000.City10000Dataset
Definition: HybridCity10000.py:71


gtsam
Author(s):
autogenerated on Wed Mar 19 2025 03:01:48