2 This file is part of the pyquaternion python module 5 Website: https://github.com/KieranWynn/pyquaternion 6 Documentation: http://kieranwynn.github.io/pyquaternion/ 9 License: The MIT License (MIT) 11 Copyright (c) 2015 Kieran Wynn 13 Permission is hereby granted, free of charge, to any person obtaining a copy 14 of this software and associated documentation files (the "Software"), to deal 15 in the Software without restriction, including without limitation the rights 16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 copies of the Software, and to permit persons to whom the Software is 18 furnished to do so, subject to the following conditions: 20 The above copyright notice and this permission notice shall be included in all 21 copies or substantial portions of the Software. 23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 quaternion.py - This file defines the core Quaternion class 35 from __future__
import absolute_import, division, print_function
37 from math
import sqrt, pi, sin, cos, asin, acos, atan2, exp, log
38 from copy
import deepcopy
43 """Class to represent a 4-dimensional complex number or quaternion. 45 Quaternion objects can be used generically as 4D numbers, 46 or as unit quaternions to represent rotations in 3D space. 49 q: Quaternion 4-vector represented as a Numpy array 54 """Initialise a new Quaternion object. 56 See Object Initialisation docs for complete behaviour: 58 http://kieranwynn.github.io/pyquaternion/initialisation/ 66 if (
"scalar" in kwargs)
or (
"vector" in kwargs):
67 scalar = kwargs.get(
"scalar", 0.0)
71 scalar = float(scalar)
73 vector = kwargs.get(
"vector", [])
76 self.
q = np.hstack((scalar, vector))
77 elif (
"real" in kwargs)
or (
"imaginary" in kwargs):
78 real = kwargs.get(
"real", 0.0)
84 imaginary = kwargs.get(
"imaginary", [])
87 self.
q = np.hstack((real, imaginary))
88 elif (
"axis" in kwargs)
or (
"radians" in kwargs)
or (
"degrees" in kwargs)
or (
"angle" in kwargs):
93 "A valid rotation 'axis' parameter must be provided to describe a meaningful rotation." 95 angle = kwargs.get(
'radians')
or self.
to_radians(kwargs.get(
'degrees'))
or kwargs.get(
'angle')
or 0.0
96 self.
q = Quaternion._from_axis_angle(axis, angle).q
97 elif "array" in kwargs:
99 elif "matrix" in kwargs:
100 optional_args = {key: kwargs[key]
for key
in kwargs
if key
in [
'rtol',
'atol']}
101 self.
q = Quaternion._from_matrix(kwargs[
"matrix"], **optional_args).q
103 keys = sorted(kwargs.keys())
104 elements = [kwargs[kw]
for kw
in keys]
105 if len(elements) == 1:
106 r = float(elements[0])
107 self.
q = np.array([r, 0.0, 0.0, 0.0])
113 self.
q = np.array([1.0, 0.0, 0.0, 0.0])
116 if isinstance(args[0], Quaternion):
120 raise TypeError(
"Object cannot be initialised from {}".format(type(args[0])))
123 self.
q = np.array([r, 0.0, 0.0, 0.0])
136 return hash(tuple(self.
q))
139 """Validate a sequence to be of a certain length and ensure it's a numpy array of floats. 142 ValueError: Invalid length or non-numeric value 148 l = [float(e)
for e
in seq]
150 raise ValueError(
"One or more elements in sequence <{!r}> cannot be interpreted as a real number".format(seq))
156 raise ValueError(
"Unexpected number of elements in sequence. Got: {}, Expected: {}.".format(len(seq), n))
161 """Initialise from matrix representation 163 Create a Quaternion by specifying the 3x3 rotation or 4x4 transformation matrix 164 (as a numpy array) from which the quaternion's rotation should be created. 169 except AttributeError:
170 raise TypeError(
"Invalid matrix type: Input must be a 3x3 or 4x4 numpy array or matrix")
174 elif shape == (4, 4):
175 R = matrix[:-1][:,:-1]
177 raise ValueError(
"Invalid matrix shape: Input must be a 3x3 or 4x4 numpy array or matrix")
180 if not np.allclose(np.dot(R, R.conj().transpose()), np.eye(3), rtol=rtol, atol=atol):
181 raise ValueError(
"Matrix must be orthogonal, i.e. its transpose should be its inverse")
182 if not np.isclose(np.linalg.det(R), 1.0, rtol=rtol, atol=atol):
183 raise ValueError(
"Matrix must be special orthogonal i.e. its determinant must be +1.0")
185 def decomposition_method(matrix):
186 """ Method supposedly able to deal with non-orthogonal matrices - NON-FUNCTIONAL! 187 Based on this method: http://arc.aiaa.org/doi/abs/10.2514/2.4654 191 [R[x, x]-R[y, y]-R[z, z], R[y, x]+R[x, y], R[z, x]+R[x, z], R[y, z]-R[z, y]],
192 [R[y, x]+R[x, y], R[y, y]-R[x, x]-R[z, z], R[z, y]+R[y, z], R[z, x]-R[x, z]],
193 [R[z, x]+R[x, z], R[z, y]+R[y, z], R[z, z]-R[x, x]-R[y, y], R[x, y]-R[y, x]],
194 [R[y, z]-R[z, y], R[z, x]-R[x, z], R[x, y]-R[y, x], R[x, x]+R[y, y]+R[z, z]]
198 e_vals, e_vecs = np.linalg.eig(K)
199 print(
'Eigenvalues:', e_vals)
200 print(
'Eigenvectors:', e_vecs)
201 max_index = np.argmax(e_vals)
202 principal_component = e_vecs[max_index]
203 return principal_component
205 def trace_method(matrix):
207 This code uses a modification of the algorithm described in: 208 https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2015/01/matrix-to-quat.pdf 209 which is itself based on the method described here: 210 http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ 212 Altered to work with the column vector convention instead of row vectors 214 m = matrix.conj().transpose()
216 if m[0, 0] > m[1, 1]:
217 t = 1 + m[0, 0] - m[1, 1] - m[2, 2]
218 q = [m[1, 2]-m[2, 1], t, m[0, 1]+m[1, 0], m[2, 0]+m[0, 2]]
220 t = 1 - m[0, 0] + m[1, 1] - m[2, 2]
221 q = [m[2, 0]-m[0, 2], m[0, 1]+m[1, 0], t, m[1, 2]+m[2, 1]]
223 if m[0, 0] < -m[1, 1]:
224 t = 1 - m[0, 0] - m[1, 1] + m[2, 2]
225 q = [m[0, 1]-m[1, 0], m[2, 0]+m[0, 2], m[1, 2]+m[2, 1], t]
227 t = 1 + m[0, 0] + m[1, 1] + m[2, 2]
228 q = [t, m[1, 2]-m[2, 1], m[2, 0]-m[0, 2], m[0, 1]-m[1, 0]]
230 q = np.array(q).astype(
'float64')
234 return cls(array=trace_method(R))
239 """Initialise from axis and angle representation 241 Create a Quaternion by specifying the 3-vector rotation axis and rotation 242 angle (in radians) from which the quaternion's rotation should be created. 245 axis: a valid numpy 3-vector 246 angle: a real valued angle in radians 248 mag_sq = np.dot(axis, axis)
250 raise ZeroDivisionError(
"Provided rotation axis has no length")
252 if (abs(1.0 - mag_sq) > 1e-12):
253 axis = axis / sqrt(mag_sq)
256 i = axis * sin(theta)
258 return cls(r, i[0], i[1], i[2])
262 """Generate a random unit quaternion. 264 Uniformly distributed across the rotation space 265 As per: http://planning.cs.uiuc.edu/node198.html 267 r1, r2, r3 = np.random.random(3)
269 q1 = sqrt(1.0 - r1) * (sin(2 * pi * r2))
270 q2 = sqrt(1.0 - r1) * (cos(2 * pi * r2))
271 q3 = sqrt(r1) * (sin(2 * pi * r3))
272 q4 = sqrt(r1) * (cos(2 * pi * r3))
274 return cls(q1, q2, q3, q4)
278 """An informal, nicely printable string representation of the Quaternion object. 280 return "{:.3f} {:+.3f}i {:+.3f}j {:+.3f}k".format(self.
q[0], self.
q[1], self.
q[2], self.
q[3])
283 """The 'official' string representation of the Quaternion object. 285 This is a string representation of a valid Python expression that could be used 286 to recreate an object with the same value (given an appropriate environment) 288 return "Quaternion({!r}, {!r}, {!r}, {!r})".format(self.
q[0], self.
q[1], self.
q[2], self.
q[3])
291 """Inserts a customisable, nicely printable string representation of the Quaternion object 293 The syntax for `format_spec` mirrors that of the built in format specifiers for floating point types. 294 Check out the official Python [format specification mini-language](https://docs.python.org/3.4/library/string.html#formatspec) for details. 296 if formatstr.strip() ==
'':
300 "{:" + formatstr +
"} " + \
301 "{:" + formatstr +
"}i " + \
302 "{:" + formatstr +
"}j " + \
303 "{:" + formatstr +
"}k" 304 return string.format(self.
q[0], self.
q[1], self.
q[2], self.
q[3])
308 """Implements type conversion to int. 310 Truncates the Quaternion object by only considering the real 311 component and rounding to the next integer value towards zero. 312 Note: to round to the closest integer, use int(round(float(q))) 314 return int(self.
q[0])
317 """Implements type conversion to float. 319 Truncates the Quaternion object by only considering the real 322 return float(self.
q[0])
325 """Implements type conversion to complex. 327 Truncates the Quaternion object by only considering the real 328 component and the first imaginary component. 329 This is equivalent to a projection from the 4-dimensional hypersphere 330 to the 2-dimensional complex plane. 332 return complex(self.
q[0], self.
q[1])
345 """Returns true if the following is true for each element: 346 `absolute(a - b) <= (atol + rtol * absolute(b))` 348 if isinstance(other, Quaternion):
352 isEqual = np.allclose(self.
q, other.q, rtol=r_tol, atol=a_tol)
353 except AttributeError:
354 raise AttributeError(
"Error in internal quaternion representation means it cannot be compared like a numpy array.")
356 return self.
__eq__(self.__class__(other))
360 return self.__class__(array= -self.
q)
368 if isinstance(other, Quaternion):
369 return self.__class__(array=self.
q + other.q)
370 return self + self.__class__(other)
380 return self + (-other)
383 return self + (-other)
386 return -(self - other)
390 if isinstance(other, Quaternion):
391 return self.__class__(array=np.dot(self.
_q_matrix(), other.q))
392 return self * self.__class__(other)
398 return self.__class__(other) * self
401 if isinstance(other, Quaternion):
402 return self.q.__matmul__(other.q)
413 if isinstance(other, Quaternion):
414 if other == self.__class__(0.0):
415 raise ZeroDivisionError(
"Quaternion divisor must be non-zero")
416 return self * other.inverse
417 return self.
__div__(self.__class__(other))
423 return self.__class__(other) * self.
inverse 437 exponent = float(exponent)
442 except ZeroDivisionError:
445 return (self.
norm ** exponent) *
Quaternion(scalar=cos(exponent * theta), vector=(n * sin(exponent * theta)))
452 return other ** float(self)
456 return np.hstack((self.
q[0], -self.
q[1:4]))
459 return np.dot(self.
q, self.
q)
463 """Quaternion conjugate, encapsulated in a new instance. 465 For a unit quaternion, this is the same as the inverse. 468 A new Quaternion object clone with its vector part negated 470 return self.__class__(scalar=self.
scalar, vector=-self.
vector)
474 """Inverse of the quaternion object, encapsulated in a new instance. 476 For a unit quaternion, this is the inverse rotation, i.e. when combined with the original rotation, will result in the null rotation. 479 A new Quaternion object representing the inverse of this object 485 raise ZeroDivisionError(
"a zero quaternion (0 + 0i + 0j + 0k) cannot be inverted")
489 """L2 norm of the quaternion 4-vector. 491 This should be 1.0 for a unit quaternion (versor) 492 Slow but accurate. If speed is a concern, consider using _fast_normalise() instead 495 A scalar real number representing the square root of the sum of the squares of the elements of the quaternion. 498 return sqrt(mag_squared)
505 """Object is guaranteed to be a unit quaternion after calling this 506 operation UNLESS the object is equivalent to Quaternion(0) 514 """Normalise the object to a unit quaternion using a fast approximation method if appropriate. 516 Object is guaranteed to be a quaternion of approximately unit length 517 after calling this operation UNLESS the object is equivalent to Quaternion(0) 520 mag_squared = np.dot(self.
q, self.
q)
521 if (mag_squared == 0):
523 if (abs(1.0 - mag_squared) < 2.107342e-08):
524 mag = ((1.0 + mag_squared) / 2.0)
526 mag = sqrt(mag_squared)
528 self.
q = self.
q / mag
532 """Get a unit quaternion (versor) copy of this Quaternion object. 534 A unit quaternion has a `norm` of 1.0 537 A new Quaternion object clone that is guaranteed to be a unit quaternion 545 vector_length = np.linalg.norm(self.
vector)
546 if vector_length <= 0.0:
547 raise ZeroDivisionError(
'Quaternion is pure real and does not have a unique unit vector')
548 return self.
vector / vector_length
557 Returns the unit vector and angle of a non-scalar quaternion according to the following decomposition 559 q = q.norm() * (e ** (q.polar_unit_vector * q.polar_angle)) 561 source: https://en.wikipedia.org/wiki/Polar_decomposition#Quaternion_polar_decomposition 570 """Determine whether the quaternion is of unit length to within a specified tolerance value. 573 tolerance: [optional] maximum absolute value by which the norm can differ from 1.0 for the object to be considered a unit quaternion. Defaults to `1e-14`. 576 `True` if the Quaternion object is of unit length to within the specified tolerance value. `False` otherwise. 581 """Matrix representation of quaternion for multiplication purposes. 584 [self.
q[0], -self.
q[1], -self.
q[2], -self.
q[3]],
585 [self.
q[1], self.
q[0], -self.
q[3], self.
q[2]],
586 [self.
q[2], self.
q[3], self.
q[0], -self.
q[1]],
587 [self.
q[3], -self.
q[2], self.
q[1], self.
q[0]]])
590 """Matrix representation of quaternion for multiplication purposes. 593 [self.
q[0], -self.
q[1], -self.
q[2], -self.
q[3]],
594 [self.
q[1], self.
q[0], self.
q[3], -self.
q[2]],
595 [self.
q[2], -self.
q[3], self.
q[0], self.
q[1]],
596 [self.
q[3], self.
q[2], -self.
q[1], self.
q[0]]])
599 """Rotate a quaternion vector using the stored rotation. 602 q: The vector to be rotated, in quaternion form (0 + xi + yj + kz) 605 A Quaternion object representing the rotated vector in quaternion from (0 + xi + yj + kz) 611 """Rotate a 3D vector by the rotation stored in the Quaternion object. 614 vector: A 3-vector specified as any ordered sequence of 3 real numbers corresponding to x, y, and z values. 615 Some types that are recognised are: numpy arrays, lists and tuples. 616 A 3-vector can also be represented by a Quaternion object who's scalar part is 0 and vector part is the required 3-vector. 617 Thus it is possible to call `Quaternion.rotate(q)` with another quaternion object as an input. 620 The rotated vector returned as the same type it was specified at input. 623 TypeError: if any of the vector elements cannot be converted to a real number. 624 ValueError: if `vector` cannot be interpreted as a 3-vector or a Quaternion object. 627 if isinstance(vector, Quaternion):
631 if isinstance(vector, list):
634 elif isinstance(vector, tuple):
642 """Quaternion Exponential. 644 Find the exponential of a quaternion amount. 647 q: the input quaternion/argument as a Quaternion object. 650 A quaternion amount representing the exp(q). See [Source](https://math.stackexchange.com/questions/1030737/exponential-function-of-quaternion-derivation for more information and mathematical background). 653 The method can compute the exponential of any quaternion. 656 v_norm = np.linalg.norm(q.vector)
658 if v_norm > tolerance:
660 magnitude =
exp(q.scalar)
661 return Quaternion(scalar = magnitude * cos(v_norm), vector = magnitude * sin(v_norm) * vec)
665 """Quaternion Logarithm. 667 Find the logarithm of a quaternion amount. 670 q: the input quaternion/argument as a Quaternion object. 673 A quaternion amount representing log(q) := (log(|q|), v/|v|acos(w/|q|)). 676 The method computes the logarithm of general quaternions. See [Source](https://math.stackexchange.com/questions/2552/the-logarithm-of-quaternion/2554#2554) for more details. 678 v_norm = np.linalg.norm(q.vector)
681 if q_norm < tolerance:
683 return Quaternion(scalar=-float(
'inf'), vector=float(
'nan')*q.vector)
684 if v_norm < tolerance:
687 vec = q.vector / v_norm
688 return Quaternion(scalar=
log(q_norm), vector=acos(q.scalar/q_norm)*vec)
692 """Quaternion exponential map. 694 Find the exponential map on the Riemannian manifold described 695 by the quaternion space. 698 q: the base point of the exponential map, i.e. a Quaternion object 699 eta: the argument of the exponential map, a tangent vector, i.e. a Quaternion object 702 A quaternion p such that p is the endpoint of the geodesic starting at q 703 in the direction of eta, having the length equal to the magnitude of eta. 706 The exponential map plays an important role in integrating orientation 707 variations (e.g. angular velocities). This is done by projecting 708 quaternion tangent vectors onto the quaternion manifold. 710 return q * Quaternion.exp(eta)
714 """Quaternion symmetrized exponential map. 716 Find the symmetrized exponential map on the quaternion Riemannian 720 q: the base point as a Quaternion object 721 eta: the tangent vector argument of the exponential map 722 as a Quaternion object 728 The symmetrized exponential formulation is akin to the exponential 729 formulation for symmetric positive definite tensors [Source](http://www.academia.edu/7656761/On_the_Averaging_of_Symmetric_Positive-Definite_Tensors) 732 return sqrt_q * Quaternion.exp(eta) * sqrt_q
736 """Quaternion logarithm map. 738 Find the logarithm map on the quaternion Riemannian manifold. 741 q: the base point at which the logarithm is computed, i.e. 743 p: the argument of the quaternion map, a Quaternion object 746 A tangent vector having the length and direction given by the 747 geodesic joining q and p. 749 return Quaternion.log(q.inverse * p)
753 """Quaternion symmetrized logarithm map. 755 Find the symmetrized logarithm map on the quaternion Riemannian manifold. 758 q: the base point at which the logarithm is computed, i.e. 760 p: the argument of the quaternion map, a Quaternion object 763 A tangent vector corresponding to the symmetrized geodesic curve formulation. 766 Information on the symmetrized formulations given in [Source](https://www.researchgate.net/publication/267191489_Riemannian_L_p_Averaging_on_Lie_Group_of_Nonzero_Quaternions). 768 inv_sqrt_q = (q ** (-0.5))
769 return Quaternion.log(inv_sqrt_q * p * inv_sqrt_q)
773 """Quaternion absolute distance. 775 Find the distance between two quaternions accounting for the sign ambiguity. 778 q0: the first quaternion 779 q1: the second quaternion 782 A positive scalar corresponding to the chord of the shortest path/arc that 786 This function does not measure the distance on the hypersphere, but 787 it takes into account the fact that q and -q encode the same rotation. 788 It is thus a good indicator for rotation similarities. 790 q0_minus_q1 = q0 - q1
792 d_minus = q0_minus_q1.norm
793 d_plus = q0_plus_q1.norm
801 """Quaternion intrinsic distance. 803 Find the intrinsic geodesic distance between q0 and q1. 806 q0: the first quaternion 807 q1: the second quaternion 810 A positive amount corresponding to the length of the geodesic arc 814 Although the q0^(-1)*q1 != q1^(-1)*q0, the length of the path joining 815 them is given by the logarithm of those product quaternions, the norm 816 of which is the same. 818 q = Quaternion.log_map(q0, q1)
823 """Quaternion symmetrized distance. 825 Find the intrinsic symmetrized geodesic distance between q0 and q1. 828 q0: the first quaternion 829 q1: the second quaternion 832 A positive amount corresponding to the length of the symmetrized 833 geodesic curve connecting q0 to q1. 836 This formulation is more numerically stable when performing 837 iterative gradient descent on the Riemannian quaternion manifold. 838 However, the distance between q and -q is equal to pi, rendering this 839 formulation not useful for measuring rotation similarities when the 840 samples are spread over a "solid" angle of more than pi/2 radians 841 (the spread refers to quaternions as point samples on the unit hypersphere). 843 q = Quaternion.sym_log_map(q0, q1)
848 """Spherical Linear Interpolation between quaternions. 849 Implemented as described in https://en.wikipedia.org/wiki/Slerp 851 Find a valid quaternion rotation at a specified distance along the 852 minor arc of a great circle passing through any two existing quaternion 853 endpoints lying on the unit radius hypersphere. 855 This is a class method and is called as a method of the class itself rather than on a particular instance. 858 q0: first endpoint rotation as a Quaternion object 859 q1: second endpoint rotation as a Quaternion object 860 amount: interpolation parameter between 0 and 1. This describes the linear placement position of 861 the result along the arc between endpoints; 0 being at `q0` and 1 being at `q1`. 862 Defaults to the midpoint (0.5). 865 A new Quaternion object representing the interpolated rotation. This is guaranteed to be a unit quaternion. 868 This feature only makes sense when interpolating between unit quaternions (those lying on the unit radius hypersphere). 869 Calling this method will implicitly normalise the endpoints to unit quaternions if they are not already unit length. 874 amount = np.clip(amount, 0, 1)
876 dot = np.dot(q0.q, q1.q)
887 qr =
Quaternion(q0.q + amount * (q1.q - q0.q))
891 theta_0 = np.arccos(dot)
892 sin_theta_0 = np.sin(theta_0)
894 theta = theta_0 * amount
895 sin_theta = np.sin(theta)
897 s0 = np.cos(theta) - dot * sin_theta / sin_theta_0
898 s1 = sin_theta / sin_theta_0
905 """Generator method to get an iterable sequence of `n` evenly spaced quaternion 906 rotations between any two existing quaternion endpoints lying on the unit 909 This is a convenience function that is based on `Quaternion.slerp()` as defined above. 911 This is a class method and is called as a method of the class itself rather than on a particular instance. 914 q_start: initial endpoint rotation as a Quaternion object 915 q_end: final endpoint rotation as a Quaternion object 916 n: number of intermediate quaternion objects to include within the interval 917 include_endpoints: [optional] if set to `True`, the sequence of intermediates 918 will be 'bookended' by `q_start` and `q_end`, resulting in a sequence length of `n + 2`. 919 If set to `False`, endpoints are not included. Defaults to `False`. 922 A generator object iterating over a sequence of intermediate quaternion objects. 925 This feature only makes sense when interpolating between unit quaternions (those lying on the unit radius hypersphere). 926 Calling this method will implicitly normalise the endpoints to unit quaternions if they are not already unit length. 928 step_size = 1.0 / (n + 1)
929 if include_endpoints:
930 steps = [i * step_size
for i
in range(0, n + 2)]
932 steps = [i * step_size
for i
in range(1, n + 1)]
934 yield cls.
slerp(q0, q1, step)
937 """Get the instantaneous quaternion derivative representing a quaternion rotating at a 3D rate vector `rate` 940 rate: numpy 3-array (or array-like) describing rotation rates about the global x, y and z axes respectively. 943 A unit quaternion describing the rotation rate 949 """Advance a time varying quaternion to its value at a time `timestep` in the future. 951 The Quaternion object will be modified to its future value. 952 It is guaranteed to remain a unit quaternion. 956 rate: numpy 3-array (or array-like) describing rotation rates about the 957 global x, y and z axes respectively. 958 timestep: interval over which to integrate into the future. 959 Assuming *now* is `T=0`, the integration occurs over the interval 960 `T=0` to `T=timestep`. Smaller intervals are more accurate when 961 `rate` changes over time. 964 The solution is closed form given the assumption that `rate` is constant 965 over the interval of length `timestep`. 970 rotation_vector = rate * timestep
971 rotation_norm = np.linalg.norm(rotation_vector)
972 if rotation_norm > 0:
973 axis = rotation_vector / rotation_norm
974 angle = rotation_norm
976 self.
q = (self * q2).q
982 """Get the 3x3 rotation matrix equivalent of the quaternion rotation. 985 A 3x3 orthogonal rotation matrix as a 3x3 Numpy array 988 This feature only makes sense when referring to a unit quaternion. Calling this method will implicitly normalise the Quaternion object to a unit quaternion if it is not already one. 993 return product_matrix[1:][:, 1:]
997 """Get the 4x4 homogeneous transformation matrix equivalent of the quaternion rotation. 1000 A 4x4 homogeneous transformation matrix as a 4x4 Numpy array 1003 This feature only makes sense when referring to a unit quaternion. Calling this method will implicitly normalise the Quaternion object to a unit quaternion if it is not already one. 1005 t = np.array([[0.0], [0.0], [0.0]])
1007 return np.vstack([Rt, np.array([0.0, 0.0, 0.0, 1.0])])
1011 """Get the equivalent yaw-pitch-roll angles aka. intrinsic Tait-Bryan angles following the z-y'-x'' convention 1014 yaw: rotation angle around the z-axis in radians, in the range `[-pi, pi]` 1015 pitch: rotation angle around the y'-axis in radians, in the range `[-pi/2, -pi/2]` 1016 roll: rotation angle around the x''-axis in radians, in the range `[-pi, pi]` 1018 The resulting rotation_matrix would be R = R_x(roll) R_y(pitch) R_z(yaw) 1021 This feature only makes sense when referring to a unit quaternion. Calling this method will implicitly normalise the Quaternion object to a unit quaternion if it is not already one. 1025 yaw = np.arctan2(2 * (self.
q[0] * self.
q[3] - self.
q[1] * self.
q[2]),
1026 1 - 2 * (self.
q[2] ** 2 + self.
q[3] ** 2))
1027 pitch = np.arcsin(2 * (self.
q[0] * self.
q[2] + self.
q[3] * self.
q[1]))
1028 roll = np.arctan2(2 * (self.
q[0] * self.
q[1] - self.
q[2] * self.
q[3]),
1029 1 - 2 * (self.
q[1] ** 2 + self.
q[2] ** 2))
1031 return yaw, pitch, roll
1034 """Helper method: Wrap any angle to lie between -pi and pi 1036 Odd multiples of pi are wrapped to +pi (as opposed to -pi) 1038 result = ((theta + pi) % (2 * pi)) - pi
1044 """Get the axis or vector about which the quaternion rotation occurs 1046 For a null rotation (a purely real quaternion), the rotation angle will 1047 always be `0`, but the rotation axis is undefined. 1048 It is by default assumed to be `[0, 0, 0]`. 1051 undefined: [optional] specify the axis vector that should define a null rotation. 1052 This is geometrically meaningless, and could be any of an infinite set of vectors, 1053 but can be specified if the default (`[0, 0, 0]`) causes undesired behaviour. 1056 A Numpy unit 3-vector describing the Quaternion object's axis of rotation. 1059 This feature only makes sense when referring to a unit quaternion. 1060 Calling this method will implicitly normalise the Quaternion object to a unit quaternion if it is not already one. 1064 norm = np.linalg.norm(self.
vector)
1065 if norm < tolerance:
1069 return self.
vector / norm
1077 """Get the angle (in radians) describing the magnitude of the quaternion rotation about its rotation axis. 1079 This is guaranteed to be within the range (-pi:pi) with the direction of 1080 rotation indicated by the sign. 1082 When a particular rotation describes a 180 degree rotation about an arbitrary 1083 axis vector `v`, the conversion to axis / angle representation may jump 1084 discontinuously between all permutations of `(-pi, pi)` and `(-v, v)`, 1085 each being geometrically equivalent (see Note in documentation). 1088 A real number in the range (-pi:pi) describing the angle of rotation 1089 in radians about a Quaternion object's axis of rotation. 1092 This feature only makes sense when referring to a unit quaternion. 1093 Calling this method will implicitly normalise the Quaternion object to a unit quaternion if it is not already one. 1096 norm = np.linalg.norm(self.
vector)
1109 """ Return the real or scalar component of the quaternion object. 1112 A real number i.e. float 1118 """ Return the imaginary or vector component of the quaternion object. 1121 A numpy 3-array of floats. NOT guaranteed to be a unit vector 1128 raise AttributeError(
"Expected vector component to be of length 3 but received length {} instead!" 1158 """ Return all the elements of the quaternion object. 1161 A numpy 4-array of floats. NOT guaranteed to be a unit vector 1167 return self.
q[index]
1171 self.
q[index] = float(value)
1174 result = self.__class__(self.
q)
1178 result = self.__class__(deepcopy(self.
q, memo))
1179 memo[id(self)] = result
1184 if angle_rad
is not None:
1185 return float(angle_rad) / pi * 180.0
1189 if angle_deg
is not None:
1190 return float(angle_deg) / 180.0 * pi
def rotation_matrix(self)
def __rtruediv__(self, other)
def derivative(self, rate)
def _vector_conjugate(self)
def polar_unit_vector(self)
def absolute_distance(cls, q0, q1)
def __imatmul__(self, other)
def __imul__(self, other)
def _validate_number_sequence(self, seq, n)
def __format__(self, formatstr)
def sym_distance(cls, q0, q1)
def __rmul__(self, other)
def __idiv__(self, other)
def _from_matrix(cls, matrix, rtol=1e-05, atol=1e-08)
def transformation_matrix(self)
def to_radians(angle_deg)
def __rsub__(self, other)
def __matmul__(self, other)
def sym_exp_map(cls, q, eta)
def integrate(self, rate, timestep)
def __setitem__(self, index, value)
def intermediates(cls, q0, q1, n, include_endpoints=False)
def sym_log_map(cls, q, p)
def __rmatmul__(self, other)
def __itruediv__(self, other)
def polar_decomposition(self)
def __radd__(self, other)
def __init__(self, args, kwargs)
def __isub__(self, other)
def __pow__(self, exponent)
def _from_axis_angle(cls, axis, angle)
def __ipow__(self, other)
def __rpow__(self, other)
def distance(cls, q0, q1)
def _sum_of_squares(self)
def is_unit(self, tolerance=1e-14)
def _wrap_angle(self, theta)
def get_axis(self, undefined=np.zeros(3))
def __deepcopy__(self, memo)
def _rotate_quaternion(self, q)
def __getitem__(self, index)
def _fast_normalise(self)
def slerp(cls, q0, q1, amount=0.5)
def to_degrees(angle_rad)
def __rdiv__(self, other)
def __truediv__(self, other)
def __iadd__(self, other)