225 lines
7.4 KiB
Python
225 lines
7.4 KiB
Python
|
import sympy
|
||
|
import sys
|
||
|
import unittest
|
||
|
import sophus
|
||
|
import functools
|
||
|
|
||
|
|
||
|
class Se2:
|
||
|
""" 2 dimensional group of rigid body transformations """
|
||
|
|
||
|
def __init__(self, so2, t):
|
||
|
""" internally represented by a unit complex number z and a translation
|
||
|
2-vector """
|
||
|
self.so2 = so2
|
||
|
self.t = t
|
||
|
|
||
|
@staticmethod
|
||
|
def exp(v):
|
||
|
""" exponential map """
|
||
|
theta = v[2]
|
||
|
so2 = sophus.So2.exp(theta)
|
||
|
|
||
|
a = so2.z.imag / theta
|
||
|
b = (1 - so2.z.real) / theta
|
||
|
|
||
|
t = sophus.Vector2(a * v[0] - b * v[1],
|
||
|
b * v[0] + a * v[1])
|
||
|
return Se2(so2, t)
|
||
|
|
||
|
def log(self):
|
||
|
theta = self.so2.log()
|
||
|
halftheta = 0.5 * theta
|
||
|
a = -(halftheta * self.so2.z.imag) / (self.so2.z.real - 1)
|
||
|
|
||
|
V_inv = sympy.Matrix([[a, halftheta],
|
||
|
[-halftheta, a]])
|
||
|
upsilon = V_inv * self.t
|
||
|
return sophus.Vector3(upsilon[0], upsilon[1], theta)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return "Se2: [" + repr(self.so2) + " " + repr(self.t)
|
||
|
|
||
|
@staticmethod
|
||
|
def hat(v):
|
||
|
upsilon = sophus.Vector2(v[0], v[1])
|
||
|
theta = v[2]
|
||
|
return sophus.So2.hat(theta).\
|
||
|
row_join(upsilon).\
|
||
|
col_join(sympy.Matrix.zeros(1, 3))
|
||
|
|
||
|
def matrix(self):
|
||
|
""" returns matrix representation """
|
||
|
R = self.so2.matrix()
|
||
|
return (R.row_join(self.t)).col_join(sympy.Matrix(1, 3, [0, 0, 1]))
|
||
|
|
||
|
def __mul__(self, right):
|
||
|
""" left-multiplication
|
||
|
either rotation concatenation or point-transform """
|
||
|
if isinstance(right, sympy.Matrix):
|
||
|
assert right.shape == (2, 1), right.shape
|
||
|
return self.so2 * right + self.t
|
||
|
elif isinstance(right, Se2):
|
||
|
return Se2(self.so2 * right.so2,
|
||
|
self.t + self.so2 * right.t)
|
||
|
assert False, "unsupported type: {0}".format(type(right))
|
||
|
|
||
|
def __getitem__(self, key):
|
||
|
""" We use the following convention [q0, q1, q2, q3, t0, t1, t2] """
|
||
|
assert (key >= 0 and key < 4)
|
||
|
if key < 2:
|
||
|
return self.so2[key]
|
||
|
else:
|
||
|
return self.t[key - 2]
|
||
|
|
||
|
@staticmethod
|
||
|
def calc_Dx_exp_x(x):
|
||
|
return sympy.Matrix(4, 3, lambda r, c:
|
||
|
sympy.diff(Se2.exp(x)[r], x[c]))
|
||
|
|
||
|
@staticmethod
|
||
|
def Dx_exp_x_at_0():
|
||
|
return sympy.Matrix([[0, 0, 0],
|
||
|
[0, 0, 1],
|
||
|
[1, 0, 0],
|
||
|
[0, 1, 0]])
|
||
|
|
||
|
def calc_Dx_this_mul_exp_x_at_0(self, x):
|
||
|
v = Se2.exp(x)
|
||
|
return sympy.Matrix(4, 3, lambda r, c:
|
||
|
sympy.diff((self * Se2.exp(x))[r], x[c])). \
|
||
|
subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
|
||
|
|
||
|
@staticmethod
|
||
|
def calc_Dx_exp_x_at_0(x):
|
||
|
return Se2.calc_Dx_exp_x(x).subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
|
||
|
|
||
|
@staticmethod
|
||
|
def Dxi_x_matrix(x, i):
|
||
|
if i < 2:
|
||
|
return sophus.So2.Dxi_x_matrix(x, i).\
|
||
|
row_join(sympy.Matrix.zeros(2, 1)).\
|
||
|
col_join(sympy.Matrix.zeros(1, 3))
|
||
|
M = sympy.Matrix.zeros(3, 3)
|
||
|
M[i - 2, 2] = 1
|
||
|
return M
|
||
|
|
||
|
@staticmethod
|
||
|
def calc_Dxi_x_matrix(x, i):
|
||
|
return sympy.Matrix(3, 3, lambda r, c:
|
||
|
sympy.diff(x.matrix()[r, c], x[i]))
|
||
|
|
||
|
@staticmethod
|
||
|
def Dxi_exp_x_matrix(x, i):
|
||
|
T = Se2.exp(x)
|
||
|
Dx_exp_x = Se2.calc_Dx_exp_x(x)
|
||
|
l = [Dx_exp_x[j, i] * Se2.Dxi_x_matrix(T, j) for j in range(0, 4)]
|
||
|
return functools.reduce((lambda a, b: a + b), l)
|
||
|
|
||
|
@staticmethod
|
||
|
def calc_Dxi_exp_x_matrix(x, i):
|
||
|
return sympy.Matrix(3, 3, lambda r, c:
|
||
|
sympy.diff(Se2.exp(x).matrix()[r, c], x[i]))
|
||
|
|
||
|
@staticmethod
|
||
|
def Dxi_exp_x_matrix_at_0(i):
|
||
|
v = sophus.ZeroVector3()
|
||
|
v[i] = 1
|
||
|
return Se2.hat(v)
|
||
|
|
||
|
@staticmethod
|
||
|
def calc_Dxi_exp_x_matrix_at_0(x, i):
|
||
|
return sympy.Matrix(3, 3, lambda r, c:
|
||
|
sympy.diff(Se2.exp(x).matrix()[r, c], x[i])
|
||
|
).subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
|
||
|
|
||
|
|
||
|
class TestSe2(unittest.TestCase):
|
||
|
def setUp(self):
|
||
|
upsilon0, upsilon1, theta = sympy.symbols(
|
||
|
'upsilon[0], upsilon[1], theta',
|
||
|
real=True)
|
||
|
x, y = sympy.symbols('c[0] c[1]', real=True)
|
||
|
p0, p1 = sympy.symbols('p0 p1', real=True)
|
||
|
t0, t1 = sympy.symbols('t[0] t[1]', real=True)
|
||
|
self.upsilon_theta = sophus.Vector3(
|
||
|
upsilon0, upsilon1, theta)
|
||
|
self.t = sophus.Vector2(t0, t1)
|
||
|
self.a = Se2(sophus.So2(sophus.Complex(x, y)), self.t)
|
||
|
self.p = sophus.Vector2(p0, p1)
|
||
|
|
||
|
def test_exp_log(self):
|
||
|
for v in [sophus.Vector3(0., 1, 0.5),
|
||
|
sophus.Vector3(0.1, 0.1, 0.1),
|
||
|
sophus.Vector3(0.01, 0.2, 0.03)]:
|
||
|
w = Se2.exp(v).log()
|
||
|
for i in range(0, 3):
|
||
|
self.assertAlmostEqual(v[i], w[i])
|
||
|
|
||
|
def test_matrix(self):
|
||
|
T_foo_bar = Se2.exp(self.upsilon_theta)
|
||
|
Tmat_foo_bar = T_foo_bar.matrix()
|
||
|
point_bar = self.p
|
||
|
p1_foo = T_foo_bar * point_bar
|
||
|
p2_foo = sophus.proj(Tmat_foo_bar * sophus.unproj(point_bar))
|
||
|
self.assertEqual(sympy.simplify(p1_foo - p2_foo),
|
||
|
sophus.ZeroVector2())
|
||
|
|
||
|
def test_derivatives(self):
|
||
|
self.assertEqual(sympy.simplify(
|
||
|
Se2.calc_Dx_exp_x_at_0(self.upsilon_theta) -
|
||
|
Se2.Dx_exp_x_at_0()),
|
||
|
sympy.Matrix.zeros(4, 3))
|
||
|
for i in range(0, 4):
|
||
|
self.assertEqual(sympy.simplify(Se2.calc_Dxi_x_matrix(self.a, i) -
|
||
|
Se2.Dxi_x_matrix(self.a, i)),
|
||
|
sympy.Matrix.zeros(3, 3))
|
||
|
for i in range(0, 3):
|
||
|
self.assertEqual(sympy.simplify(
|
||
|
Se2.Dxi_exp_x_matrix(self.upsilon_theta, i) -
|
||
|
Se2.calc_Dxi_exp_x_matrix(self.upsilon_theta, i)),
|
||
|
sympy.Matrix.zeros(3, 3))
|
||
|
self.assertEqual(sympy.simplify(
|
||
|
Se2.Dxi_exp_x_matrix_at_0(i) -
|
||
|
Se2.calc_Dxi_exp_x_matrix_at_0(self.upsilon_theta, i)),
|
||
|
sympy.Matrix.zeros(3, 3))
|
||
|
|
||
|
def test_codegen(self):
|
||
|
stream = sophus.cse_codegen(Se2.calc_Dx_exp_x(self.upsilon_theta))
|
||
|
filename = "cpp_gencode/Se2_Dx_exp_x.cpp"
|
||
|
|
||
|
# set to true to generate codegen files
|
||
|
if False:
|
||
|
file = open(filename, "w")
|
||
|
for line in stream:
|
||
|
file.write(line)
|
||
|
file.close()
|
||
|
else:
|
||
|
file = open(filename, "r")
|
||
|
file_lines = file.readlines()
|
||
|
for i, line in enumerate(stream):
|
||
|
self.assertEqual(line, file_lines[i])
|
||
|
file.close()
|
||
|
stream.close
|
||
|
|
||
|
stream = sophus.cse_codegen(self.a.calc_Dx_this_mul_exp_x_at_0(
|
||
|
self.upsilon_theta))
|
||
|
filename = "cpp_gencode/Se2_Dx_this_mul_exp_x_at_0.cpp"
|
||
|
# set to true to generate codegen files
|
||
|
if False:
|
||
|
file = open(filename, "w")
|
||
|
for line in stream:
|
||
|
file.write(line)
|
||
|
file.close()
|
||
|
else:
|
||
|
file = open(filename, "r")
|
||
|
file_lines = file.readlines()
|
||
|
for i, line in enumerate(stream):
|
||
|
self.assertEqual(line, file_lines[i])
|
||
|
file.close()
|
||
|
stream.close
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
unittest.main()
|