#include "unified_cost.h"
#include <cmath>
#include <Eigen/Dense>
namespace unified_cost {
namespace {
double NormalizeAngle(double angle) {
while (angle > M_PI) {
angle -= 2.0 * M_PI;
}
while (angle < -M_PI) {
angle += 2.0 * M_PI;
}
return angle;
}
Eigen::Matrix3d Skew(const Eigen::Vector3d& v) {
Eigen::Matrix3d m;
m << 0.0, -v.z(), v.y(),
v.z(), 0.0, -v.x(),
-v.y(), v.x(), 0.0;
return m;
}
Eigen::Vector3d SO3LogMap(const Eigen::Quaterniond& q) {
double qw = std::clamp(q.w(), -1.0, 1.0);
if (qw > 1.0 - 1e-10) {
return 2.0 * q.vec();
}
double theta = 2.0 * std::acos(qw);
double sin_half_theta = std::sqrt(1.0 - qw * qw);
if (sin_half_theta < 1e-10) {
return 2.0 * q.vec();
}
return (theta / sin_half_theta) * q.vec();
}
Eigen::Matrix3d ComputeSO3LeftJacobianInverse(const Eigen::Vector3d& theta_vec) {
double angle_sq = theta_vec.squaredNorm();
Eigen::Matrix3d theta_skew = Skew(theta_vec);
if (angle_sq < 1e-10) {
return Eigen::Matrix3d::Identity() - 0.5 * theta_skew;
}
double theta = std::sqrt(angle_sq);
double sin_theta = std::sin(theta);
double cos_theta = std::cos(theta);
double coef = 1.0 / angle_sq - (1.0 + cos_theta) / (2.0 * theta * sin_theta);
return Eigen::Matrix3d::Identity() - 0.5 * theta_skew + coef * (theta_skew * theta_skew);
}
std::pair<double, double> ComputeSE2ResidualCosts(
const Eigen::Vector3d& residual,
const Eigen::Matrix3d& information) {
double chi2 = residual.transpose() * information * residual;
double unweighted = 0.5 * residual.squaredNorm();
return {chi2, unweighted};
}
std::pair<double, double> ComputeSE3ResidualCosts(
const Eigen::Matrix<double, 6, 1>& residual,
const Eigen::Matrix<double, 6, 6>& information) {
double chi2 = residual.transpose() * information * residual;
double unweighted = 0.5 * residual.squaredNorm();
return {chi2, unweighted};
}
}
CostMetrics ComputeSE2CostMetrics(const g2o_reader::Graph2D& graph) {
CostMetrics metrics{0.0, 0.0};
if (graph.constraints.empty()) {
return metrics;
}
for (const auto& constraint : graph.constraints) {
auto pose_i_it = graph.poses.find(constraint.id_begin);
auto pose_j_it = graph.poses.find(constraint.id_end);
if (pose_i_it == graph.poses.end() || pose_j_it == graph.poses.end()) {
continue;
}
const auto& pose_i = pose_i_it->second;
const auto& pose_j = pose_j_it->second;
double cos_i = std::cos(pose_i.rotation);
double sin_i = std::sin(pose_i.rotation);
Eigen::Matrix2d R_i_inv;
R_i_inv << cos_i, sin_i,
-sin_i, cos_i;
Eigen::Vector2d t_i_inv = -R_i_inv * pose_i.translation;
double cos_j = std::cos(pose_j.rotation);
double sin_j = std::sin(pose_j.rotation);
Eigen::Matrix2d R_j;
R_j << cos_j, -sin_j,
sin_j, cos_j;
Eigen::Matrix2d R_actual = R_i_inv * R_j;
Eigen::Vector2d t_actual = R_i_inv * pose_j.translation + t_i_inv;
double theta_actual = std::atan2(R_actual(1, 0), R_actual(0, 0));
double cos_m = std::cos(constraint.measurement.rotation);
double sin_m = std::sin(constraint.measurement.rotation);
Eigen::Matrix2d R_m_inv;
R_m_inv << cos_m, sin_m,
-sin_m, cos_m;
Eigen::Vector2d t_m_inv = -R_m_inv * constraint.measurement.translation;
Eigen::Matrix2d R_error = R_m_inv * R_actual;
Eigen::Vector2d t_error = R_m_inv * t_actual + t_m_inv;
double theta_error = std::atan2(R_error(1, 0), R_error(0, 0));
theta_error = NormalizeAngle(theta_error);
double theta = theta_error;
double theta_sq = theta * theta;
double a, b;
if (theta_sq < 1e-10) {
a = 1.0 - theta_sq / 6.0;
b = 0.5 * theta - theta * theta_sq / 24.0;
} else {
a = std::sin(theta) / theta;
b = (1.0 - std::cos(theta)) / theta;
}
double det = a * a + b * b;
double a_scaled = a / det;
double b_scaled = b / det;
double x_tangent = a_scaled * t_error.x() + b_scaled * t_error.y();
double y_tangent = -b_scaled * t_error.x() + a_scaled * t_error.y();
Eigen::Vector3d residual;
residual << x_tangent, y_tangent, theta_error;
auto [chi2, unweighted] = ComputeSE2ResidualCosts(residual, constraint.information);
metrics.chi2_cost += chi2;
metrics.unweighted_cost += unweighted;
}
return metrics;
}
CostMetrics ComputeSE3CostMetrics(const g2o_reader::Graph3D& graph) {
CostMetrics metrics{0.0, 0.0};
if (graph.constraints.empty()) {
return metrics;
}
for (const auto& constraint : graph.constraints) {
auto pose_i_it = graph.poses.find(constraint.id_begin);
auto pose_j_it = graph.poses.find(constraint.id_end);
if (pose_i_it == graph.poses.end() || pose_j_it == graph.poses.end()) {
continue;
}
const auto& pose_i = pose_i_it->second;
const auto& pose_j = pose_j_it->second;
Eigen::Quaterniond q_i_inv = pose_i.rotation.conjugate();
Eigen::Vector3d t_i_inv = -(q_i_inv * pose_i.translation);
Eigen::Quaterniond q_actual = q_i_inv * pose_j.rotation;
Eigen::Vector3d t_actual = q_i_inv * pose_j.translation + t_i_inv;
Eigen::Quaterniond q_m_inv = constraint.measurement.rotation.conjugate();
Eigen::Vector3d t_m_inv = -(q_m_inv * constraint.measurement.translation);
Eigen::Quaterniond q_error = q_m_inv * q_actual;
Eigen::Vector3d t_error = q_m_inv * t_actual + t_m_inv;
Eigen::Vector3d residual_rotation = SO3LogMap(q_error);
Eigen::Matrix3d J_l_inv = ComputeSO3LeftJacobianInverse(residual_rotation);
Eigen::Vector3d residual_translation = J_l_inv * t_error;
Eigen::Matrix<double, 6, 1> residual;
residual.head<3>() = residual_translation;
residual.tail<3>() = residual_rotation;
auto [chi2, unweighted] = ComputeSE3ResidualCosts(residual, constraint.information);
metrics.chi2_cost += chi2;
metrics.unweighted_cost += unweighted;
}
return metrics;
}
double ComputeSE2Cost(const g2o_reader::Graph2D& graph) {
return ComputeSE2CostMetrics(graph).unweighted_cost;
}
double ComputeSE3Cost(const g2o_reader::Graph3D& graph) {
return ComputeSE3CostMetrics(graph).unweighted_cost;
}
}