Skip to content

Commit

Permalink
Add approx_equal methods to the BaseEdge, BasePose, and Graph classes (
Browse files Browse the repository at this point in the history
…#38)

* Add approx_equal methods

* Cleanup
  • Loading branch information
JeffLIrion committed Nov 4, 2023
1 parent 61b4890 commit cbce26c
Show file tree
Hide file tree
Showing 7 changed files with 10,756 additions and 6 deletions.
32 changes: 32 additions & 0 deletions graphslam/edge/base_edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import numpy as np

from graphslam.pose.base_pose import BasePose


#: The difference that will be used for numerical differentiation
EPSILON = 1e-6
Expand Down Expand Up @@ -171,3 +173,33 @@ def plot(self, color=''):
"""
raise NotImplementedError

def approx_equal(self, other, tol=1e-6):
"""Check whether two edges are approximately equal.
Parameters
----------
other : BaseEdge
The edge to which we are comparing
tol : float
The tolerance
Returns
-------
bool
Whether the two edges are approximately equal
"""
if len(self.vertex_ids) != len(other.vertex_ids):
return False

if any(v_id1 != v_id2 for v_id1, v_id2 in zip(self.vertex_ids, other.vertex_ids)):
return False

if self.information.shape != other.information.shape or np.linalg.norm(self.information - other.information) / max(np.linalg.norm(self.information), tol) >= tol:
return False

if isinstance(self.estimate, BasePose):
return isinstance(other.estimate, BasePose) and self.estimate.approx_equal(other.estimate, tol)

return not isinstance(other.estimate, BasePose) and np.linalg.norm(self.estimate - other.estimate) / max(np.linalg.norm(self.estimate), tol) < tol
22 changes: 22 additions & 0 deletions graphslam/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,25 @@ def plot(self, vertex_color='r', vertex_marker='o', vertex_markersize=3, edge_co
plt.title(title)

plt.show()

def approx_equal(self, other, tol=1e-6):
"""Check whether two graphs are approximately equal.
Parameters
----------
other : Graph
The graph to which we are comparing
tol : float
The tolerance
Returns
-------
bool
Whether the two graphs are approximately equal
"""
# pylint: disable=protected-access
if len(self._edges) != len(other._edges) or len(self._vertices) != len(other._vertices):
return False

return all(e1.approx_equal(e2, tol) for e1, e2 in zip(self._edges, other._edges)) and all(v1.pose.approx_equal(v2.pose, tol) for v1, v2 in zip(self._vertices, other._vertices))
18 changes: 18 additions & 0 deletions graphslam/pose/base_pose.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ def to_compact(self):
"""
raise NotImplementedError

def approx_equal(self, other, tol=1e-6):
"""Check whether two poses are approximately equal.
Parameters
----------
other : BasePose
The pose to which we are comparing
tol : float
The tolerance
Returns
-------
bool
Whether the two poses are approximately equal
"""
return np.linalg.norm(self.to_array() - other.to_array()) / max(np.linalg.norm(self.to_array()), tol) < tol

# ======================================================================= #
# #
# Properties #
Expand Down
2,711 changes: 2,711 additions & 0 deletions tests/input_INTEL_optimized.g2o

Large diffs are not rendered by default.

7,936 changes: 7,936 additions & 0 deletions tests/parking-garage_optimized.g2o

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions tests/test_base_edge.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,32 @@ def test_plot(self):
with self.assertRaises(NotImplementedError):
e.plot()

def test_approx_equal(self):
"""Test that the ``approx_equal`` method works as expected."""
p1 = PoseR2([1, 2])
p2 = PoseR2([3, 4])
estimate = PoseR2([0, 0])

v1 = Vertex(1, p1)
v2 = Vertex(2, p2)

e1 = EdgeOdometry([1, 2], np.eye(2), estimate, [v1, v2])
e2 = EdgeOdometry([1, 2], np.eye(2), estimate, [v1, v2])

self.assertTrue(e1.approx_equal(e2))

e2.estimate = 123
self.assertFalse(e2.approx_equal(e1))

e2.information = np.eye(1)
self.assertFalse(e1.approx_equal(e2))

e2.vertex_ids = [3, 4]
self.assertFalse(e1.approx_equal(e2))

e2.vertex_ids = [5]
self.assertFalse(e1.approx_equal(e2))


if __name__ == '__main__':
unittest.main()
17 changes: 11 additions & 6 deletions tests/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ def test_plot(self):
with patch("graphslam.graph.plt.show"):
self.g.plot(title="Title")

def test_approx_equal(self):
"""Test that the ``approx_equal`` method returns False when comparing to an empty graph."""
g = Graph([], [])
self.assertFalse(g.approx_equal(self.g))


class TestGraphR3(TestGraphR2):
r"""Tests for the ``Graph`` class with :math:`\mathbb{R}^3` poses.
Expand Down Expand Up @@ -243,12 +248,12 @@ def test_intel(self):

optimized = os.path.join(os.path.dirname(__file__), "input_INTEL_optimized.g2o")

# An assertion so that linting doesn't complain about `optimized` being unused
self.assertEqual(os.path.dirname(__file__), os.path.dirname(optimized))

# Uncomment this line to write the output file
# g.to_g2o(optimized)

g2 = load_g2o_se2(optimized)
self.assertTrue(g.approx_equal(g2))

def test_parking_garage(self):
"""Test for optimizing the parking garage dataset."""
parking_garage = os.path.join(os.path.dirname(__file__), "..", "data", "parking-garage.g2o")
Expand All @@ -258,12 +263,12 @@ def test_parking_garage(self):

optimized = os.path.join(os.path.dirname(__file__), "parking-garage_optimized.g2o")

# An assertion so that linting doesn't complain about `optimized` being unused
self.assertEqual(os.path.dirname(__file__), os.path.dirname(optimized))

# Uncomment this line to write the output file
# g.to_g2o(optimized)

g2 = load_g2o_se3(optimized)
self.assertTrue(g.approx_equal(g2))


if __name__ == '__main__':
unittest.main()

0 comments on commit cbce26c

Please sign in to comment.