Skip to content

Commit 491aa06

Browse files
feat: coning and sculling algorithm (#100)
1 parent 0f2f912 commit 491aa06

5 files changed

Lines changed: 2600 additions & 0 deletions

File tree

src/smsfusion/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from . import benchmark, calibrate, constants, noise
2+
from ._coning_sculling import ConingScullingAlg
23
from ._ins import AHRS, VRU, AidedINS, FixedNED, StrapdownINS, gravity
34
from ._smoothing import FixedIntervalSmoother
45
from ._transforms import quaternion_from_euler
@@ -16,4 +17,5 @@
1617
"StrapdownINS",
1718
"VRU",
1819
"quaternion_from_euler",
20+
"ConingScullingAlg",
1921
]

src/smsfusion/_coning_sculling.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import numpy as np
2+
from numpy.typing import ArrayLike
3+
4+
from smsfusion._vectorops import _cross
5+
6+
7+
class ConingScullingAlg:
8+
"""
9+
Coning and sculling algorithm.
10+
11+
Integrates the specific force and angular rate measurements to coning and
12+
sculling corrected velocity (dvel) and attitude (dtheta) changes.
13+
14+
Can be used in a strapdown algorithm as:
15+
16+
vel[m+1] = vel[m] + R(q[m]) @ dvel[m] + dvel_corr
17+
q[m+1] = q[m] ⊗ dq(dtheta[m])
18+
19+
where,
20+
21+
dvel_corr = [0, 0, g * dt] (if 'NED')
22+
dvel_corr = [0, 0, -g * dt] (if 'ENU')
23+
24+
and,
25+
26+
- dvel[m] is the sculling integral, i.e., the velocity vector change (no gravity
27+
correction) from time step m to m+1.
28+
- dtheta[m] is the coning integral, i.e., the rotation vector change from time
29+
step m to m+1.
30+
- dq(dtheta[m]) is the unit quaternion representation of the rotation increment
31+
over the interval [m, m+1].
32+
- R(q[m]) is the rotation matrix (body-to-nav) corresponding to the attitude
33+
quaternion q[m].
34+
35+
Here, ⊗ denotes quaternion multiplication (Hamilton product).
36+
37+
The coning and sculling integrals are computed using a 2nd order algorithm as
38+
described in [1]_ and [2]_.
39+
40+
References
41+
----------
42+
.. [1] Savage, Paul G., Strapdown System Algorithms, AD-P003 621, https://apps.dtic.mil/sti/tr/pdf/ADP003621.pdf
43+
.. [2] Savage, Paul G., Strapdown Analytics, 2nd Edition, Part 1, 2007, https://strapdownassociates.com/Strapdown%20Analytics%20II%20Part%201.pdf
44+
"""
45+
46+
def __init__(self, fs: float):
47+
self._fs = fs
48+
self._dt = 1.0 / fs
49+
50+
# Coning params
51+
self._theta = np.zeros(3, dtype=float)
52+
self._dtheta_con = np.zeros(3, dtype=float)
53+
self._dtheta_prev = np.zeros(3, dtype=float)
54+
55+
# Sculling params
56+
self._vel = np.zeros(3, dtype=float)
57+
self._dvel_scul = np.zeros(3, dtype=float)
58+
self._dv_prev = np.zeros(3, dtype=float)
59+
60+
def update(self, f: ArrayLike, w: ArrayLike, degrees: bool = False):
61+
"""
62+
Update the coning (dtheta) and sculling (dvel) integrals using new measurements.
63+
64+
Parameters
65+
----------
66+
f : array-like, shape (3,)
67+
Specific force (acceleration + gravity) measurements [f_x, f_y, f_z],
68+
where f_x, f_y and f_z are specific forces along the x-, y-, and z-axis,
69+
respectively.
70+
w : array-like, shape (3,)
71+
Angular rate measurements [w_x, w_y, w_z], where w_x, w_y and w_z are
72+
angular rates about the x-, y-, and z-axis, respectively.
73+
degrees : bool, default False
74+
Specifies whether the angular rates are given in degrees or radians (default).
75+
"""
76+
f = np.asarray(f, dtype=float)
77+
w = np.asarray(w, dtype=float)
78+
79+
if degrees:
80+
w = (np.pi / 180.0) * w
81+
82+
# View for readability
83+
theta = self._theta
84+
dtheta_con = self._dtheta_con
85+
dtheta_prev = self._dtheta_prev
86+
vel = self._vel
87+
dvel_scul = self._dvel_scul
88+
dv_prev = self._dv_prev
89+
90+
dv = f * self._dt # backward Euler
91+
dtheta = w * self._dt # backward Euler
92+
93+
# Sculling update (2nd order)
94+
# See Eq. (7.2.2.2.2-15) in ref [2]_ and Eq. (56) in ref [1]_
95+
dvel_scul += 0.5 * (
96+
_cross(theta + (1.0 / 6.0) * dtheta_prev, dv)
97+
+ _cross(vel + (1.0 / 6.0) * dv_prev, dtheta)
98+
)
99+
vel += dv
100+
101+
# Coning update
102+
# See Eq. (26) in ref [1]_
103+
dtheta_con += 0.5 * _cross(theta + (1.0 / 6.0) * dtheta_prev, dtheta)
104+
theta += dtheta
105+
106+
dv_prev[:] = dv
107+
dtheta_prev[:] = dtheta
108+
109+
def dtheta(self, degrees=False):
110+
"""
111+
Peek at the accumulated 'body attitude change' vector. I.e., the rotation
112+
vector describing the total rotation over all samples since initialization
113+
(or last reset).
114+
115+
Parameters
116+
----------
117+
degrees : bool, default False
118+
Specifies whether the returned rotation vector should be in degrees
119+
or radians (default).
120+
"""
121+
dtheta = self._theta + self._dtheta_con
122+
return np.degrees(dtheta) if degrees else dtheta
123+
124+
@property
125+
def _dvel_rot(self):
126+
return 0.5 * _cross(self._theta, self._vel)
127+
128+
def dvel(self):
129+
"""
130+
Peek at the accumulated specific force velocity change vector. I.e.,
131+
the total change in velocity (no gravity correction) over all samples since
132+
initialization (or last reset).
133+
"""
134+
return self._vel + self._dvel_rot + self._dvel_scul
135+
136+
def flush(self, degrees=False):
137+
"""
138+
Return dtheta (the accumulated 'body attitude change' vector) and
139+
dvel (the accumulated specific force velocity change vector), and reset
140+
the coning (dtheta) and sculling (dvel) integrals to zero.
141+
142+
Parameters
143+
----------
144+
degrees : bool, default False
145+
Specifies whether the returned rotation vector should be in degrees
146+
or radians (default).
147+
148+
Returns
149+
-------
150+
dtheta : ndarray, shape (3,)
151+
The accumulated 'body attitude change' vector, see :meth:`dtheta`.
152+
dvel : ndarray, shape (3,)
153+
The accumulated specific force velocity change vector, see :meth:`dvel`.
154+
"""
155+
dtheta = self.dtheta(degrees=degrees)
156+
dvel = self.dvel()
157+
158+
self._theta[:] = np.zeros(3, dtype=float)
159+
self._dtheta_con[:] = np.zeros(3, dtype=float)
160+
self._dvel_scul[:] = np.zeros(3, dtype=float)
161+
self._vel[:] = np.zeros(3, dtype=float)
162+
return dtheta, dvel

0 commit comments

Comments
 (0)