cmvr-es/src/utils/srs_ik/srs_ik_test.cpp
2025-11-06 16:27:54 +08:00

193 lines
6.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Created by lgv on 2025/11/3.
//
#include "gtest/gtest.h"
#include "manif/SE3.h"
#include "srs_ik/srs_ik_slover.h"
#include "srs_ik/ik_limit_analyzer.h"
#include <iostream>
#include <iomanip>
#include <fstream>
using namespace manif;
using namespace cmvr::utils;
struct IkSample {
double psi;
std::array<double,7> q; // q1..q7
};
bool write_ik_samples_csv(const std::string& filepath,
const std::vector<IkSample>& samples,
bool write_header,
int precision)
{
std::ofstream ofs(filepath, std::ios::out | std::ios::trunc);
if (!ofs.is_open()) return false;
// 固定小数点(避免本地化成逗号)
ofs.imbue(std::locale::classic());
ofs << std::fixed << std::setprecision(precision);
if (write_header) {
ofs << "psi,q1,q2,q3,q4,q5,q6,q7\n";
}
for (const auto& s : samples) {
ofs << s.psi;
for (int i = 0; i < 7; ++i) ofs << ',' << s.q[i];
ofs << '\n';
}
return true;
}
TEST(SRS_IK_TEST, SRS_IK_SLOVER_TEST) {
using std::cout;
using std::endl;
// std::cout << std::fixed << std::setprecision(7);
SRSIkSlover slover;
std::vector<IkSample> samples;
samples.reserve(4096);
std::vector<double> joint_angles(7, 0);
joint_angles = { 0.875, 0.22, 0.2644, M_PI / 2, 1.0, 1.99, 1.56 };
// 目标位姿FK(joint_angles)
const auto target_pose = slover.calc_total_transform(joint_angles);
cout << "Target Pose (FK from seed joints):\n" << target_pose << endl;
// 系数矩阵 & ψ 扫描区间
Eigen::MatrixXd s_mat(3, 9), w_mat(3, 9);
slover.cal_coefficient_matrix(target_pose, s_mat, w_mat);
auto limits = slover.calc_arm_angle_limits(s_mat, w_mat);
// 误差统计
const double kPosTol = 1e-4; // 位置容差m
const double kRotTol = 1e-3; // 姿态容差rad≈ 0.0573°
double max_pos_err = 0.0, max_rot_err = 0.0;
double sum_pos_err = 0.0, sum_rot_err = 0.0;
size_t total = 0, bad = 0;
// 便捷引用
const Eigen::Vector3d p_target = target_pose.block<3,1>(0,3);
const Eigen::Matrix3d R_target = target_pose.block<3,3>(0,0);
auto clamp = [](double x, double lo, double hi) {
return std::max(lo, std::min(hi, x));
};
auto rot_err_rad = [&](const Eigen::Matrix3d& R) -> double {
Eigen::Matrix3d dR = R_target.transpose() * R;
double c = clamp((dR.trace() - 1.0) * 0.5, -1.0, 1.0);
return std::acos(c); // [0, pi]
};
// 表头
cout << "psi(rad), pos_err(m), rot_err(rad), rot_err(deg)\n";
// ψ 扫描
for (const auto& limit : limits) {
const double psi_lo = limit.first;
const double psi_hi = limit.second;
for (double psi = psi_lo; psi < psi_hi; psi += 0.001) {
// IK 解
auto q = slover.inverse_kinematics(target_pose, psi);
if (q.size() != 7 || std::any_of(q.begin(), q.end(),
[](double v){ return !std::isfinite(v); })) {
++bad;
++total;
cout << psi << ", nan, nan, nan\n";
continue;
}
// 用 IK 解做 FK计算误差
const auto T_fk = slover.calc_total_transform(q);
const Eigen::Vector3d p_fk = T_fk.block<3,1>(0,3);
const Eigen::Matrix3d R_fk = T_fk.block<3,3>(0,0);
const double pos_err = (p_fk - p_target).norm();
const double rot_err = rot_err_rad(R_fk);
const double rot_err_deg = rot_err * 180.0 / M_PI;
// 打印每个样本的误差(一行,便于看 sweep
cout << psi << ", " << pos_err << ", " << rot_err << ", " << rot_err_deg << "\n";
// 断言(不立即中止)
SCOPED_TRACE(testing::Message() << "psi=" << psi);
EXPECT_LT(pos_err, kPosTol);
EXPECT_LT(rot_err, kRotTol);
// 统计
max_pos_err = std::max(max_pos_err, pos_err);
max_rot_err = std::max(max_rot_err, rot_err);
sum_pos_err += pos_err;
sum_rot_err += rot_err;
++total;
// 保留你原先的采样输出
samples.push_back(IkSample{psi, {q[0], q[1], q[2], q[3], q[4], q[5], q[6]}});
}
}
// 摘要打印
cout << "\nSummary:\n"
<< " total=" << total
<< " bad=" << bad
<< " pos_err_max=" << max_pos_err << " m"
<< " rot_err_max=" << max_rot_err << " rad (" << max_rot_err * 180.0 / M_PI << " deg)\n"
<< " pos_err_mean=" << (total ? (sum_pos_err / total) : 0.0) << " m"
<< " rot_err_mean=" << (total ? (sum_rot_err / total) : 0.0) << " rad ("
<< (total ? (sum_rot_err / total) * 180.0 / M_PI : 0.0) << " deg)\n";
// 文件输出(与原逻辑一致)
write_ik_samples_csv("/home/lgv/cmvr/cmvr-es/data/ik_psi_sweep.csv", samples, true, 9);
// 最终强约束(避免全是 EXPECT_* 时忽略失败)
ASSERT_LT(max_pos_err, 10 * kPosTol) << "Max position error too large.";
ASSERT_LT(max_rot_err, 10 * kRotTol) << "Max rotation error too large.";
}
TEST(SRS_IK_TEST,INTERSECT_TEST) {
// 测试 1: 有交集的区间
std::vector<std::pair<double, double>> A = {{-3.0, -1.0}, {1.0, 4.0}};
std::vector<std::pair<double, double>> B = {{-2.0, 0.5}, {2.5, 5.0}};
std::cout << "Test 1: Intersecting intervals" << std::endl;
auto result1 = IkLimitAnalyzer::intersect(A, B);
IkLimitAnalyzer::print_intervals(result1);
// 预期输出: [-2.0, -1.0] [2.5, 4.0]
// 测试 2: 相邻但不重叠的区间
std::vector<std::pair<double, double>> C = {{-3.0, -1.0}, {2.0, 4.0}};
std::vector<std::pair<double, double>> D = {{-1.0, 0.0}, {1.0, 3.0}};
std::cout << "Test 2: Adjacent intervals" << std::endl;
auto result2 = IkLimitAnalyzer::intersect(C, D);
IkLimitAnalyzer::print_intervals(result2);
// 预期输出: [2.0, 3.0]
// 测试 3: 无交集的区间
std::vector<std::pair<double, double>> E = {{-5.0, -3.0}, {2.0, 4.0}};
std::vector<std::pair<double, double>> F = {{5.0, 6.0}, {7.0, 8.0}};
std::cout << "Test 3: Non-intersecting intervals" << std::endl;
auto result3 = IkLimitAnalyzer::intersect(E, F);
IkLimitAnalyzer::print_intervals(result3);
// 预期输出: (无输出)
// 测试 4: 一个空的区间集
std::vector<std::pair<double, double>> G = {};
std::vector<std::pair<double, double>> H = {{1.0, 2.0}, {3.0, 4.0}};
std::cout << "Test 4: Empty intervals" << std::endl;
auto result4 = IkLimitAnalyzer::intersect(G, H);
IkLimitAnalyzer::print_intervals(result4);
// 预期输出: (无输出)
}