se3.py 9.1 KB

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