se2.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import sympy
  2. import sys
  3. import unittest
  4. import sophus
  5. import functools
  6. class Se2:
  7. """ 2 dimensional group of rigid body transformations """
  8. def __init__(self, so2, t):
  9. """ internally represented by a unit complex number z and a translation
  10. 2-vector """
  11. self.so2 = so2
  12. self.t = t
  13. @staticmethod
  14. def exp(v):
  15. """ exponential map """
  16. theta = v[2]
  17. so2 = sophus.So2.exp(theta)
  18. a = so2.z.imag / theta
  19. b = (1 - so2.z.real) / theta
  20. t = sophus.Vector2(a * v[0] - b * v[1],
  21. b * v[0] + a * v[1])
  22. return Se2(so2, t)
  23. def log(self):
  24. theta = self.so2.log()
  25. halftheta = 0.5 * theta
  26. a = -(halftheta * self.so2.z.imag) / (self.so2.z.real - 1)
  27. V_inv = sympy.Matrix([[a, halftheta],
  28. [-halftheta, a]])
  29. upsilon = V_inv * self.t
  30. return sophus.Vector3(upsilon[0], upsilon[1], theta)
  31. def __repr__(self):
  32. return "Se2: [" + repr(self.so2) + " " + repr(self.t)
  33. @staticmethod
  34. def hat(v):
  35. upsilon = sophus.Vector2(v[0], v[1])
  36. theta = v[2]
  37. return sophus.So2.hat(theta).\
  38. row_join(upsilon).\
  39. col_join(sympy.Matrix.zeros(1, 3))
  40. def matrix(self):
  41. """ returns matrix representation """
  42. R = self.so2.matrix()
  43. return (R.row_join(self.t)).col_join(sympy.Matrix(1, 3, [0, 0, 1]))
  44. def __mul__(self, right):
  45. """ left-multiplication
  46. either rotation concatenation or point-transform """
  47. if isinstance(right, sympy.Matrix):
  48. assert right.shape == (2, 1), right.shape
  49. return self.so2 * right + self.t
  50. elif isinstance(right, Se2):
  51. return Se2(self.so2 * right.so2,
  52. self.t + self.so2 * right.t)
  53. assert False, "unsupported type: {0}".format(type(right))
  54. def __getitem__(self, key):
  55. """ We use the following convention [q0, q1, q2, q3, t0, t1, t2] """
  56. assert (key >= 0 and key < 4)
  57. if key < 2:
  58. return self.so2[key]
  59. else:
  60. return self.t[key - 2]
  61. @staticmethod
  62. def calc_Dx_exp_x(x):
  63. return sympy.Matrix(4, 3, lambda r, c:
  64. sympy.diff(Se2.exp(x)[r], x[c]))
  65. @staticmethod
  66. def Dx_exp_x_at_0():
  67. return sympy.Matrix([[0, 0, 0],
  68. [0, 0, 1],
  69. [1, 0, 0],
  70. [0, 1, 0]])
  71. def calc_Dx_this_mul_exp_x_at_0(self, x):
  72. v = Se2.exp(x)
  73. return sympy.Matrix(4, 3, lambda r, c:
  74. sympy.diff((self * Se2.exp(x))[r], x[c])). \
  75. subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
  76. @staticmethod
  77. def calc_Dx_exp_x_at_0(x):
  78. return Se2.calc_Dx_exp_x(x).subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
  79. @staticmethod
  80. def Dxi_x_matrix(x, i):
  81. if i < 2:
  82. return sophus.So2.Dxi_x_matrix(x, i).\
  83. row_join(sympy.Matrix.zeros(2, 1)).\
  84. col_join(sympy.Matrix.zeros(1, 3))
  85. M = sympy.Matrix.zeros(3, 3)
  86. M[i - 2, 2] = 1
  87. return M
  88. @staticmethod
  89. def calc_Dxi_x_matrix(x, i):
  90. return sympy.Matrix(3, 3, lambda r, c:
  91. sympy.diff(x.matrix()[r, c], x[i]))
  92. @staticmethod
  93. def Dxi_exp_x_matrix(x, i):
  94. T = Se2.exp(x)
  95. Dx_exp_x = Se2.calc_Dx_exp_x(x)
  96. l = [Dx_exp_x[j, i] * Se2.Dxi_x_matrix(T, j) for j in range(0, 4)]
  97. return functools.reduce((lambda a, b: a + b), l)
  98. @staticmethod
  99. def calc_Dxi_exp_x_matrix(x, i):
  100. return sympy.Matrix(3, 3, lambda r, c:
  101. sympy.diff(Se2.exp(x).matrix()[r, c], x[i]))
  102. @staticmethod
  103. def Dxi_exp_x_matrix_at_0(i):
  104. v = sophus.ZeroVector3()
  105. v[i] = 1
  106. return Se2.hat(v)
  107. @staticmethod
  108. def calc_Dxi_exp_x_matrix_at_0(x, i):
  109. return sympy.Matrix(3, 3, lambda r, c:
  110. sympy.diff(Se2.exp(x).matrix()[r, c], x[i])
  111. ).subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
  112. class TestSe2(unittest.TestCase):
  113. def setUp(self):
  114. upsilon0, upsilon1, theta = sympy.symbols(
  115. 'upsilon[0], upsilon[1], theta',
  116. real=True)
  117. x, y = sympy.symbols('c[0] c[1]', real=True)
  118. p0, p1 = sympy.symbols('p0 p1', real=True)
  119. t0, t1 = sympy.symbols('t[0] t[1]', real=True)
  120. self.upsilon_theta = sophus.Vector3(
  121. upsilon0, upsilon1, theta)
  122. self.t = sophus.Vector2(t0, t1)
  123. self.a = Se2(sophus.So2(sophus.Complex(x, y)), self.t)
  124. self.p = sophus.Vector2(p0, p1)
  125. def test_exp_log(self):
  126. for v in [sophus.Vector3(0., 1, 0.5),
  127. sophus.Vector3(0.1, 0.1, 0.1),
  128. sophus.Vector3(0.01, 0.2, 0.03)]:
  129. w = Se2.exp(v).log()
  130. for i in range(0, 3):
  131. self.assertAlmostEqual(v[i], w[i])
  132. def test_matrix(self):
  133. T_foo_bar = Se2.exp(self.upsilon_theta)
  134. Tmat_foo_bar = T_foo_bar.matrix()
  135. point_bar = self.p
  136. p1_foo = T_foo_bar * point_bar
  137. p2_foo = sophus.proj(Tmat_foo_bar * sophus.unproj(point_bar))
  138. self.assertEqual(sympy.simplify(p1_foo - p2_foo),
  139. sophus.ZeroVector2())
  140. def test_derivatives(self):
  141. self.assertEqual(sympy.simplify(
  142. Se2.calc_Dx_exp_x_at_0(self.upsilon_theta) -
  143. Se2.Dx_exp_x_at_0()),
  144. sympy.Matrix.zeros(4, 3))
  145. for i in range(0, 4):
  146. self.assertEqual(sympy.simplify(Se2.calc_Dxi_x_matrix(self.a, i) -
  147. Se2.Dxi_x_matrix(self.a, i)),
  148. sympy.Matrix.zeros(3, 3))
  149. for i in range(0, 3):
  150. self.assertEqual(sympy.simplify(
  151. Se2.Dxi_exp_x_matrix(self.upsilon_theta, i) -
  152. Se2.calc_Dxi_exp_x_matrix(self.upsilon_theta, i)),
  153. sympy.Matrix.zeros(3, 3))
  154. self.assertEqual(sympy.simplify(
  155. Se2.Dxi_exp_x_matrix_at_0(i) -
  156. Se2.calc_Dxi_exp_x_matrix_at_0(self.upsilon_theta, i)),
  157. sympy.Matrix.zeros(3, 3))
  158. def test_codegen(self):
  159. stream = sophus.cse_codegen(Se2.calc_Dx_exp_x(self.upsilon_theta))
  160. filename = "cpp_gencode/Se2_Dx_exp_x.cpp"
  161. # set to true to generate codegen files
  162. if False:
  163. file = open(filename, "w")
  164. for line in stream:
  165. file.write(line)
  166. file.close()
  167. else:
  168. file = open(filename, "r")
  169. file_lines = file.readlines()
  170. for i, line in enumerate(stream):
  171. self.assertEqual(line, file_lines[i])
  172. file.close()
  173. stream.close
  174. stream = sophus.cse_codegen(self.a.calc_Dx_this_mul_exp_x_at_0(
  175. self.upsilon_theta))
  176. filename = "cpp_gencode/Se2_Dx_this_mul_exp_x_at_0.cpp"
  177. # set to true to generate codegen files
  178. if False:
  179. file = open(filename, "w")
  180. for line in stream:
  181. file.write(line)
  182. file.close()
  183. else:
  184. file = open(filename, "r")
  185. file_lines = file.readlines()
  186. for i, line in enumerate(stream):
  187. self.assertEqual(line, file_lines[i])
  188. file.close()
  189. stream.close
  190. if __name__ == '__main__':
  191. unittest.main()