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.