SLAM/Inertial/ES15PoseCorrection.mo
Download original .moGitHub source
Documentation: SLAM.Inertial.ES15PoseCorrection
within SLAM.Inertial;
// One complete ES15 pose-observation correction, matching ErrorStateFilter.correct_pose.
// World-additive p/v, right-local theta, body accel/gyro biases; Hamilton wxyz output.
// Compile this source together with SPD6Solve.mo for ES15CorrectionSolve below.
// Same-frame Modelica composition (no host numerical solve):
// 1. ES15CorrectionSolve(A=observation_covariance,B=zeros) supplies its valid flag.
// 2. Evaluate this component's innovation_covariance/solve_rhs from the prior state.
// 3. ES15CorrectionSolve(A=innovation_covariance,B=solve_rhs) supplies solved/solve_valid.
// 4. Reevaluate this component with those results; commit only its final next outputs.
// The preview uses solve_valid=0; never feed its diagnostics back as another observation.
// The caller supplies a finite valid nominal state and symmetric positive-definite P.
// Covariance inputs have no replacement/default covariance. Feed every entry per frame.
// WASM's finite-value input boundary rejects NaN/Infinity before this numerical component.
// Outputs are a proposed next frame: rejection preserves every nominal/P entry.
model ES15PoseCorrection
input Real rotation[3,3] = identity(3);
input Real position[3] = {0.0,0.0,0.0};
input Real velocity[3] = {0.0,0.0,0.0};
input Real accel_bias[3] = {0.0,0.0,0.0};
input Real gyro_bias[3] = {0.0,0.0,0.0};
input Real covariance[15,15];
input Real observed_rotation[3,3] = identity(3);
input Real observed_position[3] = {0.0,0.0,0.0};
input Real observation_covariance[6,6];
input Real solved[6,16];
input Real solve_valid = 0.0;
input Real observation_covariance_valid = 0.0;
input Real accepted_count = 0.0;
input Real rejected_count = 0.0;
input Real last_nis = 0.0;
output Real accepted;
output Real next_accepted_count; output Real next_rejected_count; output Real next_last_nis;
output Real next_position[3]; output Real next_velocity[3];
output Real next_accel_bias[3]; output Real next_gyro_bias[3];
output Real next_rotation[3,3]; output Real next_quaternion[4];
output Real next_covariance[15,15];
output Real H[6,15]; output Real cross_covariance[15,6];
output Real innovation_covariance[6,6]; output Real solve_rhs[6,16];
output Real innovation[6]; output Real gain[15,6]; output Real correction[15];
output Real joseph_covariance[15,15];
protected
Real gram[3,3]; Real rotation_checks[3,3]; Real determinant;
Real admissible; Real raw_nis; Real correction_checks[15];
Real relative[3,3]; Real angle; Real antisymmetric[3]; Real log_scale;
Real relative_raw_q[4]; Real relative_q[4]; Real relative_q_norm;
Real relative_q_sine; Real relative_q_scale;
Real qr; Real qx; Real qy; Real qz;
Real delta_angle; Real a; Real b; Real W[3,3]; Real increment[3,3];
Real proposed_rotation[3,3]; Real proposed_accel_bias[3]; Real proposed_gyro_bias[3];
Real residual_map[15,15]; Real left[15,15]; Real noise_left[15,6];
Real reset_a; Real reset_b; Real reset_jacobian[3,3];
Real reset_left[15,15]; Real reset_proposed[15,15];
Real output_raw_q[4]; Real output_q_norm; Real ow; Real ox; Real oy; Real oz;
equation
gram = transpose(observed_rotation)*observed_rotation;
determinant = observed_rotation[1,1]*(observed_rotation[2,2]*observed_rotation[3,3]-observed_rotation[2,3]*observed_rotation[3,2])
- observed_rotation[1,2]*(observed_rotation[2,1]*observed_rotation[3,3]-observed_rotation[2,3]*observed_rotation[3,1])
+ observed_rotation[1,3]*(observed_rotation[2,1]*observed_rotation[3,2]-observed_rotation[2,2]*observed_rotation[3,1]);
for i in 1:3 loop
for j in 1:3 loop
rotation_checks[i,j] = if noEvent(abs(gram[i,j]-(if i == j then 1.0 else 0.0)) <= 1e-6) then 0.0 else 1.0;
end for;
end for;
admissible = if noEvent(sum(rotation_checks[i,j] for i in 1:3, j in 1:3) < 0.5 and abs(determinant-1.0) <= 1e-6 and observation_covariance_valid > 0.5) then 1.0 else 0.0;
relative = transpose(rotation)*observed_rotation;
angle = acos(min(1.0,max(-1.0,(relative[1,1]+relative[2,2]+relative[3,3]-1.0)/2.0)));
antisymmetric = {relative[3,2]-relative[2,3],relative[1,3]-relative[3,1],relative[2,1]-relative[1,2]};
// Stable dominant-component Hamilton quaternion, including the pi branch.
qr = 2.0*sqrt(max(0.0,1.0+relative[1,1]+relative[2,2]+relative[3,3]));
qx = 2.0*sqrt(max(0.0,1.0+relative[1,1]-relative[2,2]-relative[3,3]));
qy = 2.0*sqrt(max(0.0,1.0-relative[1,1]+relative[2,2]-relative[3,3]));
qz = 2.0*sqrt(max(0.0,1.0-relative[1,1]-relative[2,2]+relative[3,3]));
relative_raw_q = if noEvent(relative[1,1]+relative[2,2]+relative[3,3] > 0.0) then
{qr/4.0,antisymmetric[1]/max(qr,1e-12),antisymmetric[2]/max(qr,1e-12),antisymmetric[3]/max(qr,1e-12)}
elseif noEvent(relative[1,1] > relative[2,2] and relative[1,1] > relative[3,3]) then
{antisymmetric[1]/max(qx,1e-12),qx/4.0,(relative[1,2]+relative[2,1])/max(qx,1e-12),(relative[1,3]+relative[3,1])/max(qx,1e-12)}
elseif noEvent(relative[2,2] > relative[3,3]) then
{antisymmetric[2]/max(qy,1e-12),(relative[1,2]+relative[2,1])/max(qy,1e-12),qy/4.0,(relative[2,3]+relative[3,2])/max(qy,1e-12)}
else {antisymmetric[3]/max(qz,1e-12),(relative[1,3]+relative[3,1])/max(qz,1e-12),(relative[2,3]+relative[3,2])/max(qz,1e-12),qz/4.0};
relative_q_norm = sqrt(sum(relative_raw_q[k]^2 for k in 1:4));
relative_q = (if noEvent(relative_raw_q[1] < 0.0) then -1.0 else 1.0)*relative_raw_q/max(relative_q_norm,1e-12);
relative_q_sine = sqrt(relative_q[2]^2+relative_q[3]^2+relative_q[4]^2);
relative_q_scale = 2.0*atan2(relative_q_sine,relative_q[1])/max(relative_q_sine,1e-12);
log_scale = if noEvent(angle < 1e-7) then 0.5 else angle/(2.0*max(sin(angle),1e-12));
for i in 1:3 loop
innovation[i] = observed_position[i]-position[i];
innovation[i+3] = if noEvent(angle < 3.141592653589793-1e-5) then log_scale*antisymmetric[i] else relative_q_scale*relative_q[i+1];
end for;
for i in 1:6 loop
for j in 1:15 loop
H[i,j] = if i <= 3 and j == i or i > 3 and j == i+3 then 1.0 else 0.0;
solve_rhs[i,j] = cross_covariance[j,i];
end for;
solve_rhs[i,16] = innovation[i];
end for;
cross_covariance = covariance*transpose(H);
innovation_covariance = H*cross_covariance+observation_covariance;
for i in 1:15 loop
for j in 1:6 loop
gain[i,j] = solved[j,i];
end for;
correction[i] = sum(gain[i,j]*innovation[j] for j in 1:6);
correction_checks[i] = if noEvent(abs(correction[i]) <= 1.7976931348623157e308) then 0.0 else 1.0;
end for;
raw_nis = sum(innovation[i]*solved[i,16] for i in 1:6);
next_last_nis = if noEvent(admissible > 0.5) then (if noEvent(abs(raw_nis) <= 1.7976931348623157e308) then raw_nis else 0.0) else last_nis;
proposed_accel_bias = accel_bias+{correction[10],correction[11],correction[12]};
proposed_gyro_bias = gyro_bias+{correction[13],correction[14],correction[15]};
accepted = if noEvent(admissible > 0.5 and solve_valid > 0.5 and raw_nis >= 0.0 and raw_nis <= 22.46 and
innovation[4]^2+innovation[5]^2+innovation[6]^2 <= 0.35^2 and sum(correction_checks[i] for i in 1:15) < 0.5 and
sum(proposed_accel_bias[i]^2 for i in 1:3) <= 2.0^2 and sum(proposed_gyro_bias[i]^2 for i in 1:3) <= 0.3^2) then 1.0 else 0.0;
next_accepted_count = accepted_count+accepted;
next_rejected_count = rejected_count+1.0-accepted;
residual_map = identity(15)-gain*H;
left = residual_map*covariance;
noise_left = gain*observation_covariance;
joseph_covariance = left*transpose(residual_map)+noise_left*transpose(gain);
delta_angle = sqrt(correction[7]^2+correction[8]^2+correction[9]^2);
a = if noEvent(delta_angle < 1e-7) then 1.0 else sin(delta_angle)/max(delta_angle,1e-7);
b = if noEvent(delta_angle < 1e-7) then 0.5 else (1.0-cos(delta_angle))/max(delta_angle^2,1e-14);
W = [0.0,-correction[9],correction[8];correction[9],0.0,-correction[7];-correction[8],correction[7],0.0];
increment = identity(3)+a*W+b*(W*W);
proposed_rotation = rotation*increment;
// Exact right-local reset, including every p/v/bias-attitude cross term.
reset_a = if noEvent(delta_angle < 1e-6) then 0.5 else (1.0-cos(delta_angle))/max(delta_angle^2,1e-12);
reset_b = if noEvent(delta_angle < 1e-6) then 1.0/6.0 else (delta_angle-sin(delta_angle))/max(delta_angle^3,1e-18);
reset_jacobian = identity(3)-reset_a*W+reset_b*(W*W);
for j in 1:15 loop
for i in 1:6 loop
reset_left[i,j] = joseph_covariance[i,j];
end for;
for i in 1:3 loop
reset_left[i+6,j] = sum(reset_jacobian[i,k]*joseph_covariance[k+6,j] for k in 1:3);
end for;
for i in 10:15 loop
reset_left[i,j] = joseph_covariance[i,j];
end for;
end for;
for i in 1:15 loop
for j in 1:6 loop
reset_proposed[i,j] = reset_left[i,j];
end for;
for j in 1:3 loop
reset_proposed[i,j+6] = sum(reset_left[i,k+6]*reset_jacobian[j,k] for k in 1:3);
end for;
for j in 10:15 loop
reset_proposed[i,j] = reset_left[i,j];
end for;
end for;
for i in 1:3 loop
next_position[i] = if noEvent(accepted > 0.5) then position[i]+correction[i] else position[i];
next_velocity[i] = if noEvent(accepted > 0.5) then velocity[i]+correction[i+3] else velocity[i];
next_accel_bias[i] = if noEvent(accepted > 0.5) then proposed_accel_bias[i] else accel_bias[i];
next_gyro_bias[i] = if noEvent(accepted > 0.5) then proposed_gyro_bias[i] else gyro_bias[i];
for j in 1:3 loop
next_rotation[i,j] = if noEvent(accepted > 0.5) then proposed_rotation[i,j] else rotation[i,j];
end for;
end for;
for i in 1:15 loop
for j in 1:15 loop
next_covariance[i,j] = if noEvent(accepted > 0.5) then 0.5*(reset_proposed[i,j]+reset_proposed[j,i]) else covariance[i,j];
end for;
end for;
ow = 2.0*sqrt(max(0.0,1.0+next_rotation[1,1]+next_rotation[2,2]+next_rotation[3,3]));
ox = 2.0*sqrt(max(0.0,1.0+next_rotation[1,1]-next_rotation[2,2]-next_rotation[3,3]));
oy = 2.0*sqrt(max(0.0,1.0-next_rotation[1,1]+next_rotation[2,2]-next_rotation[3,3]));
oz = 2.0*sqrt(max(0.0,1.0-next_rotation[1,1]-next_rotation[2,2]+next_rotation[3,3]));
output_raw_q = if noEvent(next_rotation[1,1]+next_rotation[2,2]+next_rotation[3,3] > 0.0) then
{ow/4.0,(next_rotation[3,2]-next_rotation[2,3])/max(ow,1e-12),(next_rotation[1,3]-next_rotation[3,1])/max(ow,1e-12),(next_rotation[2,1]-next_rotation[1,2])/max(ow,1e-12)}
elseif noEvent(next_rotation[1,1] > next_rotation[2,2] and next_rotation[1,1] > next_rotation[3,3]) then
{(next_rotation[3,2]-next_rotation[2,3])/max(ox,1e-12),ox/4.0,(next_rotation[1,2]+next_rotation[2,1])/max(ox,1e-12),(next_rotation[1,3]+next_rotation[3,1])/max(ox,1e-12)}
elseif noEvent(next_rotation[2,2] > next_rotation[3,3]) then
{(next_rotation[1,3]-next_rotation[3,1])/max(oy,1e-12),(next_rotation[1,2]+next_rotation[2,1])/max(oy,1e-12),oy/4.0,(next_rotation[2,3]+next_rotation[3,2])/max(oy,1e-12)}
else {(next_rotation[2,1]-next_rotation[1,2])/max(oz,1e-12),(next_rotation[1,3]+next_rotation[3,1])/max(oz,1e-12),(next_rotation[2,3]+next_rotation[3,2])/max(oz,1e-12),oz/4.0};
output_q_norm = sqrt(sum(output_raw_q[k]^2 for k in 1:4));
next_quaternion = (if noEvent(output_raw_q[1] < 0.0) then -1.0 else 1.0)*output_raw_q/max(output_q_norm,1e-12);
end ES15PoseCorrection;