function
Estimation.StrapdownINS.preintegrateImuStep
Compose one IMU interval into a closed-form SE_2(3) preintegral
Accumulate an interval
Start a new preintegral with zero position and velocity, identity quaternion
{1,0,0,0}, and zero bias Jacobians. Choose fixed gyroscope and accelerometer bias
anchors for the accumulation window. For each positive dt, feed the
previous outputs back as the next inputs and supply calibrated body FLU IMU data.
Position and velocity increments are expressed in the body frame at the window
start; gravity is applied by navigation prediction, not included here.
Zero-order or first-order hold
The default holds the current sample over the interval.
useFirstOrderHold=true uses the supplied interval-start sample and
current interval-end sample. Both endpoints must describe that same interval.
It evaluates the manuscript's retained Magnus exponent and differentiates its
mixed exponential and composition; it does not evaluate the continuous-flow
error certificate or an exact stochastic covariance.
Publish the accumulated sample
Copy the deltas and all five Jacobians to ImuSample, together with the unchanged bias anchors, total integration duration and interval-end timestamp. Consume the packet once. Use correctPreintegratedImu when the estimated bias changes. See the references in StrapdownINS for the underlying ZOH, FOH and bias-anchor results.
Public declarations
Explicit source declarations; dimensions, modifications and defaults are shown as written, without evaluation.
| Role | Name | Type | Dimensions / attributes / default | Description |
|---|---|---|---|---|
| input | previousDeltaPosition_m | Real | [3] | |
| input | previousDeltaVelocity_m_s | Real | [3] | |
| input | previousDeltaQuaternion | Real | [4] | |
| input | previousRotationGyroscopeBiasJacobian_s | Real | [3, 3] | |
| input | previousVelocityGyroscopeBiasJacobian_m | Real | [3, 3] | |
| input | previousVelocityAccelerometerBiasJacobian_s | Real | [3, 3] | |
| input | previousPositionGyroscopeBiasJacobian_m_s | Real | [3, 3] | |
| input | previousPositionAccelerometerBiasJacobian_s2 | Real | [3, 3] | |
| input | angularVelocityMeasuredBodyFlu_rad_s | Real | [3] | |
| input | specificForceMeasuredBodyFlu_m_s2 | Real | [3] | |
| input | gyroscopeBiasLinearizationBodyFlu_rad_s | Real | [3] | |
| input | accelerometerBiasLinearizationBodyFlu_m_s2 | Real | [3] | |
| input | dt | Real | (unit = "s") | |
| input | useFirstOrderHold | Boolean | = false | True treats the interval input as linear between the previous and the current sample; false holds the current sample |
| input | previousAngularVelocityMeasuredBodyFlu_rad_s | Real | [3] = zeros(3) | Measured angular velocity at the interval start (first-order hold only) |
| input | previousSpecificForceMeasuredBodyFlu_m_s2 | Real | [3] = zeros(3) | Measured specific force at the interval start (first-order hold only) |
| output | deltaPosition_m | Real | [3] | |
| output | deltaVelocity_m_s | Real | [3] | |
| output | deltaQuaternion | Real | [4] | |
| output | rotationGyroscopeBiasJacobian_s | Real | [3, 3] | |
| output | velocityGyroscopeBiasJacobian_m | Real | [3, 3] | |
| output | velocityAccelerometerBiasJacobian_s | Real | [3, 3] | |
| output | positionGyroscopeBiasJacobian_m_s | Real | [3, 3] | |
| output | positionAccelerometerBiasJacobian_s2 | Real | [3, 3] |
Modelica declarations and implementation
Show parameters, interfaces and source
Parameters, inputs, outputs, defaults and equations from the source file.
function preintegrateImuStep
"Compose one IMU interval into a closed-form SE_2(3) preintegral"
input Real previousDeltaPosition_m[3];
input Real previousDeltaVelocity_m_s[3];
input Real previousDeltaQuaternion[4];
input Real previousRotationGyroscopeBiasJacobian_s[3, 3];
input Real previousVelocityGyroscopeBiasJacobian_m[3, 3];
input Real previousVelocityAccelerometerBiasJacobian_s[3, 3];
input Real previousPositionGyroscopeBiasJacobian_m_s[3, 3];
input Real previousPositionAccelerometerBiasJacobian_s2[3, 3];
input Real angularVelocityMeasuredBodyFlu_rad_s[3];
input Real specificForceMeasuredBodyFlu_m_s2[3];
input Real gyroscopeBiasLinearizationBodyFlu_rad_s[3];
input Real accelerometerBiasLinearizationBodyFlu_m_s2[3];
input Real dt(unit = "s");
input Boolean useFirstOrderHold = false
"True treats the interval input as linear between the previous and the current sample; false holds the current sample";
input Real previousAngularVelocityMeasuredBodyFlu_rad_s[3] = zeros(3)
"Measured angular velocity at the interval start (first-order hold only)";
input Real previousSpecificForceMeasuredBodyFlu_m_s2[3] = zeros(3)
"Measured specific force at the interval start (first-order hold only)";
output Real deltaPosition_m[3];
output Real deltaVelocity_m_s[3];
output Real deltaQuaternion[4];
output Real rotationGyroscopeBiasJacobian_s[3, 3];
output Real velocityGyroscopeBiasJacobian_m[3, 3];
output Real velocityAccelerometerBiasJacobian_s[3, 3];
output Real positionGyroscopeBiasJacobian_m_s[3, 3];
output Real positionAccelerometerBiasJacobian_s2[3, 3];
protected
Real correctedAngularVelocity_rad_s[3];
Real correctedSpecificForce_m_s2[3];
Real startAngularVelocity_rad_s[3];
Real startSpecificForce_m_s2[3];
Real angularVelocityDelta_rad_s[3];
Real specificForceDelta_m_s2[3];
Real rotationIncrement_rad[3];
Real velocityIncrement_m_s[3];
Real positionIncrement_m[3];
Real bodyIncrement[9];
Real coupling[2, 2];
Real incrementTranslationJacobian[6, 9];
Real rotationBiasJacobian[3, 3];
Real velocityGyroBiasJacobian[3, 3];
Real translationBiasJacobian[6, 6];
Real translationIncrement[3, 2];
Real previousRotation[3, 3];
Real previousExtendedPose[10];
Real updatedExtendedPose[10];
Real rotationIncrement[3, 3];
algorithm
correctedAngularVelocity_rad_s := angularVelocityMeasuredBodyFlu_rad_s
- gyroscopeBiasLinearizationBodyFlu_rad_s;
correctedSpecificForce_m_s2 := specificForceMeasuredBodyFlu_m_s2
- accelerometerBiasLinearizationBodyFlu_m_s2;
if useFirstOrderHold then
// First-order hold: the body input is linear between the interval-start
// and interval-end samples. The right increment is the truncated Magnus
// exponent T*N0 + (T^2/2)*N1 + (T^3/12)*[N0, N1] whose single Lie
// bracket decomposes into the classical coning, sculling, and position
// (scrolling) corrections.
//
// Magnus expansion: Magnus, Comm. Pure Appl. Math. 7(4):649-673, 1954;
// survey Blanes, Casas, Oteo and Ros, Phys. Rep. 470:151-238, 2009. The
// truncation to third order, its exactly-vanishing T^4 grade, and the
// O(T^5) residual are Lemma 2 and Theorem 2 of Reynolds, Condie,
// Perseghetti and Goppert, "First-Order-Hold Magnus Preintegration on
// SE_n(3) with Computable Flow-Error Bounds" (ACC 2027 manuscript).
// Proposition 3 gives the coning/sculling/scrolling split. The coning term
// coincides with the classical two-sample correction of Bortz (1971) and
// Savage (1998, parts 1 and 2). See the References block on
// Estimation.StrapdownINS for the full entries. The deltas are formed as sample differences
// (the constant bias anchor cancels) and the cross terms use those
// differences directly, never two nearly parallel consecutive samples,
// to avoid cancellation in single-precision generated code.
startAngularVelocity_rad_s :=
previousAngularVelocityMeasuredBodyFlu_rad_s
- gyroscopeBiasLinearizationBodyFlu_rad_s;
startSpecificForce_m_s2 := previousSpecificForceMeasuredBodyFlu_m_s2
- accelerometerBiasLinearizationBodyFlu_m_s2;
angularVelocityDelta_rad_s := angularVelocityMeasuredBodyFlu_rad_s
- previousAngularVelocityMeasuredBodyFlu_rad_s;
specificForceDelta_m_s2 := specificForceMeasuredBodyFlu_m_s2
- previousSpecificForceMeasuredBodyFlu_m_s2;
rotationIncrement_rad := (startAngularVelocity_rad_s
+ 0.5 * angularVelocityDelta_rad_s) * dt
+ (dt * dt / 12.0)
* LieGroups.SO3.Quat.wedge(startAngularVelocity_rad_s)
* angularVelocityDelta_rad_s;
velocityIncrement_m_s := (startSpecificForce_m_s2
+ 0.5 * specificForceDelta_m_s2) * dt
+ (dt * dt / 12.0)
* (LieGroups.SO3.Quat.wedge(startAngularVelocity_rad_s)
* specificForceDelta_m_s2
- LieGroups.SO3.Quat.wedge(angularVelocityDelta_rad_s)
* startSpecificForce_m_s2);
positionIncrement_m := -(dt * dt / 12.0) * specificForceDelta_m_s2;
else
startAngularVelocity_rad_s := correctedAngularVelocity_rad_s;
startSpecificForce_m_s2 := correctedSpecificForce_m_s2;
angularVelocityDelta_rad_s := zeros(3);
specificForceDelta_m_s2 := zeros(3);
rotationIncrement_rad := correctedAngularVelocity_rad_s * dt;
velocityIncrement_m_s := correctedSpecificForce_m_s2 * dt;
positionIncrement_m := zeros(3);
end if;
bodyIncrement := cat(1, positionIncrement_m, velocityIncrement_m_s,
rotationIncrement_rad);
coupling := [0.0, dt; 0.0, 0.0];
previousExtendedPose := cat(1, previousDeltaPosition_m,
previousDeltaVelocity_m_s, previousDeltaQuaternion);
// The mixed exponential is the closed-form solution from Lin, Pant,
// Perseghetti, and Goppert, "On Closed-Form Preintegration for a Class of
// Mixed-Invariant Systems in SE_n(3)", IEEE L-CSS, 2025. With no world
// input this integrates one body-rate/specific-force interval and includes
// velocity-to-position coupling through the nilpotent B block. Under the
// first-order hold the scrolling correction feeds the position slot of the
// body tangent, which is identically zero under the zero-order hold; the
// bracket has no time-block component, so the closed-form machinery
// applies verbatim.
updatedExtendedPose := LieGroups.SE23.Quat.exp_mixed(
previousExtendedPose,
bodyIncrement, zeros(9), coupling);
deltaPosition_m := updatedExtendedPose[1:3];
deltaVelocity_m_s := updatedExtendedPose[4:6];
deltaQuaternion := LieGroups.SO3.Quat.normalize(
updatedExtendedPose[7:10]);
// Equation (11) differentiates the retained exponent, not its physical
// translation increments. Apply the chain rule through the same closed-form
// increment block used above. This also differentiates the position-column
// correction through rotation and transports the previous right attitude
// sensitivity through the full increment, without a midpoint approximation.
// Columns are {gyro bias, accelerometer bias}; rows are {position, velocity,
// rotation}. Sample differences and the time block are bias-invariant.
rotationBiasJacobian := -dt * identity(3)
+ (dt * dt / 12.0) * LieGroups.SO3.Quat.wedge(angularVelocityDelta_rad_s);
velocityGyroBiasJacobian := (dt * dt / 12.0)
* LieGroups.SO3.Quat.wedge(specificForceDelta_m_s2);
translationIncrement := LieGroups.SE23.Quat.mixed_increment_matrix(
bodyIncrement, coupling);
incrementTranslationJacobian :=
LieGroups.SE23.Quat.mixed_increment_matrix_jacobian(
bodyIncrement, coupling);
translationBiasJacobian := cat(2,
incrementTranslationJacobian[:, 4:6] * velocityGyroBiasJacobian
+ incrementTranslationJacobian[:, 7:9] * rotationBiasJacobian,
incrementTranslationJacobian[:, 4:6] * rotationBiasJacobian);
previousRotation := LieGroups.SO3.Quat.to_DCM(previousDeltaQuaternion);
rotationIncrement := LieGroups.SO3.Quat.to_DCM(
LieGroups.SO3.Quat.exp_map(rotationIncrement_rad));
rotationGyroscopeBiasJacobian_s := transpose(rotationIncrement)
* previousRotationGyroscopeBiasJacobian_s
+ LieGroups.SO3.Quat.right_jacobian_exact(rotationIncrement_rad)
* rotationBiasJacobian;
velocityGyroscopeBiasJacobian_m := previousVelocityGyroscopeBiasJacobian_m
+ previousRotation * (translationBiasJacobian[1:3, 1:3]
- LieGroups.SO3.Quat.wedge(translationIncrement[:, 1])
* previousRotationGyroscopeBiasJacobian_s);
velocityAccelerometerBiasJacobian_s :=
previousVelocityAccelerometerBiasJacobian_s
+ previousRotation * translationBiasJacobian[1:3, 4:6];
positionGyroscopeBiasJacobian_m_s :=
previousPositionGyroscopeBiasJacobian_m_s
+ previousVelocityGyroscopeBiasJacobian_m * dt
+ previousRotation * (translationBiasJacobian[4:6, 1:3]
- LieGroups.SO3.Quat.wedge(translationIncrement[:, 2])
* previousRotationGyroscopeBiasJacobian_s);
positionAccelerometerBiasJacobian_s2 :=
previousPositionAccelerometerBiasJacobian_s2
+ previousVelocityAccelerometerBiasJacobian_s * dt
+ previousRotation * translationBiasJacobian[4:6, 4:6];
annotation(Documentation(info = "<html>
<h4>Accumulate an interval</h4>
<p>Start a new preintegral with zero position and velocity, identity quaternion
{1,0,0,0}, and zero bias Jacobians. Choose fixed gyroscope and accelerometer bias
anchors for the accumulation window. For each positive <code>dt</code>, feed the
previous outputs back as the next inputs and supply calibrated body FLU IMU data.
Position and velocity increments are expressed in the body frame at the window
start; gravity is applied by navigation prediction, not included here.</p>
<h4>Zero-order or first-order hold</h4>
<p>The default holds the current sample over the interval.
<code>useFirstOrderHold=true</code> uses the supplied interval-start sample and
current interval-end sample. Both endpoints must describe that same interval.
It evaluates the manuscript's retained Magnus exponent and differentiates its
mixed exponential and composition; it does not evaluate the continuous-flow
error certificate or an exact stochastic covariance.</p>
<h4>Publish the accumulated sample</h4>
<p>Copy the deltas and all five Jacobians to
<a href=\"modelica://Avionics.ImuSample\">ImuSample</a>, together with the unchanged
bias anchors, total integration duration and interval-end timestamp. Consume the
packet once. Use
<a href=\"modelica://Estimation.StrapdownINS.correctPreintegratedImu\">correctPreintegratedImu</a>
when the estimated bias changes. See the references in
<a href=\"modelica://Estimation.StrapdownINS\">StrapdownINS</a> for the underlying
ZOH, FOH and bias-anchor results.</p>
</html>"));
end preintegrateImuStep;