ekf2: fusion always succeeds, do not return boolean

This commit is contained in:
bresch
2024-09-24 10:48:14 -04:00
committed by Daniel Agar
parent 23f343aa3a
commit 35efe44a6b
15 changed files with 109 additions and 157 deletions
@@ -215,17 +215,13 @@ void Ekf::fuseAirspeed(const airspeedSample &airspeed_sample, estimator_aid_sour
K.slice<State::wind_vel.dof, 1>(State::wind_vel.idx, 0) = K_wind; K.slice<State::wind_vel.dof, 1>(State::wind_vel.idx, 0) = K_wind;
} }
const bool is_fused = measurementUpdate(K, H, aid_src.observation_variance, aid_src.innovation); measurementUpdate(K, H, aid_src.observation_variance, aid_src.innovation);
aid_src.fused = is_fused; aid_src.fused = true;
_fault_status.flags.bad_airspeed = !is_fused; aid_src.time_last_fuse = _time_delayed_us;
if (is_fused) { if (!update_wind_only) {
aid_src.time_last_fuse = _time_delayed_us; _time_last_hor_vel_fuse = _time_delayed_us;
if (!update_wind_only) {
_time_last_hor_vel_fuse = _time_delayed_us;
}
} }
} }
@@ -107,8 +107,6 @@ void Ekf::fuseDrag(const dragSample &drag_sample)
bcoef_inv(1) = bcoef_inv(0); bcoef_inv(1) = bcoef_inv(0);
} }
bool fused[] {false, false};
Vector2f observation{}; Vector2f observation{};
Vector2f observation_variance{R_ACC, R_ACC}; Vector2f observation_variance{R_ACC, R_ACC};
Vector2f innovation{}; Vector2f innovation{};
@@ -152,7 +150,7 @@ void Ekf::fuseDrag(const dragSample &drag_sample)
if (innovation_variance(axis_index) < R_ACC) { if (innovation_variance(axis_index) < R_ACC) {
// calculation is badly conditioned // calculation is badly conditioned
break; return;
} }
const float test_ratio = sq(innovation(axis_index)) / (sq(innov_gate) * innovation_variance(axis_index)); const float test_ratio = sq(innovation(axis_index)) / (sq(innov_gate) * innovation_variance(axis_index));
@@ -164,9 +162,7 @@ void Ekf::fuseDrag(const dragSample &drag_sample)
VectorState K = P * H / innovation_variance(axis_index); VectorState K = P * H / innovation_variance(axis_index);
if (measurementUpdate(K, H, R_ACC, innovation(axis_index))) { measurementUpdate(K, H, R_ACC, innovation(axis_index));
fused[axis_index] = true;
}
} }
} }
@@ -178,8 +174,6 @@ void Ekf::fuseDrag(const dragSample &drag_sample)
innovation_variance, // innovation variance innovation_variance, // innovation variance
innov_gate); // innovation gate innov_gate); // innovation gate
if (fused[0] && fused[1]) { _aid_src_drag.fused = true;
_aid_src_drag.fused = true; _aid_src_drag.time_last_fuse = _time_delayed_us;
_aid_src_drag.time_last_fuse = _time_delayed_us;
}
} }
@@ -182,27 +182,24 @@ void Ekf::fuseBodyFrameVelocity(estimator_aid_source3d_s &aid_src, const uint64_
innovation_gate); // innovation gate innovation_gate); // innovation gate
if (!aid_src.innovation_rejected) { if (!aid_src.innovation_rejected) {
aid_src.fused = true;
for (uint8_t index = 0; index <= 2; index++) { for (uint8_t index = 0; index <= 2; index++) {
if (aid_src.fused) { if (index == 1) {
if (index == 1) { sym::ComputeBodyVelYInnovVar(state_vector, P, measurement_var(index), &aid_src.innovation_variance[index]);
sym::ComputeBodyVelYInnovVar(state_vector, P, measurement_var(index), &aid_src.innovation_variance[index]);
} else if (index == 2) { } else if (index == 2) {
sym::ComputeBodyVelZInnovVar(state_vector, P, measurement_var(index), &aid_src.innovation_variance[index]); sym::ComputeBodyVelZInnovVar(state_vector, P, measurement_var(index), &aid_src.innovation_variance[index]);
}
aid_src.innovation[index] = Vector3f(_R_to_earth.transpose().row(index)) * _state.vel - measurement(index);
VectorState Kfusion = P * H[index] / aid_src.innovation_variance[index];
aid_src.fused &= measurementUpdate(Kfusion, H[index], aid_src.observation_variance[index], aid_src.innovation[index]);
} }
aid_src.innovation[index] = Vector3f(_R_to_earth.transpose().row(index)) * _state.vel - measurement(index);
VectorState Kfusion = P * H[index] / aid_src.innovation_variance[index];
measurementUpdate(Kfusion, H[index], aid_src.observation_variance[index], aid_src.innovation[index]);
} }
if (aid_src.fused) { aid_src.fused = true;
_time_last_hor_vel_fuse = _time_delayed_us; aid_src.time_last_fuse = _time_delayed_us;
_time_last_ver_vel_fuse = _time_delayed_us;
aid_src.time_last_fuse = _time_delayed_us; _time_last_hor_vel_fuse = _time_delayed_us;
} _time_last_ver_vel_fuse = _time_delayed_us;
} }
} }
@@ -63,10 +63,10 @@ void Ekf::controlFakeHgtFusion()
// always protect against extreme values that could result in a NaN // always protect against extreme values that could result in a NaN
if (aid_src.test_ratio < sq(100.0f / innov_gate)) { if (aid_src.test_ratio < sq(100.0f / innov_gate)) {
if (!aid_src.innovation_rejected if (!aid_src.innovation_rejected) {
&& fuseDirectStateMeasurement(aid_src.innovation, aid_src.innovation_variance, aid_src.observation_variance, fuseDirectStateMeasurement(aid_src.innovation, aid_src.innovation_variance, aid_src.observation_variance,
State::pos.idx + 2) State::pos.idx + 2);
) {
aid_src.fused = true; aid_src.fused = true;
aid_src.time_last_fuse = _time_delayed_us; aid_src.time_last_fuse = _time_delayed_us;
} }
@@ -108,12 +108,12 @@ bool Ekf::runFakePosStateMachine(const bool enable_conditions_passing, bool stat
{ {
if (status_flag) { if (status_flag) {
if (enable_conditions_passing) { if (enable_conditions_passing) {
if (!aid_src.innovation_rejected if (!aid_src.innovation_rejected) {
&& fuseDirectStateMeasurement(aid_src.innovation[0], aid_src.innovation_variance[0], aid_src.observation_variance[0], for (unsigned i = 0; i < 2; i++) {
State::pos.idx + 0) fuseDirectStateMeasurement(aid_src.innovation[i], aid_src.innovation_variance[i], aid_src.observation_variance[i],
&& fuseDirectStateMeasurement(aid_src.innovation[1], aid_src.innovation_variance[1], aid_src.observation_variance[1], State::pos.idx + i);
State::pos.idx + 1) }
) {
aid_src.fused = true; aid_src.fused = true;
aid_src.time_last_fuse = _time_delayed_us; aid_src.time_last_fuse = _time_delayed_us;
} }
@@ -205,14 +205,13 @@ void Ekf::fuseGnssYaw(float antenna_yaw_offset)
// only calculate gains for states we are using // only calculate gains for states we are using
VectorState Kfusion = P * H / aid_src.innovation_variance; VectorState Kfusion = P * H / aid_src.innovation_variance;
const bool is_fused = measurementUpdate(Kfusion, H, aid_src.observation_variance, aid_src.innovation); measurementUpdate(Kfusion, H, aid_src.observation_variance, aid_src.innovation);
_fault_status.flags.bad_hdg = !is_fused;
aid_src.fused = is_fused;
if (is_fused) { _fault_status.flags.bad_hdg = false;
_time_last_heading_fuse = _time_delayed_us; aid_src.fused = true;
aid_src.time_last_fuse = _time_delayed_us; aid_src.time_last_fuse = _time_delayed_us;
}
_time_last_heading_fuse = _time_delayed_us;
} }
bool Ekf::resetYawToGnss(const float gnss_yaw, const float gnss_yaw_offset) bool Ekf::resetYawToGnss(const float gnss_yaw, const float gnss_yaw_offset)
@@ -78,8 +78,6 @@ void Ekf::controlGravityFusion(const imuSample &imu)
innovation_variance, // innovation variance innovation_variance, // innovation variance
0.25f); // innovation gate 0.25f); // innovation gate
bool fused[3] {false, false, false};
// update the states and covariance using sequential fusion // update the states and covariance using sequential fusion
for (uint8_t index = 0; index <= 2; index++) { for (uint8_t index = 0; index <= 2; index++) {
// Calculate Kalman gains and observation jacobians // Calculate Kalman gains and observation jacobians
@@ -108,13 +106,10 @@ void Ekf::controlGravityFusion(const imuSample &imu)
const bool accel_clipping = imu.delta_vel_clipping[0] || imu.delta_vel_clipping[1] || imu.delta_vel_clipping[2]; const bool accel_clipping = imu.delta_vel_clipping[0] || imu.delta_vel_clipping[1] || imu.delta_vel_clipping[2];
if (_control_status.flags.gravity_vector && !_aid_src_gravity.innovation_rejected && !accel_clipping) { if (_control_status.flags.gravity_vector && !_aid_src_gravity.innovation_rejected && !accel_clipping) {
fused[index] = measurementUpdate(K, H, _aid_src_gravity.observation_variance[index], measurementUpdate(K, H, _aid_src_gravity.observation_variance[index], _aid_src_gravity.innovation[index]);
_aid_src_gravity.innovation[index]);
} }
} }
if (fused[0] && fused[1] && fused[2]) { _aid_src_gravity.fused = true;
_aid_src_gravity.fused = true; _aid_src_gravity.time_last_fuse = imu.time_us;
_aid_src_gravity.time_last_fuse = imu.time_us;
}
} }
@@ -60,8 +60,6 @@ bool Ekf::fuseMag(const Vector3f &mag, const float R_MAG, VectorState &H, estima
const auto state_vector = _state.vector(); const auto state_vector = _state.vector();
bool fused[3] {false, false, false};
// update the states and covariance using sequential fusion of the magnetometer components // update the states and covariance using sequential fusion of the magnetometer components
for (uint8_t index = 0; index <= 2; index++) { for (uint8_t index = 0; index <= 2; index++) {
// Calculate Kalman gains and observation jacobians // Calculate Kalman gains and observation jacobians
@@ -79,7 +77,6 @@ bool Ekf::fuseMag(const Vector3f &mag, const float R_MAG, VectorState &H, estima
} else if (index == 2) { } else if (index == 2) {
// we do not fuse synthesized magnetomter measurements when doing 3D fusion // we do not fuse synthesized magnetomter measurements when doing 3D fusion
if (_control_status.flags.synthetic_mag_z) { if (_control_status.flags.synthetic_mag_z) {
fused[2] = true;
continue; continue;
} }
@@ -126,29 +123,21 @@ bool Ekf::fuseMag(const Vector3f &mag, const float R_MAG, VectorState &H, estima
Kfusion.slice<State::mag_B.dof, 1>(State::mag_B.idx, 0) = K_mag_B; Kfusion.slice<State::mag_B.dof, 1>(State::mag_B.idx, 0) = K_mag_B;
} }
if (measurementUpdate(Kfusion, H, aid_src.observation_variance[index], aid_src.innovation[index])) { measurementUpdate(Kfusion, H, aid_src.observation_variance[index], aid_src.innovation[index]);
fused[index] = true;
}
} }
_fault_status.flags.bad_mag_x = false;
_fault_status.flags.bad_mag_y = false;
_fault_status.flags.bad_mag_z = false;
aid_src.fused = true;
aid_src.time_last_fuse = _time_delayed_us;
if (update_all_states) { if (update_all_states) {
_fault_status.flags.bad_mag_x = !fused[0]; _time_last_heading_fuse = _time_delayed_us;
_fault_status.flags.bad_mag_y = !fused[1];
_fault_status.flags.bad_mag_z = !fused[2];
} }
if (fused[0] && fused[1] && fused[2]) { return true;
aid_src.fused = true;
aid_src.time_last_fuse = _time_delayed_us;
if (update_all_states) {
_time_last_heading_fuse = _time_delayed_us;
}
return true;
}
return false;
} }
bool Ekf::fuseDeclination(float decl_measurement_rad, float R, bool update_all_states) bool Ekf::fuseDeclination(float decl_measurement_rad, float R, bool update_all_states)
@@ -184,11 +173,11 @@ bool Ekf::fuseDeclination(float decl_measurement_rad, float R, bool update_all_s
Kfusion.slice<State::mag_B.dof, 1>(State::mag_B.idx, 0) = K_mag_B; Kfusion.slice<State::mag_B.dof, 1>(State::mag_B.idx, 0) = K_mag_B;
} }
const bool is_fused = measurementUpdate(Kfusion, H, R, innovation); measurementUpdate(Kfusion, H, R, innovation);
_fault_status.flags.bad_mag_decl = !is_fused; _fault_status.flags.bad_mag_decl = false;
return is_fused; return true;
} }
float Ekf::calculate_synthetic_mag_z_measurement(const Vector3f &mag_meas, const Vector3f &mag_earth_predicted) float Ekf::calculate_synthetic_mag_z_measurement(const Vector3f &mag_meas, const Vector3f &mag_earth_predicted)
@@ -54,18 +54,8 @@ bool Ekf::fuseOptFlow(VectorState &H, const bool update_terrain)
return false; return false;
} }
bool fused[2] {false, false};
// fuse observation axes sequentially // fuse observation axes sequentially
for (uint8_t index = 0; index <= 1; index++) { for (uint8_t index = 0; index <= 1; index++) {
if (_aid_src_optical_flow.innovation_variance[index] < _aid_src_optical_flow.observation_variance[index]) {
// we need to reinitialise the covariance matrix and abort this fusion step
ECL_ERR("Opt flow error - covariance reset");
initialiseCovariance();
return false;
}
if (index == 0) { if (index == 0) {
// everything was already computed above // everything was already computed above
@@ -80,35 +70,36 @@ bool Ekf::fuseOptFlow(VectorState &H, const bool update_terrain)
_aid_src_optical_flow.innovation[1] = predictFlow(flow_gyro_corrected)(1) - _aid_src_optical_flow.observation[1]; _aid_src_optical_flow.innovation[1] = predictFlow(flow_gyro_corrected)(1) - _aid_src_optical_flow.observation[1];
} }
if (_aid_src_optical_flow.innovation_variance[index] < _aid_src_optical_flow.observation_variance[index]) {
// we need to reinitialise the covariance matrix and abort this fusion step
ECL_ERR("Opt flow error - covariance reset");
initialiseCovariance();
return false;
}
VectorState Kfusion = P * H / _aid_src_optical_flow.innovation_variance[index]; VectorState Kfusion = P * H / _aid_src_optical_flow.innovation_variance[index];
if (!update_terrain) { if (!update_terrain) {
Kfusion(State::terrain.idx) = 0.f; Kfusion(State::terrain.idx) = 0.f;
} }
if (measurementUpdate(Kfusion, H, _aid_src_optical_flow.observation_variance[index], measurementUpdate(Kfusion, H, _aid_src_optical_flow.observation_variance[index],
_aid_src_optical_flow.innovation[index])) { _aid_src_optical_flow.innovation[index]);
fused[index] = true;
}
} }
_fault_status.flags.bad_optflow_X = !fused[0]; _fault_status.flags.bad_optflow_X = false;
_fault_status.flags.bad_optflow_Y = !fused[1]; _fault_status.flags.bad_optflow_Y = false;
if (fused[0] && fused[1]) { _aid_src_optical_flow.time_last_fuse = _time_delayed_us;
_aid_src_optical_flow.time_last_fuse = _time_delayed_us; _aid_src_optical_flow.fused = true;
_aid_src_optical_flow.fused = true;
_time_last_hor_vel_fuse = _time_delayed_us; _time_last_hor_vel_fuse = _time_delayed_us;
if (update_terrain) { if (update_terrain) {
_time_last_terrain_fuse = _time_delayed_us; _time_last_terrain_fuse = _time_delayed_us;
}
return true;
} }
return false; return true;
} }
float Ekf::predictFlowRange() const float Ekf::predictFlowRange() const
@@ -138,14 +138,12 @@ bool Ekf::fuseSideslip(estimator_aid_source1d_s &sideslip)
K.slice<State::wind_vel.dof, 1>(State::wind_vel.idx, 0) = K_wind; K.slice<State::wind_vel.dof, 1>(State::wind_vel.idx, 0) = K_wind;
} }
const bool is_fused = measurementUpdate(K, H, sideslip.observation_variance, sideslip.innovation); measurementUpdate(K, H, sideslip.observation_variance, sideslip.innovation);
sideslip.fused = is_fused; sideslip.fused = true;
_fault_status.flags.bad_sideslip = !is_fused; sideslip.time_last_fuse = _time_delayed_us;
if (is_fused) { _fault_status.flags.bad_sideslip = false;
sideslip.time_last_fuse = _time_delayed_us;
}
return is_fused; return true;
} }
+3 -2
View File
@@ -246,7 +246,7 @@ public:
} }
// fuse single direct state measurement (eg NED velocity, NED position, mag earth field, etc) // fuse single direct state measurement (eg NED velocity, NED position, mag earth field, etc)
bool fuseDirectStateMeasurement(const float innov, const float innov_var, const float R, const int state_index); void fuseDirectStateMeasurement(const float innov, const float innov_var, const float R, const int state_index);
bool measurementUpdate(VectorState &K, const VectorState &H, const float R, const float innovation); bool measurementUpdate(VectorState &K, const VectorState &H, const float R, const float innovation);
@@ -834,7 +834,8 @@ private:
void fuseBodyVelocity(estimator_aid_source1d_s &aid_src, float &innov_var, VectorState &H) void fuseBodyVelocity(estimator_aid_source1d_s &aid_src, float &innov_var, VectorState &H)
{ {
VectorState Kfusion = P * H / innov_var; VectorState Kfusion = P * H / innov_var;
aid_src.fused = measurementUpdate(Kfusion, H, aid_src.observation_variance, aid_src.innovation); measurementUpdate(Kfusion, H, aid_src.observation_variance, aid_src.innovation);
aid_src.fused = true;
} }
#endif // CONFIG_EKF2_EXTERNAL_VISION #endif // CONFIG_EKF2_EXTERNAL_VISION
+1 -2
View File
@@ -1005,7 +1005,7 @@ void Ekf::updateIMUBiasInhibit(const imuSample &imu_delayed)
} }
} }
bool Ekf::fuseDirectStateMeasurement(const float innov, const float innov_var, const float R, const int state_index) void Ekf::fuseDirectStateMeasurement(const float innov, const float innov_var, const float R, const int state_index)
{ {
VectorState K; // Kalman gain vector for any single observation - sequential fusion is used. VectorState K; // Kalman gain vector for any single observation - sequential fusion is used.
@@ -1063,7 +1063,6 @@ bool Ekf::fuseDirectStateMeasurement(const float innov, const float innov_var, c
// apply the state corrections // apply the state corrections
fuse(K, innov); fuse(K, innov);
return true;
} }
bool Ekf::measurementUpdate(VectorState &K, const VectorState &H, const float R, const float innovation) bool Ekf::measurementUpdate(VectorState &K, const VectorState &H, const float R, const float innovation)
+10 -10
View File
@@ -56,12 +56,12 @@ void Ekf::updateVerticalPositionAidStatus(estimator_aid_source1d_s &aid_src, con
bool Ekf::fuseHorizontalPosition(estimator_aid_source2d_s &aid_src) bool Ekf::fuseHorizontalPosition(estimator_aid_source2d_s &aid_src)
{ {
// x & y // x & y
if (!aid_src.innovation_rejected if (!aid_src.innovation_rejected) {
&& fuseDirectStateMeasurement(aid_src.innovation[0], aid_src.innovation_variance[0], aid_src.observation_variance[0], for (unsigned i = 0; i < 2; i++) {
State::pos.idx + 0) fuseDirectStateMeasurement(aid_src.innovation[i], aid_src.innovation_variance[i], aid_src.observation_variance[i],
&& fuseDirectStateMeasurement(aid_src.innovation[1], aid_src.innovation_variance[1], aid_src.observation_variance[1], State::pos.idx + i);
State::pos.idx + 1) }
) {
aid_src.fused = true; aid_src.fused = true;
aid_src.time_last_fuse = _time_delayed_us; aid_src.time_last_fuse = _time_delayed_us;
@@ -77,10 +77,10 @@ bool Ekf::fuseHorizontalPosition(estimator_aid_source2d_s &aid_src)
bool Ekf::fuseVerticalPosition(estimator_aid_source1d_s &aid_src) bool Ekf::fuseVerticalPosition(estimator_aid_source1d_s &aid_src)
{ {
// z // z
if (!aid_src.innovation_rejected if (!aid_src.innovation_rejected) {
&& fuseDirectStateMeasurement(aid_src.innovation, aid_src.innovation_variance, aid_src.observation_variance, fuseDirectStateMeasurement(aid_src.innovation, aid_src.innovation_variance, aid_src.observation_variance,
State::pos.idx + 2) State::pos.idx + 2);
) {
aid_src.fused = true; aid_src.fused = true;
aid_src.time_last_fuse = _time_delayed_us; aid_src.time_last_fuse = _time_delayed_us;
+12 -14
View File
@@ -36,12 +36,12 @@
bool Ekf::fuseHorizontalVelocity(estimator_aid_source2d_s &aid_src) bool Ekf::fuseHorizontalVelocity(estimator_aid_source2d_s &aid_src)
{ {
// vx, vy // vx, vy
if (!aid_src.innovation_rejected if (!aid_src.innovation_rejected) {
&& fuseDirectStateMeasurement(aid_src.innovation[0], aid_src.innovation_variance[0], aid_src.observation_variance[0], for (unsigned i = 0; i < 2; i++) {
State::vel.idx + 0) fuseDirectStateMeasurement(aid_src.innovation[i], aid_src.innovation_variance[i], aid_src.observation_variance[i],
&& fuseDirectStateMeasurement(aid_src.innovation[1], aid_src.innovation_variance[1], aid_src.observation_variance[1], State::vel.idx + i);
State::vel.idx + 1) }
) {
aid_src.fused = true; aid_src.fused = true;
aid_src.time_last_fuse = _time_delayed_us; aid_src.time_last_fuse = _time_delayed_us;
@@ -57,14 +57,12 @@ bool Ekf::fuseHorizontalVelocity(estimator_aid_source2d_s &aid_src)
bool Ekf::fuseVelocity(estimator_aid_source3d_s &aid_src) bool Ekf::fuseVelocity(estimator_aid_source3d_s &aid_src)
{ {
// vx, vy, vz // vx, vy, vz
if (!aid_src.innovation_rejected if (!aid_src.innovation_rejected) {
&& fuseDirectStateMeasurement(aid_src.innovation[0], aid_src.innovation_variance[0], aid_src.observation_variance[0], for (unsigned i = 0; i < 3; i++) {
State::vel.idx + 0) fuseDirectStateMeasurement(aid_src.innovation[i], aid_src.innovation_variance[i], aid_src.observation_variance[i],
&& fuseDirectStateMeasurement(aid_src.innovation[1], aid_src.innovation_variance[1], aid_src.observation_variance[1], State::vel.idx + i);
State::vel.idx + 1) }
&& fuseDirectStateMeasurement(aid_src.innovation[2], aid_src.innovation_variance[2], aid_src.observation_variance[2],
State::vel.idx + 2)
) {
aid_src.fused = true; aid_src.fused = true;
aid_src.time_last_fuse = _time_delayed_us; aid_src.time_last_fuse = _time_delayed_us;
+6 -11
View File
@@ -98,21 +98,16 @@ bool Ekf::fuseYaw(estimator_aid_source1d_s &aid_src_status, const VectorState &H
_innov_check_fail_status.flags.reject_yaw = false; _innov_check_fail_status.flags.reject_yaw = false;
} }
if (measurementUpdate(Kfusion, H_YAW, aid_src_status.observation_variance, aid_src_status.innovation)) { measurementUpdate(Kfusion, H_YAW, aid_src_status.observation_variance, aid_src_status.innovation);
_time_last_heading_fuse = _time_delayed_us; _time_last_heading_fuse = _time_delayed_us;
aid_src_status.time_last_fuse = _time_delayed_us; aid_src_status.time_last_fuse = _time_delayed_us;
aid_src_status.fused = true; aid_src_status.fused = true;
_fault_status.flags.bad_hdg = false; _fault_status.flags.bad_hdg = false;
return true; return true;
}
// otherwise
_fault_status.flags.bad_hdg = true;
return false;
} }
void Ekf::computeYawInnovVarAndH(float variance, float &innovation_variance, VectorState &H_YAW) const void Ekf::computeYawInnovVarAndH(float variance, float &innovation_variance, VectorState &H_YAW) const