average#

ahrs.common.quaternion.QuaternionArray.average(self, span: Tuple[int, int] = None, weights: ndarray = None) ndarray#

Average quaternion using Markley’s method [MCCO07].

It has to be clear that we intend to average attitudes rather than quaternions. It just happens that we represent these attitudes with unit quaternions, that is \(\|\mathbf{q}\|=1\).

The average quaternion \(\bar{\mathbf{q}}\) should minimize a weighted sum of the squared Frobenius norms of attitude matrix differences:

\[\bar{\mathbf{q}} = \mathrm{arg min}\sum_{i=1}^nw_i\|\mathbf{A}(\mathbf{q}) - \mathbf{A}(\mathbf{q}_i)\|_F^2\]

Taking advantage of the attitude’s orthogonality in SO(3), this can be rewritten as a maximization problem:

\[\bar{\mathbf{q}} = \mathrm{arg max} \big\{\mathrm{tr}(\mathbf{A}(\mathbf{q})\mathbf{B}^T)\big\}\]

with:

\[\mathbf{B} = \sum_{i=1}^nw_i\mathbf{A}(\mathbf{q}_i)\]

We can verify the identity:

\[\mathrm{tr}(\mathbf{A}(\mathbf{q})\mathbf{B}^T) = \mathbf{q}^T\mathbf{Kq}\]

using Davenport’s symmetric traceless \(4\times 4\) matrix:

\[\mathbf{K}=4\mathbf{M}-w_\mathrm{tot}\mathbf{I}_{4\times 4}\]

where \(w_\mathrm{tot}=\sum_{i=1}^nw_i\), and \(\mathbf{M}\) is the \(4\times 4\) matrix:

\[\mathbf{M} = \sum_{i=1}^nw_i\mathbf{q}_i\mathbf{q}_i^T\]

Warning

In this case, the product \(\mathbf{q}_i\mathbf{q}_i^T\) is a normal matrix multiplication, not the Hamilton product, of the elements of each quaternion.

Finally, the average quaternion \(\bar{\mathbf{q}}\) is the eigenvector corresponding to the maximum eigenvalue of \(\mathbf{M}\), which in turns maximizes the procedure:

\[\bar{\mathbf{q}} = \mathrm{arg max} \big\{\mathbf{q}^T\mathbf{Mq}\big\}\]

Changing the sign of any \(\mathbf{q}_i\) does not change the value of \(\mathbf{M}\). Thus, the averaging procedure determines \(\bar{\mathbf{q}}\) up to a sign, which is consistent with the nature of the attitude representation using unit quaternions.

Parameters:
  • span (tuple, default: None) – Span of data to average. If none given, it averages all.

  • weights (numpy.ndarray, default: None) – Weights of each quaternion. If none given, they are all equal to 1.

Returns:

q – Average quaternion.

Return type:

numpy.ndarray

Example

>>> qts = np.tile([1., -2., 3., -4], (5, 1))    # Five equal quaternions
>>> v = np.random.standard_normal((5, 4))*0.1   # Zero-mean gaussian noise
>>> Q = QuaternionArray(qts + v)
>>> Q.view()
QuaternionArray([[ 0.17614144, -0.39173347,  0.56303067, -0.70605634],
                 [ 0.17607515, -0.3839024 ,  0.52673809, -0.73767437],
                 [ 0.16823806, -0.35898889,  0.53664261, -0.74487424],
                 [ 0.17094453, -0.3723117 ,  0.54109885, -0.73442086],
                 [ 0.1862619 , -0.38421818,  0.5260265 , -0.73551276]])
>>> Q.average()
array([-0.17557859,  0.37832975, -0.53884688,  0.73190355])

The result is as expected, remembering that a quaternion with opposite signs on each element represents the same orientation.