123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- import sympy
- import sys
- import unittest
- import sophus
- import functools
- class So3:
- """ 3 dimensional group of orthogonal matrices with determinant 1 """
- def __init__(self, q):
- """ internally represented by a unit quaternion q """
- self.q = q
- @staticmethod
- def exp(v):
- """ exponential map """
- theta_sq = sophus.squared_norm(v)
- theta = sympy.sqrt(theta_sq)
- return So3(
- sophus.Quaternion(
- sympy.cos(0.5 * theta),
- sympy.sin(0.5 * theta) / theta * v))
- def log(self):
- """ logarithmic map"""
- n = sympy.sqrt(sophus.squared_norm(self.q.vec))
- return 2 * sympy.atan(n / self.q.real) / n * self.q.vec
- def __repr__(self):
- return "So3:" + repr(self.q)
- def inverse(self):
- return So3(self.q.conj())
- @staticmethod
- def hat(o):
- return sympy.Matrix([[0, -o[2], o[1]],
- [o[2], 0, -o[0]],
- [-o[1], o[0], 0]])
-
- """vee-operator
-
- It takes the 3x3-matrix representation ``Omega`` and maps it to the
- corresponding vector representation of Lie algebra.
-
- This is the inverse of the hat-operator, see above.
-
- Precondition: ``Omega`` must have the following structure:
-
- | 0 -c b |
- | c 0 -a |
- | -b a 0 |
- """
- @staticmethod
- def vee(Omega):
- v = sophus.Vector3(Omega.row(2).col(1), Omega.row(0).col(2), Omega.row(1).col(0))
- return v
- def matrix(self):
- """ returns matrix representation """
- return sympy.Matrix([[
- 1 - 2 * self.q.vec[1]**2 - 2 * self.q.vec[2]**2,
- 2 * self.q.vec[0] * self.q.vec[1] -
- 2 * self.q.vec[2] * self.q[3],
- 2 * self.q.vec[0] * self.q.vec[2] +
- 2 * self.q.vec[1] * self.q[3]
- ], [
- 2 * self.q.vec[0] * self.q.vec[1] +
- 2 * self.q.vec[2] * self.q[3],
- 1 - 2 * self.q.vec[0]**2 - 2 * self.q.vec[2]**2,
- 2 * self.q.vec[1] * self.q.vec[2] -
- 2 * self.q.vec[0] * self.q[3]
- ], [
- 2 * self.q.vec[0] * self.q.vec[2] -
- 2 * self.q.vec[1] * self.q[3],
- 2 * self.q.vec[1] * self.q.vec[2] +
- 2 * self.q.vec[0] * self.q[3],
- 1 - 2 * self.q.vec[0]**2 - 2 * self.q.vec[1]**2
- ]])
- def __mul__(self, right):
- """ left-multiplication
- either rotation concatenation or point-transform """
- if isinstance(right, sympy.Matrix):
- assert right.shape == (3, 1), right.shape
- return (self.q * sophus.Quaternion(0, right) * self.q.conj()).vec
- elif isinstance(right, So3):
- return So3(self.q * right.q)
- assert False, "unsupported type: {0}".format(type(right))
- def __getitem__(self, key):
- return self.q[key]
- @staticmethod
- def calc_Dx_exp_x(x):
- return sympy.Matrix(4, 3, lambda r, c:
- sympy.diff(So3.exp(x)[r], x[c]))
- @staticmethod
- def Dx_exp_x_at_0():
- return sympy.Matrix([[0.5, 0.0, 0.0],
- [0.0, 0.5, 0.0],
- [0.0, 0.0, 0.5],
- [0.0, 0.0, 0.0]])
- @staticmethod
- def calc_Dx_exp_x_at_0(x):
- return So3.calc_Dx_exp_x(x).subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
- def calc_Dx_this_mul_exp_x_at_0(self, x):
- return sympy.Matrix(4, 3, lambda r, c:
- sympy.diff((self * So3.exp(x))[r], x[c]))\
- .subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
- def calc_Dx_exp_x_mul_this_at_0(self, x):
- return sympy.Matrix(3, 4, lambda r, c:
- sympy.diff((self * So3.exp(x))[c], x[r, 0]))\
- .subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
- @staticmethod
- def Dxi_x_matrix(x, i):
- if i == 0:
- return sympy.Matrix([[0, 2 * x[1], 2 * x[2]],
- [2 * x[1], -4 * x[0], -2 * x[3]],
- [2 * x[2], 2 * x[3], -4 * x[0]]])
- if i == 1:
- return sympy.Matrix([[-4 * x[1], 2 * x[0], 2 * x[3]],
- [2 * x[0], 0, 2 * x[2]],
- [-2 * x[3], 2 * x[2], -4 * x[1]]])
- if i == 2:
- return sympy.Matrix([[-4 * x[2], -2 * x[3], 2 * x[0]],
- [2 * x[3], -4 * x[2], 2 * x[1]],
- [2 * x[0], 2 * x[1], 0]])
- if i == 3:
- return sympy.Matrix([[0, -2 * x[2], 2 * x[1]],
- [2 * x[2], 0, -2 * x[0]],
- [-2 * x[1], 2 * x[0], 0]])
- @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):
- R = So3.exp(x)
- Dx_exp_x = So3.calc_Dx_exp_x(x)
- l = [Dx_exp_x[j, i] * So3.Dxi_x_matrix(R, j) for j in [0, 1, 2, 3]]
- 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(So3.exp(x).matrix()[r, c], x[i]))
- @staticmethod
- def Dxi_exp_x_matrix_at_0(i):
- v = sophus.ZeroVector3()
- v[i] = 1
- return So3.hat(v)
- @staticmethod
- def calc_Dxi_exp_x_matrix_at_0(x, i):
- return sympy.Matrix(3, 3, lambda r, c:
- sympy.diff(So3.exp(x).matrix()[r, c], x[i])
- ).subs(x[0], 0).subs(x[1], 0).limit(x[2], 0)
- class TestSo3(unittest.TestCase):
- def setUp(self):
- omega0, omega1, omega2 = sympy.symbols(
- 'omega[0], omega[1], omega[2]', real=True)
- x, v0, v1, v2 = sympy.symbols('q.w() q.x() q.y() q.z()', real=True)
- p0, p1, p2 = sympy.symbols('p0 p1 p2', real=True)
- v = sophus.Vector3(v0, v1, v2)
- self.omega = sophus.Vector3(omega0, omega1, omega2)
- self.a = So3(sophus.Quaternion(x, v))
- self.p = sophus.Vector3(p0, p1, p2)
- def test_exp_log(self):
- for o in [sophus.Vector3(0., 1, 0.5),
- sophus.Vector3(0.1, 0.1, 0.1),
- sophus.Vector3(0.01, 0.2, 0.03)]:
- w = So3.exp(o).log()
- for i in range(0, 3):
- self.assertAlmostEqual(o[i], w[i])
- def test_matrix(self):
- R_foo_bar = So3.exp(self.omega)
- Rmat_foo_bar = R_foo_bar.matrix()
- point_bar = self.p
- p1_foo = R_foo_bar * point_bar
- p2_foo = Rmat_foo_bar * point_bar
- self.assertEqual(sympy.simplify(p1_foo - p2_foo),
- sophus.ZeroVector3())
- def test_derivatives(self):
- self.assertEqual(sympy.simplify(So3.calc_Dx_exp_x_at_0(self.omega) -
- So3.Dx_exp_x_at_0()),
- sympy.Matrix.zeros(4, 3))
- for i in [0, 1, 2, 3]:
- self.assertEqual(sympy.simplify(So3.calc_Dxi_x_matrix(self.a, i) -
- So3.Dxi_x_matrix(self.a, i)),
- sympy.Matrix.zeros(3, 3))
- for i in [0, 1, 2]:
- self.assertEqual(sympy.simplify(
- So3.Dxi_exp_x_matrix(self.omega, i) -
- So3.calc_Dxi_exp_x_matrix(self.omega, i)),
- sympy.Matrix.zeros(3, 3))
- self.assertEqual(sympy.simplify(
- So3.Dxi_exp_x_matrix_at_0(i) -
- So3.calc_Dxi_exp_x_matrix_at_0(self.omega, i)),
- sympy.Matrix.zeros(3, 3))
- def test_codegen(self):
- stream = sophus.cse_codegen(So3.calc_Dx_exp_x(self.omega))
- filename = "cpp_gencode/So3_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.omega))
- filename = "cpp_gencode/So3_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()
|