Skip to content

Commit 095de24

Browse files
add user configurable diag mapping support of termination types. (#410)
* add user configurable diag mapping support of termination types. * missing return. * replace the entire block for diag. * add unit test. * Update fuse_optimizers/src/fixed_lag_smoother.cpp Co-authored-by: Stephen Williams <swilliams@locusrobotics.com> --------- Co-authored-by: Stephen Williams <swilliams@locusrobotics.com>
1 parent 97ec689 commit 095de24

File tree

6 files changed

+246
-14
lines changed

6 files changed

+246
-14
lines changed

fuse_optimizers/CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,34 @@ if(CATKIN_ENABLE_TESTING)
199199

200200
add_rostest(test/optimizer.test ARGS config:=list DEPENDENCIES test_optimizer)
201201

202+
# Fixed lag smoother Tests
203+
add_rostest_gtest(test_fixed_lag_smoother
204+
test/fixed_lag_smoother.test
205+
test/test_fixed_lag_smoother.cpp
206+
)
207+
add_dependencies(test_fixed_lag_smoother
208+
${catkin_EXPORTED_TARGETS}
209+
)
210+
target_include_directories(test_fixed_lag_smoother
211+
PRIVATE
212+
include
213+
)
214+
target_include_directories(test_fixed_lag_smoother
215+
SYSTEM PRIVATE
216+
${catkin_INCLUDE_DIRS}
217+
${CMAKE_CURRENT_SOURCE_DIR}
218+
)
219+
target_link_libraries(test_fixed_lag_smoother
220+
${PROJECT_NAME}
221+
${catkin_LIBRARIES}
222+
)
223+
set_target_properties(test_fixed_lag_smoother
224+
PROPERTIES
225+
CXX_STANDARD 17
226+
CXX_STANDARD_REQUIRED YES
227+
)
228+
229+
add_rostest(test/fixed_lag_smoother.test ARGS config:=list DEPENDENCIES test_fixed_lag_smoother)
202230
# Fixed-lag Ignition test
203231
add_rostest_gtest(test_fixed_lag_ignition
204232
test/fixed_lag_ignition.test

fuse_optimizers/include/fuse_optimizers/fixed_lag_smoother.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,23 @@ class FixedLagSmoother : public Optimizer
334334
const std::string& sensor_name,
335335
fuse_core::Transaction::SharedPtr transaction) override;
336336

337+
/**
338+
* @brief Helper function to generate the diagnostic status for each optimization termination type
339+
*
340+
* The termination type -> diagnostic status mapping is as follows:
341+
*
342+
* - CONVERGENCE, USER_SUCCESS -> OK
343+
* - NO_CONVERGENCE -> WARN
344+
* - FAILURE, USER_FAILURE -> ERROR (default)
345+
*
346+
* @param[in] termination_type The optimization termination type
347+
* @param[in] diag_warnings The diagnostic warnings
348+
* @param[in] diag_errors The diagnostic errors
349+
* @return The diagnostic status with the level and message corresponding to the optimization termination type
350+
*/
351+
diagnostic_msgs::DiagnosticStatus terminationTypeToDiagnosticStatus(const ceres::TerminationType termination_type,
352+
const std::vector<std::string>& diag_warnings,
353+
const std::vector<std::string>& diag_errors);
337354
/**
338355
* @brief Update and publish diagnotics
339356
* @param[in] status The diagnostic status

fuse_optimizers/include/fuse_optimizers/fixed_lag_smoother_params.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ namespace fuse_optimizers
5555
struct FixedLagSmootherParams
5656
{
5757
public:
58+
/**
59+
* @brief Map the ceres::optimizer log levels to diagnostic levels
60+
*/
61+
std::vector<std::string> diagnostic_warning_status { "NO_CONVERGENCE" };
62+
63+
/**
64+
* @brief Map the ceres::optimizer log levels to diagnostic levels
65+
*/
66+
std::vector<std::string> diagnostic_error_status {"FAILURE", "USER_FAILURE"};
67+
5868
/**
5969
* @brief If true, the state estimator will not start until the start or reset service is called
6070
*/
@@ -115,6 +125,10 @@ struct FixedLagSmootherParams
115125
void loadFromROS(const ros::NodeHandle& nh)
116126
{
117127
// Read settings from the parameter server
128+
nh.param("diagnostic_warning_status", diagnostic_warning_status, diagnostic_warning_status);
129+
130+
nh.param("diagnostic_error_status", diagnostic_error_status, diagnostic_error_status);
131+
118132
nh.getParam("disabled_at_startup", disabled_at_startup);
119133

120134
fuse_core::getPositiveParam(nh, "lag_duration", lag_duration);

fuse_optimizers/src/fixed_lag_smoother.cpp

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -624,28 +624,58 @@ diagnostic_msgs::DiagnosticStatus makeDiagnosticStatus(const int8_t level, const
624624
}
625625

626626
/**
627-
* @brief Helper function to generate the diagnostic status for each optimization termination type
627+
* @brief Helper function to map termination type to string
628628
*
629-
* The termination type -> diagnostic status mapping is as follows:
630-
*
631-
* - CONVERGENCE, USER_SUCCESS -> OK
632-
* - NO_CONVERGENCE -> WARN
633-
* - FAILURE, USER_FAILURE -> ERROR (default)
634-
*
635-
* @param[in] termination_type The optimization termination type
636-
* @return The diagnostic status with the level and message corresponding to the optimization termination type
629+
* @param[in] termination_type The termination type
630+
* @return the string format of the termination type
637631
*/
638-
diagnostic_msgs::DiagnosticStatus terminationTypeToDiagnosticStatus(const ceres::TerminationType termination_type)
632+
std::string mapCeresLogToDiagLog(const ceres::TerminationType & termination_type)
639633
{
640634
switch (termination_type)
641635
{
642636
case ceres::TerminationType::CONVERGENCE:
637+
return "CONVERGENCE";
643638
case ceres::TerminationType::USER_SUCCESS:
644-
return makeDiagnosticStatus(diagnostic_msgs::DiagnosticStatus::OK, "Optimization converged");
639+
return "USER_SUCCESS";
645640
case ceres::TerminationType::NO_CONVERGENCE:
646-
return makeDiagnosticStatus(diagnostic_msgs::DiagnosticStatus::WARN, "Optimization didn't converge");
641+
return "NO_CONVERGENCE";
642+
case ceres::TerminationType::FAILURE:
643+
return "FAILURE";
644+
case ceres::TerminationType::USER_FAILURE:
645+
return "USER_FAILURE";
647646
default:
648-
return makeDiagnosticStatus(diagnostic_msgs::DiagnosticStatus::ERROR, "Optimization failed");
647+
return "FAILURE";
648+
}
649+
}
650+
651+
/**
652+
* @brief Helper function to check if a string is contained in the list
653+
*
654+
* @param[in] vec The list of strings
655+
* @param[in] str The str to search
656+
* @return true if contains, false otherwise
657+
*/
658+
inline bool contains(const std::vector<std::string>& vec, const std::string& str)
659+
{
660+
return std::find(vec.begin(), vec.end(), str) != vec.end();
661+
}
662+
663+
diagnostic_msgs::DiagnosticStatus FixedLagSmoother::terminationTypeToDiagnosticStatus(
664+
const ceres::TerminationType termination_type, const std::vector<std::string>& diag_warnings,
665+
const std::vector<std::string>& diag_errors)
666+
{
667+
std::string diag_level = mapCeresLogToDiagLog(termination_type);
668+
if (contains(diag_errors, diag_level))
669+
{
670+
return makeDiagnosticStatus(diagnostic_msgs::DiagnosticStatus::ERROR, "Optimization failed");
671+
}
672+
else if (contains(diag_warnings, diag_level))
673+
{
674+
return makeDiagnosticStatus(diagnostic_msgs::DiagnosticStatus::WARN, "Optimization didn't converge");
675+
}
676+
else
677+
{
678+
return makeDiagnosticStatus(diagnostic_msgs::DiagnosticStatus::OK, "Optimization converged");
649679
}
650680
}
651681

@@ -686,7 +716,8 @@ void FixedLagSmoother::setDiagnostics(diagnostic_updater::DiagnosticStatusWrappe
686716
status.add("Initial Cost", summary.initial_cost);
687717
status.add("Final Cost", summary.final_cost);
688718

689-
status.mergeSummary(terminationTypeToDiagnosticStatus(summary.termination_type));
719+
status.mergeSummary(terminationTypeToDiagnosticStatus(summary.termination_type, params_.diagnostic_warning_status,
720+
params_.diagnostic_error_status));
690721
}
691722

692723
// Add time since the last optimization request time. This is useful to detect if no transactions are received for
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0"?>
2+
<launch>
3+
<arg name="config" default="struct" doc="Config format: {list, struct}"/>
4+
5+
<test test-name="FixedLagSmoother" pkg="fuse_optimizers" type="test_fixed_lag_smoother">
6+
</test>
7+
</launch>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Software License Agreement (BSD License)
3+
*
4+
* Copyright (c) 2025, Locus Robotics
5+
* All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions
9+
* are met:
10+
*
11+
* * Redistributions of source code must retain the above copyright
12+
* notice, this list of conditions and the following disclaimer.
13+
* * Redistributions in binary form must reproduce the above
14+
* copyright notice, this list of conditions and the following
15+
* disclaimer in the documentation and/or other materials provided
16+
* with the distribution.
17+
* * Neither the name of the copyright holder nor the names of its
18+
* contributors may be used to endorse or promote products derived
19+
* from this software without specific prior written permission.
20+
*
21+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24+
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25+
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27+
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32+
* POSSIBILITY OF SUCH DAMAGE.
33+
*/
34+
35+
#include <fuse_graphs/hash_graph.h>
36+
#include <fuse_graphs/hash_graph_params.h>
37+
#include <fuse_optimizers/fixed_lag_smoother.h>
38+
39+
#include <gtest/gtest.h>
40+
#include <ros/ros.h>
41+
42+
#include <memory>
43+
#include <string>
44+
#include <utility>
45+
#include <vector>
46+
47+
48+
class FixedLagSmootherForTest : public fuse_optimizers::FixedLagSmoother
49+
{
50+
public:
51+
FixedLagSmootherForTest(fuse_core::Graph::UniquePtr graph,
52+
const ros::NodeHandle& nh,
53+
const ros::NodeHandle& pnh)
54+
: fuse_optimizers::FixedLagSmoother(std::move(graph), nh, pnh) {}
55+
56+
using fuse_optimizers::FixedLagSmoother::terminationTypeToDiagnosticStatus;
57+
};
58+
59+
class TestFixedLagSmoother : public ::testing::Test
60+
{
61+
protected:
62+
ros::NodeHandle nh_;
63+
ros::NodeHandle pnh_;
64+
std::unique_ptr<FixedLagSmootherForTest> smoother_;
65+
66+
void SetUp() override
67+
{
68+
fuse_graphs::HashGraphParams graph_params;
69+
graph_params.loadFromROS(pnh_);
70+
smoother_ = std::make_unique<FixedLagSmootherForTest>(
71+
fuse_graphs::HashGraph::make_unique(graph_params),
72+
nh_, pnh_);
73+
}
74+
75+
void TearDown() override
76+
{
77+
smoother_.reset();
78+
}
79+
};
80+
81+
TEST_F(TestFixedLagSmoother, terminationTypeToDiagnosticStatus)
82+
{
83+
ceres::TerminationType termination_type;
84+
std::vector<std::string> diagnostic_warning_status { "NO_CONVERGENCE" };
85+
std::vector<std::string> diagnostic_error_status {"FAILURE", "USER_FAILURE"};
86+
diagnostic_msgs::DiagnosticStatus diag_msg;
87+
88+
// NO_CONVERGENCE as WARNING
89+
termination_type = ceres::TerminationType::NO_CONVERGENCE;
90+
diag_msg = smoother_->terminationTypeToDiagnosticStatus(termination_type, diagnostic_warning_status,
91+
diagnostic_error_status);
92+
EXPECT_EQ(diag_msg.level, diagnostic_msgs::DiagnosticStatus::WARN);
93+
94+
// NO_CONVERGENCE as OK
95+
diagnostic_warning_status = {};
96+
diag_msg = smoother_->terminationTypeToDiagnosticStatus(termination_type, diagnostic_warning_status,
97+
diagnostic_error_status);
98+
EXPECT_EQ(diag_msg.level, diagnostic_msgs::DiagnosticStatus::OK);
99+
100+
// CONVERGENCE
101+
termination_type = ceres::TerminationType::CONVERGENCE;
102+
diag_msg = smoother_->terminationTypeToDiagnosticStatus(termination_type, diagnostic_warning_status,
103+
diagnostic_error_status);
104+
EXPECT_EQ(diag_msg.level, diagnostic_msgs::DiagnosticStatus::OK);
105+
106+
// USER_SUCCESS
107+
termination_type = ceres::TerminationType::USER_SUCCESS;
108+
diag_msg = smoother_->terminationTypeToDiagnosticStatus(termination_type, diagnostic_warning_status,
109+
diagnostic_error_status);
110+
EXPECT_EQ(diag_msg.level, diagnostic_msgs::DiagnosticStatus::OK);
111+
112+
// FAILURE
113+
termination_type = ceres::TerminationType::FAILURE;
114+
diag_msg = smoother_->terminationTypeToDiagnosticStatus(termination_type, diagnostic_warning_status,
115+
diagnostic_error_status);
116+
EXPECT_EQ(diag_msg.level, diagnostic_msgs::DiagnosticStatus::ERROR);
117+
118+
// USER_FAILURE
119+
termination_type = ceres::TerminationType::USER_FAILURE;
120+
diag_msg = smoother_->terminationTypeToDiagnosticStatus(termination_type, diagnostic_warning_status,
121+
diagnostic_error_status);
122+
EXPECT_EQ(diag_msg.level, diagnostic_msgs::DiagnosticStatus::ERROR);
123+
}
124+
125+
int main(int argc, char** argv)
126+
{
127+
testing::InitGoogleTest(&argc, argv);
128+
ros::init(argc, argv, "fixed_lag_smoother_node");
129+
ros::AsyncSpinner spinner(1);
130+
spinner.start();
131+
int ret = RUN_ALL_TESTS();
132+
spinner.stop();
133+
ros::shutdown();
134+
return ret;
135+
}

0 commit comments

Comments
 (0)