· 21 min read · 19 comments
Robotics

How to Integrate Quaternions

I’ve been working with inertial measurement units lately, and I’ve come to realize that there’s a surprising amount of mathematics involved in processing the raw data from the sensors. The story begins with me trying to integrate a three-dimensional angular velocity vector to get the orientation of an object. It turns out that while angular velocity is a vector, common representations of orientation, like Euler Angles, are not. So calculating the orientation is not as simple as integrating the angular velocity vector over time (0tωdt\int_0^t \vec{\omega} \mathscr{d}t).

Rotations in two dimensions are simple because there is only one plane in which you can rotate. The only variable that can be freely set is the center of rotation. So you can represent the orientation of an object in 2D by a single angle ϕ\phi. The angular velocity of an object in two dimensions can also be represented by a single number ω\omega, the time derivative of the angle. This produces the familiar formula I learned in high school physics.

ω=dϕdt=vr\omega = \frac{d\phi}{dt} = \frac{v_{\bot}}{r}

angular_velocity_image

Rotations in 3D are much more complicated. In 3 dimensions, the plane of rotation can be any among an infinite number of possibilities. The origin or “center” of rotation now becomes an axis of rotation which is represented by a vector. The plane of rotation is perpendicular to the axis of rotation. This is a result of the Euler Rotation Theorem

Before I can start integrating angular velocity I need to choose a representation for the orientation. There are several different conventions when it comes to representing the orientation of an object in 3D. The most popular approach is to use a set of three angles called Euler Angles. This approach was developed by the mathematician Leonhard Euler (pronounced oy-ler not you-ler). These set of three angles specify three successive rotations around three different axes in a very specific order. The order is important because rotations in 3D have an important property: they do not commute. This means that if I do rotation 1 before 2 I end up with a different orientation than if I do 2 before 1. Euler angles usually specify rotations in the ZXZ order. This means that the first axis of rotation is the current Z-axis. The second axis of rotation is the new X-axis after the first rotation. The final axis of rotation is the new Z-axis after the first and second rotations. A closely related approach is to use what’s called Tait-Bryan angles. Tait-Bryan angles are called yaw, pitch and roll and are commonly used when talking about the orientation of aircraft. The angles represent successive rotations around the body fixed X, Y and Z axes. Both Euler angles and Tait-Bryan angles however, suffer from something called Gimbal Lock. This is when for a certain value of one of the rotation angles, a degree of freedom is lost and two rotations “collapse” into a single rotation. The Wikipedia page linked has some nice visualizations and a short mathematical explanation (using rotation matrices) for why this happens.

Using Quaternions to represent rotations is a way to avoid the Gimbal Lock problem. Quaternions are so useful for representing orientations that most Kalman Filters that need to track 3D orientations use them instead of Euler Angles. So I settled on using quaternions. When I first started working with quaternions I found them a little difficult to understand. So I thought of writing an article about the path I took to understand and use quaternions for “integrating” angular velocity.

From all my time working with mathematics, I’ve come to realize that mathematical ideas exist on a spectrum. At one end is math that is focused on the practical application. Math that is very close to the real world (as experienced by humans) and can be understood easily by making analogies to things that humans encounter in everyday life. This is the type of math that is often applied to solve everyday problems like settling bills or building bridges. At the other end of the spectrum is the type of math that is pure abstraction. This type of abstract math is often used to generalize specific results in practical math to other problems or to come up with new insights that can pave the way for new solutions to a problem. Quaternions are a little more towards the abstract end of the spectrum and can be difficult to get an intuition for. Sometimes though there are ideas that you can’t get an intuition for. Working with such math is mostly a matter of getting used to it. With abstract math, the best way I’ve found to get used to it is to learn the basic definition and keep applying it to problems until you get the hang of it. The understanding forms in the brain automatically as you get the hang of it.

Quaternion Math

So without further ado, I’ll talk about what quaternions are and how they behave.

Representation

A quaternion qq can be represented as a tuple of 4 numbers:

q=[wxyz]=[wv]=w+xi+yj+zkq = \begin{bmatrix} w & \\ x &\\ y & \\ z \end{bmatrix} = \begin{bmatrix} w & \\ \vec{v}\end{bmatrix} = w + x\mathrm{i} + y\mathrm{j} + z\mathrm{k}

where the ww is the scalar part and the v\vec{v} is the vector part.

Two binary operations are defined for quaternions: addition ++ and quaternion multiplication \otimes.

Addition

Addition is defined as the component-wise sum just like for a 4D vector. The sum is commutative (order is not important) and associative (grouping is not important).

q1+q2=[w1+w2x1+x2y1+y2z1+z2]=q2+q1q_1 + q_2 = \begin{bmatrix} w_1 + w_2 & \\ x_1 + x_2 & \\ y_1 + y_2 & \\ z_1 + z_2 \end{bmatrix} = q_2 + q_1

Multiplication

Quaternion multiplication is defined in multiple ways but the formula that I find the easiest to remember is:

q1q2=[w1w2v1v2w1v2+w2v1+v1×v2]q_1 \otimes q_2 = \begin{bmatrix} w_1 w_2 - \vec{v}_1\cdot\vec{v}_2 & \\ w_1\vec{v}_2 + w_2\vec{v}_1 + \vec{v}_1\times\vec{v}_2 \end{bmatrix}

The multiplication is non-commutative (q1q2q2q1q_1 \otimes q_2 \neq q_2 \otimes q_1 ) and distributive over the sum (q1(q2+q3)=q1q2+q1q3)(q_1 \otimes (q_2 + q_3) = q_1 \otimes q_2 + q_1 \otimes q_3 ).

Norm, Conjugate and Inverse

It’s also useful to define the norm of a quaternion as:

q=w2+x2+y2+z2||q|| = \sqrt{w^2 + x^2 + y^2 + z^2}

a conjugate quaternion qq^* that satisfies the property qq=q2q \otimes q^* = ||q||^2 as

q=[wxyz]q^* = \begin{bmatrix} w & \\ -x & \\ -y & \\ -z \end{bmatrix}

and a quaternion inverse q1q^{-1} with the property qq1=[1000]q \otimes q^{-1} = \begin{bmatrix} 1 \\ 0 \\ 0 \\ 0 \end{bmatrix} as

q1=qq2q^{-1} = \frac{q^*}{||q||^2}

Using Quaternions for Rotation

Now that the behaviour of quaternions are established, there is the question of how to use them to represent 3D rotation. From Euler’s Rotation Theorem it is clear that rotations have 3 degrees of freedom. But quaternions as 4 tuples have 4 degrees of freedom. So an additional constraint needs to be imposed to use them to represent rotations. This is done by requiring that the quaternions are unit quaternions: q=1||q|| = 1. Unit quaternions are also called versors. There are many diagrams and visualizations that attempt to make understanding the unit quaternion more intuitive but I found that for me, most were misleading. I found it the most useful to think of quaternions as just an abstract object with the properties that I’ve mentioned and not trying to have any picture in mind. The moment I left behind the crutch of visualization and forced myself to accept and think about quaternions as they are, everything fell into place.

To rotate a 3D vector r\vec{r} by a versor qq an operation called conjugation is used.

r=q[0r]q\vec{r}' = q \otimes \begin{bmatrix} 0 & \\ \vec{r} \end{bmatrix} \otimes q^{*}

If you find the formula quite mysterious (as I did when I first saw it), it’s helpful to use one of the other methods (euler angles, axis angle) to rotate a vector and verify that the result is the same.

It’s possible to compose two rotations represented by two quaternions q1q_1 and q2q_2 by multiplying the two quaternions together q2q1q_2 \otimes q_1. This product represents the rotation q1q_1 applied before q2q_2.

Quaternions, Rotation Matrices and the Rotation Group SO(3)SO(3)

Earlier, I mentioned that rotations in 3D have certain properties (like non-commutativity) that implies that they don’t belong to a vector space (and therefore can’t be represented by one). If I want to be exact when talking about rotations, I have to consider them as a group. A group is yet another mathematical abstraction. Abstractly, think of them as a set of objects that follow certain rules. Remember that groups are another concept that leans towards the abstract end of the mathematical spectrum. They can be applied to rotations but they were invented by mathematicians to study more general ideas. So what rules does a group follow?

A group is a set GG along with some binary operation \cdot (takes two elements of the set and gives a third). The elements of the set follow these rules:

  1. Closure: If a,bGa,b \in G then abGa\cdot b \in G.
  2. Associativity: If a,b,cGa,b,c \in G then a(bc)=(ab)ca\cdot ( b \cdot c) = (a \cdot b) \cdot c .
  3. Identity: There’s some element of the set ee which has the property ae=ea=aa \cdot e = e\cdot a = a.
  4. Inverse: If aGa \in G there’s some element aa^* such that aa=aa=ea \cdot a^* = a^* \cdot a = e.

If you compare this to 3D rotations, you can see that the set of 3D rotations are an example of a group under the binary operation of composition (doing one rotation after another)!

  1. Closure: If I compose two rotations it forms another rotation. It doesn’t suddenly become a translation or scaling or shear. There’s no way to combine rotations to do any of these other operations.
  2. Associativity: If I do three rotations, it doesn’t matter which two I compose first.
  3. Identity: There’s an identity rotation (00^\circ of rotation around any axis).
  4. And there’s an inverse rotation (rotating by negative of the angle around the same axis.)

Quaternions under multiplications also satisfy all these properties.

If I convert Euler angle rotations to rotation matrices and compare them with quaternions, the parallels between them are very clear.

Group PropertyRotation Matrix RiGR_i \in GQuaternion qiHRq_i \in \mathbb{H}_R
ClosureR1R2GR_1 \cdot R_2 \in Gq1q2HRq_1 \otimes q_2 \in \mathbb{H}_R
AssociativityR1(R2R3)=(R1R2)R3R_1 \cdot (R_2 \cdot R_3) = (R_1 \cdot R_2) \cdot R_3q1(q2q3)=(q1q2)q3q_1 \otimes (q_2 \otimes q_3) = (q_1 \otimes q_2) \otimes q_3
IdentityII11
InverseRi1=RiTR_i^{-1} = R_i^{T} qi1q_i^{-1}

The closure and associativity properties rotation matrices can be easily seen as a consequence of the fact that rotation matrices are orthogonal matrices.

With this knowledge of the rules that rotations follow, it’s clear why it’s silly to think of the Euler angles as a vector. They’re a set of related numbers but vectors they are not! It’s also clear why integrating the angular velocity vector over time does not directly give the orientation in an easily usable form.

Representing quaternion rotation as a matrix

Linearity of operations are an important property that both engineers and mathematicians like to take advantage of. It can result in useful simplifications of results. So it’s useful (later in this article) to ask the question: Is quaternion multiplication a linear operation? The simplest way to find out is to write out the result of a general quaternion multiplication operation and check if it’s possible to represent it as a matrix multiplication.

The symbolic result of multiplying two quaternions q1q_1 and q2q_2 is:

q1q2=[w1w2x1x2y1y2z1z2 w1x2+w2x1+y1z2y2z1 w1y2+w2y1x1z2+x2z1 w1z2+w2z1+x1y2x2y1]q_1 \otimes q_2 = \left[\begin{matrix}w_1 w_2 - x_1 x_2 - y_1 y_2 - z_1 z_2\\\ w_1 x_2 + w_2 x_1 + y_1 z_2 - y_2 z_1\\\ w_1 y_2 + w_2 y_1 - x_1 z_2 + x_2 z_1\\\ w_1 z_2 + w_2 z_1 + x_1 y_2 - x_2 y_1\end{matrix}\right]

From inspection, this can be written as a matrix product:

q1q2=[w2x2y2z2 x2w2z2y2 y2z2w2x2 z2y2x2w2][w1 x1 y1 z1]q_1 \otimes q_2 = \left[\begin{matrix}w_2 & -x_2 & -y_2 & -z_2\\\ x_2 & w_2 & z_2 & - y_2\\\ y_2 & - z_2 & w_2 & x_2\\\ z_2 & y_2 & - x_2 & w_2\end{matrix}\right] \begin{bmatrix} w_1 \\\ x_1 \\\ y_1 \\\ z_1 \end{bmatrix}

And if I change the order of multiplication:

q2q1=[w2x2y2z2 x2w2z2y2 y2z2w2x2 z2y2x2w2][w1 x1 y1 z1]q_2 \otimes q_1 = \left[\begin{matrix}w_2 & - x_2 & - y_2 & - z_2 \\\ x_2 & w_2 & - z_2 & y_2 \\\ y_2 & z_2 & w_2 & - x_2 \\\ z_2 & - y_2 & x_2 & w_2\end{matrix}\right] \begin{bmatrix} w_1 \\\ x_1 \\\ y_1 \\\ z_1 \end{bmatrix}

Integrating Angular Velocity

To properly integrate angular velocity to get a quaternion, I need to find a relationship between quaternions and angular velocity - or more precisely - a differential equation that relates the time derivative of the quaternion q˙\dot{q} and the angular velocity vector ω\vec{\omega}.

A natural place to start is the original definition of the angular velocity from physical law. If I imagine a vector of constant s(t)\vec{s}(t) length stretching out from the origin undergoing rotation with the instantaneous angular velocity ω(t)\vec{\omega}(t) I can find the velocity at the tip of this vector by taking it’s derivative.

From rigid-body kinematics, dsdt=ω×s\frac{\mathrm{d}\vec{s}}{\mathrm{d}t} = \vec{\omega} \times \vec{s}

If we view ω\vec{\omega} and s\vec{s} as pure quaternions, their Hamilton product is

ωs=(ωs,  ω×s)\vec{\omega} \otimes \vec{s} = (-\vec{\omega} \cdot \vec{s}, \; \vec{\omega} \times \vec{s})

Since ωs\vec{\omega} \cdot \vec{s} is not generally zero, it’s cleaner to write the time derivative as the vector part, equivalently

dsdt=ω×s=12[ω,s]\frac{\mathrm{d}\vec{s}}{\mathrm{d}t} = \vec{\omega} \times \vec{s} = \frac{1}{2} [\vec{\omega}, \vec{s}]

(Background: [a,b]=abba[a,b] = a \otimes b - b \otimes a and for pure quaternions [a,b]=2(0,a×b)[a,b] = 2(0, \mathbf{a} \times \mathbf{b}).)

Now I imagine that this instantaneous vector is represented by a quaternion qq rotation from a constant vector s0\vec{s}_0.

s=qs0qdsdt=ddt[qs0q]\begin{align} \vec{s} &= q \otimes \vec{s}_0 \otimes q^* \\ \frac{\mathrm{d}\vec{s}}{\mathrm{d}t} &= \frac{\mathrm{d}}{\mathrm{d}t} \left[ q \otimes \vec{s}_0 \otimes q^* \right] \end{align}

So how do I take that nasty looking derivative on the side? Well it turns out that the product rule of derivatives that is valid in basic calculus is also perfectly valid for quaternion multiplication! I converted the quaternion product into a matrix multiplication and spent some time converting the derivatives of the product to the result from applying the product rule. The only thing to watch out for is the non commutativity of the multiplication. The order of the quaternion product shouldn’t be changed when applying the product rule.

ddt[qs0q]=q˙s0q+qs0q˙\frac{\mathrm{d}}{\mathrm{d}t} \left[ q \otimes \vec{s}_0 \otimes q^* \right] = \dot{q} \otimes \vec{s}_0 \otimes q^* + q \otimes \vec{s}_0 \otimes \dot{q^*}

There’s a term in this equation - the derivative of the conjugate - that’ll cause some trouble. It is possible to eliminate this using other methods but the simplest way is to directly find a relationship between q˙\dot{q} and q˙\dot{q^*}. The easy way to do this is to take the derivative of the product of a quaternion and it’s conjugate which we know to be 1:

ddt(qq)=ddt1q˙q+qq˙=0\begin{align} \frac{\mathrm{d}}{\mathrm{d}t} (q \otimes q^* ) &= \frac{\mathrm{d}}{\mathrm{d}t} 1 \\ \dot{q} \otimes q^* + q \otimes \dot{q^*} &= 0 \end{align}

That gives the relationship

q˙=qq˙q\dot{q^* } = -q^* \otimes \dot{q} \otimes q^*

Expressing s0s_0 in terms of ss, substituting q˙\dot{q^* } and doing a little algebra:

ddt[qs0q]=q˙qssq˙q=ωs\frac{\mathrm{d}}{\mathrm{d}t} \left[ q \otimes \vec{s}_0 \otimes q^* \right] = \dot{q} \otimes q^* \otimes \vec{s} - \vec{s} \otimes \dot{q} \otimes q^* = \vec{\omega} \otimes \vec{s}

The Quaternion Commutator

The expression q˙qssq˙q\dot{q} \otimes q^* \otimes \vec{s} - \vec{s} \otimes \dot{q} \otimes q^* is in the form pqqpp \otimes q - q \otimes p which is defined as a commutator operation written as [p,q][p, q]. Going through the algebra of this operation and simplifying:

[q1,q2]=[02(v1×v2)][q_1, q_2] = \begin{bmatrix} 0 \\ 2(\vec{v}_1 \times \vec{v}_2) \end{bmatrix}

If pp and qq are pure quaternions (zero scalar parts), then

[p,q]=pqqp=2(0,p×q)[p, q] = p \otimes q - q \otimes p = 2(0, \mathbf{p} \times \mathbf{q})

Meanwhile the product is

pq=pq+(0,p×q)p \otimes q = -\mathbf{p} \cdot \mathbf{q} + (0, \mathbf{p} \times \mathbf{q})

so [p,q]=2(0,p×q)2(pq)[p, q] = 2(0, \mathbf{p} \times \mathbf{q}) \neq 2(p \otimes q) in general; equality holds only when pq=0\mathbf{p} \cdot \mathbf{q} = 0 (orthogonality). For example [i,i]=0[\mathbf{i}, \mathbf{i}] = 0 but 2ii=22\mathbf{i}\mathbf{i} = -2.

The Product q˙q\dot{q} q^* is a Pure Quaternion

q˙q+qq˙=0q˙q=(q˙q)\begin{align} \dot{q} \otimes q^* + q \otimes \dot{q^* } &= 0 \\ \dot{q} \otimes q^* &= -(\dot{q} \otimes q^* )^* \end{align}

Saying that a q=qq = -q^* is the same as saying that w=ww = -w which means that the scalar part of the quaternion is zero.

The Differential Equation

So finally, I can extract the quaternion differential equation:

q˙qssq˙q=ω×s[q˙q,s]=ω×s=12[ω,s]\begin{align} \dot{q} \otimes q^* \otimes \vec{s} - \vec{s} \otimes \dot{q} \otimes q^* &= \vec{\omega} \times \vec{s} \\ \left[\dot{q} \otimes q^* , \vec{s}\right] &= \vec{\omega} \times \vec{s} = \frac{1}{2}[\vec{\omega}, \vec{s}] \end{align}

Since q˙q\dot{q} \otimes q^* is a pure quaternion (its conjugate equals its negative), the identity [p,s]=2p×s[p, \vec{s}] = 2p \times \vec{s} for pure pp implies the equality holds for all s\vec{s} only if

q˙q=12ω\dot{q} \otimes q^* = \frac{1}{2} \vec{\omega}

Right-multiplying by qq yields the standard kinematic equation

q˙=12ωq\boxed{\dot{q} = \frac{1}{2} \vec{\omega} \otimes q}

(global ω\vec{\omega}), and equivalently q˙=12qω\dot{q} = \frac{1}{2} q \otimes \vec{\omega} when ω\vec{\omega} is expressed in the body frame.

In the first equation the ω\vec{\omega} is the angular velocity in the global fixed frame. In many situations it’s more useful to have an equation in terms of the angular velocity as measured by a reference frame fixed to the moving body - like when it is measured using a gyroscope. The angular velocity in this frame is the global angular velocity rotated into the body frame ω=qωq\vec{\omega}' = q^* \otimes\vec{\omega}\otimes q . This gives the body-frame differential equation:

q˙=12qω\dot{q} = \frac{1}{2} q \otimes \vec{\omega}

Integration

To solve this differential equation is to be able to integrate it. Normal differential equations are difficult enough. How does one solve a differential equation with a quaternion multiplication in it? This is where the linearity of the quaternion multiplication becomes very useful. I am also going to make a (reasonable) assumption - that the angular velocity is constant over a time Δt\Delta t. Then I can rewrite the differential equation in a well known form:

[w˙ x˙ y˙ z˙]=12[0ωxωyωz ωx0ωzωy ωyωz0ωx ωzωyωx0][w x y z]\begin{bmatrix} \dot{w} \\\ \dot{x} \\\ \dot{y} \\\ \dot{z} \end{bmatrix} = \frac{1}{2} \cdot \left[\begin{matrix}0 & - \omega_x & - \omega_y & - \omega_z \\\ \omega_x & 0 & \omega_z & - \omega_y \\\ \omega_y & - \omega_z & 0 & \omega_x \\\ \omega_z & \omega_y & - \omega_x & 0 \end{matrix}\right] \cdot \begin{bmatrix} w \\\ x \\\ y \\\ z \end{bmatrix}

This is an ODE in the form q˙=Aq\dot{q} = Aq where A is the big matrix. The solution to this differential equation then is:

q(t)=eA(tt0)q0q(t) = e^{A(t - t_0 )} q_0

Quaternion Exponential

Interestingly, if I define the quaternion exponential in the same way as the matrix exponential (using its Taylor Series representation), I get a quaternion equivalent formula 5.

exp(q)=ewev=ew(0vkk!)=ew(cosv+vvsinv)\begin{align} \exp{(q)} &= e^{w}e^{\vec{v}} \\ &= e^{w}\left(\sum_0^\infty \frac{\vec{v}^k}{k!}\right) \\ &= e^{w}\left(\cos{|\vec{v}|} + \frac{\vec{v}}{|\vec{v}|} \sin{|\vec{v}|}\right) \end{align}

Using the quaternion exponential, the solution to the differential equation can be expressed in quaternion form as:

q(t)=exp(12ωΔt)q0q(t) = \exp{\left(\frac{1}{2}\vec{\omega}\Delta t\right)} \otimes q_0

Summing Up

I started out with the simple sounding task of integrating angular velocity and in trying to solve it, traversed through a several different areas of mathematics and learned a lot along the way before coming to the final solution. However, my description here is far from complete. The equation above only holds if the angular velocity is constant over a time period. This means its a “first order” model. Dropping this assumption gives nthn^{th} order models for integration. There are also intricate details that I’m only beginning to understand. For instance, I’m reading about how rotations are a special type of group called Lie Groups where the group is also a differentiable manifold (yet another interesting abstract mathematical object). The space of angular velocity forms what is called a Lie Algebra on the group. And the quaternion exponential function which most texts I refer to seem to pull out of thin air is actually related to a more general idea called an exponential map which maps general Lie Algebras to Lie Groups.

Despite it’s incompleteness however, it is the minimum that I needed to understand to be able to actually implement in code the integration of a quaternion - i.e use quaternions practically for integrating data coming in from an inertial measurement unit. I hope that others trying to understand quaternions and their role in representing 3D rotations will find this article useful. Some of the references below go deeper into the nature of quaternions and how to use them for useful things like tracking orientations.

References

  1. Boyle, Michael. “The integration of angular velocity.” Advances in Applied Clifford Algebras (2016): 1-30.
  2. https://en.wikipedia.org/wiki/Rotation_group_SO(3)
  3. https://en.wikipedia.org/wiki/Quaternion
  4. Sola, Joan. “Quaternion kinematics for the error-state KF.” (2015).
  5. https://math.stackexchange.com/questions/1030737/exponential-function-of-quaternion-derivation

Correspondence

Reader Comments & Discussion

Chung Hoàng ·

Awesome! Thank you for the post!

rational_ash ·

Glad you found it useful! :)

chettali ·

Hi Ashwin,

Thanks for your post. It was fantastic and amazing. But I have some doubts; I hope you will be having something to explode it.

  1. If I have the rotation angles along each of the zz, yy, and xx axes (yaw, pitch, and roll) and I calculate the quaternion for each axis rotation, I get QyQ_y, QpQ_p, and QrQ_r. Is the product QyQpQrQ_y Q_p Q_r the same as the rotation matrix product RyRpRrR_y R_p R_r?

Thank you, chettali

rational_ash ·

Yes, multiplying quaternions that represent rotations is the same as composing the rotations (Just like with the matrices!).

florian ·

Hi Ashwin,

I found your explanation really nice and clear but at the end I am stuck trying to implement the integration on a very simple Python case. I am not sure I really understand what the ww vector stands for in the last formula... Is it a quaternion rotation? Is q0q_0 the previously computed quaternion? Have you any implementation example?

rational_ash ·

Hi Florian,

The ww vector in the last formula is the angular velocity vector. Angular velocity only has 3 components but you can also consider it as a quaternion with a scalar part of 00. So you write the angular velocity as [0,ωx,ωy,ωz][0, \omega_x, \omega_y, \omega_z] and apply the quaternion exponential formula to it. I don't have a Python implementation on hand at the moment but there's a quaternion library in Python that implements this integration procedure. Maybe digging around in the source code there will help you. :)

florian ·

Thank you for your prompt reply. I will have a look at rowan library, it seems to fit my needs.

Ran Wang ·

Hi Ashwin,

Thanks a lot for the posts. It's amazing! But I still have one question: if I implement the integration in the code, do I have to check the norm of the quaternions or is the unit-norm property (q=1\lVert q \rVert = 1) automatically guaranteed?

Thank you! Ran

rational_ash ·

Unfortunately, I don't think implementing this will avoid the need to renormalize once in a while. Even with quaternions, since we're doing the multiplication with floating point numbers with finite precision on a computer, rounding errors will eventually accumulate. You'd have to do some experiments to see how long it takes for the quaternion to drift off too far from the unit norm value.

Alex Dobrinsky ·

Hi Ashwin,

I think you forgot 1/21/2 in:

[w˙x˙y˙z˙]=[0ωxωyωzωx0ωzωyωyωz0ωxωzωyωx0][wxyz]\begin{bmatrix} \dot{w} \\ \dot{x} \\ \dot{y} \\ \dot{z} \end{bmatrix} = \left[\begin{matrix}0 & - \omega_x & - \omega_y & - \omega_z \\ \omega_x & 0 & \omega_z & - \omega_y \\ \omega_y & - \omega_z & 0 & \omega_x \\ \omega_z & \omega_y & - \omega_x & 0 \end{matrix}\right] \cdot \begin{bmatrix} w \\ x \\ y \\ z \end{bmatrix}

rational_ash ·

Saw this super late. Fixed, thanks! ^_^

Plads ·

There is a simpler way to represent your orientations, such that you can simply find the integral of the angular velocity and use it directly without using quaternion or rotation matrix math to translate the result to a different orientation representation system.

Suppose you have an angular velocity ω\mathbf{\omega} and you want to integrate it.

This yields something like Xω=Vωt+C\mathbf{X}_{\omega} = \mathbf{V}_{\omega} \cdot t + \mathbf{C}.

If t=0t = 0, then C=Xω(0)\mathbf{C} = \mathbf{X}_{\omega}(0), your initial angular position.

This angular position is a rotation vector in 3D. But how do you use a rotation vector to express angular position?

It's pretty simple, actually. One way to do it is to define two vectors, which represent the resting orientation of your angularly positioned system. They can be arbitrary vectors, with the exception that they cannot point in the same direction (or in opposite directions). I personally like to define one as “up” and the other as “forward”, but they can be anything. As stated, these vectors represent the basic resting orientation of your angularly positioned system. They are constant, and serve as a reference orientation only.

Then, if you want to know the current orientation of your object, you simply rotate these two vectors by your angular position, which is, as I stated, a simple 3D rotation vector.

This yields two vectors pointing in a new orientation, the actual current 3D orientation of your object. If you used one vector as “up” and the other as “forward”, you can easily find the right or left direction of your object with a cross product of the two.

If you want to find back the angular position of your object with only the two vectors representing the current orientation of your object and the two vectors we defined as the resting orientation of your angularly positioned system, you can find it back relatively quickly with a bit of 3D geometry.

I found this method very recently, and I'm pretty proud of it.

First, to rotate any vector v1\mathbf{v}_1 to another vector v2\mathbf{v}_2, you can find that there actually exists an infinite number of possible rotators, which lie on a circle.

To find the normal of this circle, you can simply find the cross product of v1\mathbf{v}_1 and v2\mathbf{v}_2, and then take the cross product of the resulting vector with both v1\mathbf{v}_1 and v2\mathbf{v}_2 added (so something like v1.cross(v2).cross(v1 + v2)).

You can then find the first circle normal, n1\mathbf{n}_1, using this method with your first orientation vector and the equivalent first vector in the resting orientation. For example, you find the circle normal between the “up” vector of your oriented object and the “up” vector in the resting orientation reference. You find n2\mathbf{n}_2 with the remaining pair of orientation vectors.

Then, these two circles, which represent all the possible rotators for both of your orientation vectors, intersect. The two vectors lying on the intersection of these circles represent the possible angular rotations that you need to apply to the resting orientation vectors in order to rotate them into the correct 3D orientation of your object (actually, it only gives the direction in which the angular position vector is facing, but you can pretty easily find back its magnitude with a bit of trigonometry). It yields effectively an angular position, which is a rotation vector in 3D.

And there you have it. You can now integrate your angular velocity formulas, and use them as is, without the extra quaternion or rotation matrix math.

Of course, you need to have an implementation of a rotation function that rotates vectors following the right-hand rule, which can be done with quaternions, for example. My point is that using the method I just described, you can integrate your formulas of angular stuff just as if it was linear.

Lars Maier ·

If pp and qq are pure, then the equation [p,q]=2pq[p, q] = 2\, p \otimes q does not hold. It doesn't even hold for ii, which clearly commutes with itself, thus [i,i]=0[i, i] = 0, but 2ii=22\, i \otimes i = -2. This is only true if pp and qq are orthogonal. This is in general not true and it is not true for ww and ss, except when ss lies in the plane of rotation.

rational_ash ·

Hi! You're probably right about this one. This derivation was meant to give me an intuition for the diff. eq. and it's definitely not mathematically rigorous. I'll get around to adding this correction sometime. Thank you!

xacuti9 ·

Hi Ashwin, Thank you for writing this excellent article. I had one question regarding the convention you are using for representing rotations. Are you using passive (ROS-style) or active convention (a reference is here)? They are inverse of each other but have important implications.

I implemented the results you derived using Octave. I had to switch the positions of the exp()\exp(\cdot) and q0q_0 terms in the last equation you mentioned here to get the expected result under the passive transformation convention. This makes me think that you might be using the active transformation convention but I'd like to confirm that. Please let me know.

Below is an Octave/MATLAB implementation for anyone who may benefit. If you reuse this code, please note the discrepancy mentioned toward the end of quaternion_test.m. Apologies for how the code looks—the website's renderer won't respect my indentation :(

Quaternion.m

properties
coeffs_ = [0, 0, 0, 1]';
endproperties

methods
function obj = Quaternion(xyzw)
obj.coeffs_ = xyzw;
endfunction

function x = x(obj)
x = obj.coeffs_(1);
endfunction

function y = y(obj)
y = obj.coeffs_(2);
endfunction

function z = z(obj)
z = obj.coeffs_(3);
endfunction

function w = w(obj)
w = obj.coeffs_(4);
endfunction

function result = quaternionProduct(obj, rhs)
result = Quaternion([
obj.w() * rhs.x() + obj.x() * rhs.w() - obj.y() * rhs.z() - obj.z() * rhs.y(),
obj.w() * rhs.y() + obj.y() * rhs.w() - obj.x() * rhs.z() + obj.z() * rhs.x(),
obj.w() * rhs.z() + obj.z() * rhs.w() + obj.x() * rhs.y() - obj.y() * rhs.x(),
obj.w() * rhs.w() - obj.x() * rhs.x() - obj.y() * rhs.y() - obj.z() * rhs.z()
]);
endfunction

function result = conjugate(obj)
result = Quaternion([-obj.x(), -obj.y(), -obj.z(), obj.w()]');
endfunction

function result = exponentiate(obj)
q_v = obj.coeffs_(1:3, :);
mod_v = norm(q_v);

if(mod_v < 1e-6)
disp('Very small mod V');
result = Quaternion(exp(obj.w()) * [q_v; 1])
else
result = Quaternion(exp(obj.w()) * [q_v / mod_v * sin(mod_v); cos(mod_v)]);
endif
endfunction

function rotation_matrix = toRotationMatrix(obj)
rotation_matrix = [
2*(obj.w()^2+obj.x()^2)-1, 2*(obj.x()*obj.y()-obj.w()*obj.z()), 2*(obj.x()*obj.z()+obj.w()*obj.y());
2*(obj.x()*obj.y()+obj.w()*obj.z()), 2*(obj.w()^2+obj.y()^2)-1, 2*(obj.y()*obj.z()-obj.w()*obj.x()),
2*(obj.x()*obj.z()-obj.w()*obj.y()), 2*(obj.y()*obj.z()+obj.w()*obj.x()), 2*(obj.w()^2+obj.z()^2)-1
];
endfunction

endmethods
endclassdef

quaternion_test.m

clc();

% A pi/2 rotation about the z-axis representing initial orientation of a
% vehicle frame w.r.t a reference frame.
%
% Assume that the referece frame is such that:
% x-axis points forwards.
% y-axis points to the left.
% z-axis points up.
%
% The below quaternion transforms points w.r.t vehicle frame(see below)
% to the above established reference frame (passive transformation) such that,
%
% reference_frame_point = q0 * vehicle_frame_point
%
% With that, we expect that the vehicle frame is initially oriented such that,
% x-axis points to the left.
% y-axis points backwards.
% z-axis points up.
%
% The rotation matrix equivalent to the quaternion q0 is expected to be:
% [0.0, -1.0, 0.0;
% 1.0, 0.0, 0.0;
% 0.0, 0.0, 1.0]
q0 = Quaternion([0, 0, sin(pi / 4), cos(pi / 4)]');

% Define parameters for rotation of the vehicle frame w.r.t to its
% instantaneous self(aka body frame representation)
% The parameter below should represent a rotation of pi/2 radians about the
% x-axis of the vehicle frame.
rotation_axis = [1.0, 0.0, 0.0]';
rotation_rate = pi / 2.0;
omega = rotation_rate * rotation_axis;
delta_t = 1.0;

% Build the angular_velocity * dt into a quaternion.
omega_times_dt_quat = Quaternion([0.5 * omega * delta_t; 0.0]);

% Under passive trasformations convention, this composition should effectively
% be:
% qAt1sec = rot(pi/2 about z) * rot(pi/2 about x)
%
% we expect that the new orientation of vehicle frame such that:
% x-axis points to the left.
% y-axis points up.
% z-axis points forwards.
%
% The quivalent rotation matrix that converts points from this new orientation
% of the vehicle to the reference frame we established in line 6
% is expected to be:
% [0.0, 0.0, 1.0;
% 1.0, 0.0, 0.0;
% 0.0, 1.0, 0.0]
%
% The composition below expected to emit the orientation of the vehicle w.r.t
% the reference frame (hopefully under the passive transformation convention).
qAt1secPerBlogAlgo = omega_times_dt_quat.exponentiate().quaternionProduct(q0);
qAt1secPerExpectations = q0.quaternionProduct(omega_times_dt_quat.exponentiate());

disp('Initial orientation of the vehicle:');
disp(q0.toRotationMatrix());

% This doesn't match the expectation in lines 52-54.
disp('Final orientation of the vehicle per algo:');
disp(qAt1secPerBlogAlgo.toRotationMatrix())

% This matches the expectations.
disp('Final orientation of the vehicle as expected under passive trasformations conventions:')
disp(qAt1secPerExpectations.toRotationMatrix())
Ragul Karthikeyan ·

Hi Ashwin, thank you for this detailed article. I have one doubt: in other forums I have seen the diagonal elements are 11 instead of 00; for some reason they add an identity matrix along with this matrix. Could you explain why it is needed?

AllisonRoad ·

I think you are referring to the exp(A(tt0))\exp(A (t - t_0)) expression in Ashwin's Integration section. If you use MATLAB to find that 4×44 \times 4 matrix, you will see ones along the resulting diagonal. That makes sense since that matrix multiplies q0q_0 to get q(t)q(t). But Ashwin shows that you don't need to use MATLAB at all. At the bottom of his Quaternion Exponential section, he shows you only need a quaternion multiplication to perform the integration! So awesome! Be sure to look at his References too, as they show the details of that multiplication in great detail (see Ref 4 and 5 above). Great work, Ashwin!

rational_ash ·

Hi! Thanks for sharing code! I believe Matlab / Octave has better support for quaternions now.

rational_ash ·

Thanks for replying and glad you're finding the article useful!

Join the Discussion

Kept private
Live Preview Markdown & LaTeX

Start typing to see how your comment will render.

Comments are moderated and will appear after review.

© 2025 Ashwin Narayan. All rights reserved.