Skip to content

Commit c3ae35d

Browse files
committed
feat(freespace_planning_algorithms): add tests for updated astar algorithm
1 parent 56ad4b4 commit c3ae35d

File tree

2 files changed

+366
-1
lines changed

2 files changed

+366
-1
lines changed

planning/freespace_planning_algorithms/CMakeLists.txt

+7-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@ if(BUILD_TESTING)
3434
target_link_libraries(freespace_planning_algorithms-test
3535
freespace_planning_algorithms
3636
)
37-
37+
find_package(ament_cmake_ros REQUIRED)
38+
ament_add_ros_isolated_gtest(freespace_planning_algorithms_astar-test
39+
test/src/test_freespace_planning_algorithms_astar.cpp
40+
)
41+
target_link_libraries(freespace_planning_algorithms_astar-test
42+
freespace_planning_algorithms
43+
)
3844
find_package(ament_cmake_ros REQUIRED)
3945
ament_add_gtest(rrtstar_core_informed-test
4046
test/src/test_rrtstar_core_informed.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
// Copyright 2021 Tier IV, Inc. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "freespace_planning_algorithms/abstract_algorithm.hpp"
16+
#include "freespace_planning_algorithms/astar_search.hpp"
17+
#include "freespace_planning_algorithms/rrtstar.hpp"
18+
19+
#include <rclcpp/rclcpp.hpp>
20+
#include <rcpputils/filesystem_helper.hpp>
21+
#include <rosbag2_cpp/writer.hpp>
22+
23+
#include <std_msgs/msg/float64.hpp>
24+
25+
#include <gtest/gtest.h>
26+
#include <rcutils/time.h>
27+
#include <tf2/utils.h>
28+
29+
#include <algorithm>
30+
#include <array>
31+
#include <fstream>
32+
#include <string>
33+
#include <unordered_map>
34+
#include <vector>
35+
36+
namespace fpa = freespace_planning_algorithms;
37+
38+
const double length_lexus = 5.5;
39+
const double width_lexus = 2.75;
40+
const fpa::VehicleShape vehicle_shape = fpa::VehicleShape(length_lexus, width_lexus, 1.5);
41+
const double pi = 3.1415926;
42+
const std::array<double, 3> start_pose1{8., 7., pi * 0.5};
43+
const std::array<double, 3> start_pose2{9., 12., pi * 0.25};
44+
const std::array<double, 3> start_pose3{13.3, 9., pi * 0.5};
45+
const std::array<double, 3> start_pose4{25.0, 16., - pi * 0.25};
46+
const std::array<double, 3> start_pose5{20.5, 8.5, 0.0};
47+
const std::array<double, 3> goal_pose{18.0, 10.5, pi * 0.5};
48+
const std::array<std::array<double, 3>, 5> start_poses{
49+
start_pose1, start_pose2, start_pose3, start_pose4, start_pose5};
50+
51+
geometry_msgs::msg::Pose create_pose_msg(std::array<double, 3> pose3d)
52+
{
53+
geometry_msgs::msg::Pose pose{};
54+
tf2::Quaternion quat{};
55+
quat.setRPY(0, 0, pose3d[2]);
56+
tf2::convert(quat, pose.orientation);
57+
pose.position.x = pose3d[0];
58+
pose.position.y = pose3d[1];
59+
pose.position.z = 0.0;
60+
return pose;
61+
}
62+
63+
std_msgs::msg::Float64 create_float_msg(double val)
64+
{
65+
std_msgs::msg::Float64 msg;
66+
msg.data = val;
67+
return msg;
68+
}
69+
70+
nav_msgs::msg::OccupancyGrid construct_cost_map(
71+
size_t width, size_t height, double resolution, size_t n_padding)
72+
{
73+
nav_msgs::msg::OccupancyGrid costmap_msg{};
74+
75+
// create info
76+
costmap_msg.info.width = width;
77+
costmap_msg.info.height = height;
78+
costmap_msg.info.resolution = resolution;
79+
80+
// create data
81+
const size_t n_elem = width * height;
82+
for (size_t i = 0; i < n_elem; ++i) {
83+
costmap_msg.data.push_back(0.0);
84+
}
85+
86+
for (size_t i = 0; i < height; ++i) {
87+
for (size_t j = 0; j < width; ++j) {
88+
const double x = j * resolution;
89+
const double y = i * resolution;
90+
91+
//road
92+
if (5.4 < x && x < 5.4 + 4.5 && 0.0 < y && y < 0.0 + 19.4) {
93+
costmap_msg.data[i * width + j] = 40.0;
94+
}
95+
96+
if (9.0 < x && x < 40.0 && 15.4 < y && y < 15.4 + 4.0) {
97+
costmap_msg.data[i * width + j] = 40.0;
98+
}
99+
100+
double width_p = 3.0;
101+
double height_p = 6.0;
102+
103+
//parking 1 (16,9)
104+
if (16.0 < x && x < 16.0 + width_p && 9.0 < y && y < 9.0 + height_p) {
105+
costmap_msg.data[i * width + j] = 50.0;
106+
}
107+
108+
//parking 2
109+
if (19.3 < x && x < 19.3 + width_p && 9.0 < y && y < 9.0 + height_p) {
110+
costmap_msg.data[i * width + j] = 50.0;
111+
}
112+
113+
//parking 3
114+
if (22.6 < x && x < 22.6 + width_p && 9.0 < y && y < 9.0 + height_p) {
115+
costmap_msg.data[i * width + j] = 50.0;
116+
}
117+
118+
//parking 4
119+
if (25.9 < x && x < 25.9 + width_p && 9.0 < y && y < 9.0 + height_p) {
120+
costmap_msg.data[i * width + j] = 50.0;
121+
}
122+
123+
//parking 5
124+
if (29.2 < x && x < 29.2 + width_p && 9.0 < y && y < 9.0 + height_p) {
125+
costmap_msg.data[i * width + j] = 50.0;
126+
}
127+
}
128+
}
129+
130+
for (size_t i = 0; i < n_padding; ++i) {
131+
// fill left
132+
for (size_t j = width * i; j <= width * (i + 1); ++j) {
133+
costmap_msg.data[j] = 100.0;
134+
}
135+
// fill right
136+
for (size_t j = width * (height - n_padding + i); j <= width * (height - n_padding + i + 1);
137+
++j) {
138+
costmap_msg.data[j] = 100.0;
139+
}
140+
}
141+
142+
for (size_t i = 0; i < height; ++i) {
143+
// fill bottom
144+
for (size_t j = i * width; j <= i * width + n_padding; ++j) {
145+
costmap_msg.data[j] = 100.0;
146+
}
147+
for (size_t j = (i + 1) * width - n_padding; j <= (i + 1) * width; ++j) {
148+
costmap_msg.data[j] = 100.0;
149+
}
150+
}
151+
152+
return costmap_msg;
153+
}
154+
155+
template <typename MessageT>
156+
void add_message_to_rosbag(
157+
rosbag2_cpp::Writer & writer, const MessageT & message, const std::string & name,
158+
const std::string & type)
159+
{
160+
rclcpp::SerializedMessage serialized_msg;
161+
rclcpp::Serialization<MessageT> serialization;
162+
serialization.serialize_message(&message, &serialized_msg);
163+
164+
rosbag2_storage::TopicMetadata tm;
165+
tm.name = name;
166+
tm.type = type;
167+
tm.serialization_format = "cdr";
168+
writer.create_topic(tm);
169+
170+
auto bag_message = std::make_shared<rosbag2_storage::SerializedBagMessage>();
171+
auto ret = rcutils_system_time_now(&bag_message->time_stamp);
172+
if (ret != RCL_RET_OK) {
173+
RCLCPP_ERROR(rclcpp::get_logger("saveToBag"), "couldn't assign time rosbag message");
174+
}
175+
176+
bag_message->topic_name = tm.name;
177+
bag_message->serialized_data = std::shared_ptr<rcutils_uint8_array_t>(
178+
&serialized_msg.get_rcl_serialized_message(), [](rcutils_uint8_array_t * /* data */) {});
179+
writer.write(bag_message);
180+
}
181+
182+
fpa::PlannerCommonParam get_default_planner_params()
183+
{
184+
// set problem configuration
185+
const double time_limit = 10 * 1000.0;
186+
const double minimum_turning_radius = 9.0;
187+
const double maximum_turning_radius = 9.0;
188+
const int turning_radius_size = 1;
189+
190+
// Transitions every 2.5 deg
191+
const int theta_size = 144;
192+
193+
const double curve_weight = 1.5;
194+
const double reverse_weight = 2.0;
195+
196+
const double lateral_goal_range = 0.5;
197+
const double longitudinal_goal_range = 2.0;
198+
const double angle_goal_range = 6.0;
199+
const int obstacle_threshold = 100;
200+
201+
return fpa::PlannerCommonParam{
202+
time_limit,
203+
minimum_turning_radius,
204+
maximum_turning_radius,
205+
turning_radius_size,
206+
theta_size,
207+
curve_weight,
208+
reverse_weight,
209+
lateral_goal_range,
210+
longitudinal_goal_range,
211+
angle_goal_range,
212+
obstacle_threshold};
213+
}
214+
215+
std::unique_ptr<fpa::AbstractPlanningAlgorithm> configure_astar(bool use_multi, bool cw)
216+
{
217+
auto planner_common_param = get_default_planner_params();
218+
219+
// configure astar param
220+
const bool only_behind_solutions = false;
221+
const bool use_back = true;
222+
const bool use_curve_weight = false;
223+
const bool use_complete_astar = false;
224+
const double distance_heuristic_weight = 1.0;
225+
auto astar_param =
226+
fpa::AstarParam{only_behind_solutions, use_back, use_curve_weight, use_complete_astar, distance_heuristic_weight};
227+
228+
if (use_multi) {
229+
planner_common_param.minimum_turning_radius = 5.0;
230+
planner_common_param.maximum_turning_radius = 9.0;
231+
planner_common_param.turning_radius_size = 3;
232+
// if multi-curvature and using curve weight
233+
if (cw){
234+
astar_param.use_curve_weight = true;
235+
}
236+
astar_param.use_complete_astar = true;
237+
}
238+
239+
auto algo = std::make_unique<fpa::AstarSearch>(planner_common_param, vehicle_shape, astar_param);
240+
return algo;
241+
}
242+
243+
enum AlgorithmType {
244+
ASTAR_SINGLE,
245+
ASTAR_MULTI,
246+
ASTAR_MULTI_NOCW,
247+
};
248+
// cspell: ignore fpalgos
249+
std::unordered_map<AlgorithmType, std::string> rosbag_dir_prefix_table(
250+
{{ASTAR_SINGLE, "fpalgos-astar_single"},
251+
{ASTAR_MULTI, "fpalgos-astar_multi"},
252+
{ASTAR_MULTI_NOCW, "fpalgos-astar_multi_nocw"},
253+
});
254+
255+
bool test_algorithm(enum AlgorithmType algo_type, bool dump_rosbag = false)
256+
{
257+
std::unique_ptr<fpa::AbstractPlanningAlgorithm> algo;
258+
if (algo_type == AlgorithmType::ASTAR_SINGLE) {
259+
algo = configure_astar(false, false);
260+
} else if (algo_type == AlgorithmType::ASTAR_MULTI) {
261+
algo = configure_astar(true, true);
262+
} else if (algo_type == AlgorithmType::ASTAR_MULTI_NOCW) {
263+
algo = configure_astar(true, false);
264+
} else {
265+
throw std::runtime_error("invalid algorithm time");
266+
}
267+
268+
// All algorithms have the same interface.
269+
const auto costmap_msg = construct_cost_map(200, 120, 0.2, 10);
270+
bool success_all = true; // if any local test below fails, overwrite this function
271+
272+
rclcpp::Clock clock{RCL_SYSTEM_TIME};
273+
for (size_t i = 0; i < start_poses.size(); ++i) {
274+
const auto start_pose = start_poses.at(i);
275+
276+
algo->setMap(costmap_msg);
277+
double msec;
278+
double cost;
279+
280+
{
281+
const rclcpp::Time begin = clock.now();
282+
if (!algo->makePlan(create_pose_msg(start_pose), create_pose_msg(goal_pose))) {
283+
success_all = false;
284+
std::cout << "plan fail" << std::endl;
285+
continue;
286+
}
287+
const rclcpp::Time now = clock.now();
288+
msec = (now - begin).seconds() * 1000.0;
289+
cost = algo->getWaypoints().compute_length();
290+
}
291+
292+
std::cout << "plan success : " << msec << "[msec]"
293+
<< ", solution cost : " << cost << std::endl;
294+
const auto result = algo->getWaypoints();
295+
geometry_msgs::msg::PoseArray trajectory;
296+
for (const auto & pose : result.waypoints) {
297+
trajectory.poses.push_back(pose.pose.pose);
298+
}
299+
if (algo->hasObstacleOnTrajectory(trajectory)) {
300+
std::cout << "not feasible trajectory" << std::endl;
301+
success_all = false;
302+
}
303+
304+
if (dump_rosbag) {
305+
// dump rosbag for visualization using python script
306+
const std::string dir_name =
307+
"/tmp/" + rosbag_dir_prefix_table[algo_type] + "-case" + std::to_string(i);
308+
309+
rcpputils::fs::remove_all(dir_name);
310+
311+
rosbag2_storage::StorageOptions storage_options;
312+
storage_options.uri = dir_name;
313+
storage_options.storage_id = "sqlite3";
314+
315+
rosbag2_cpp::ConverterOptions converter_options;
316+
converter_options.input_serialization_format = "cdr";
317+
converter_options.output_serialization_format = "cdr";
318+
319+
rosbag2_cpp::Writer writer(std::make_unique<rosbag2_cpp::writers::SequentialWriter>());
320+
writer.open(storage_options, converter_options);
321+
322+
add_message_to_rosbag(
323+
writer, create_float_msg(vehicle_shape.length), "vehicle_length", "std_msgs/msg/Float64");
324+
add_message_to_rosbag(
325+
writer, create_float_msg(vehicle_shape.width), "vehicle_width", "std_msgs/msg/Float64");
326+
add_message_to_rosbag(
327+
writer, create_float_msg(vehicle_shape.base2back), "vehicle_base2back",
328+
"std_msgs/msg/Float64");
329+
330+
add_message_to_rosbag(writer, costmap_msg, "costmap", "nav_msgs/msg/OccupancyGrid");
331+
add_message_to_rosbag(writer, create_pose_msg(start_pose), "start", "geometry_msgs/msg/Pose");
332+
add_message_to_rosbag(writer, create_pose_msg(goal_pose), "goal", "geometry_msgs/msg/Pose");
333+
add_message_to_rosbag(writer, trajectory, "trajectory", "geometry_msgs/msg/PoseArray");
334+
add_message_to_rosbag(writer, create_float_msg(msec), "elapsed_time", "std_msgs/msg/Float64");
335+
}
336+
}
337+
return success_all;
338+
}
339+
340+
TEST(AstarSearchTestSuite, SingleCurvature)
341+
{
342+
EXPECT_TRUE(test_algorithm(AlgorithmType::ASTAR_SINGLE));
343+
}
344+
345+
TEST(AstarSearchTestSuite, MultiCurvature)
346+
{
347+
EXPECT_TRUE(test_algorithm(AlgorithmType::ASTAR_MULTI));
348+
}
349+
350+
TEST(AstarSearchTestSuite, MultiCurvaturenocw)
351+
{
352+
EXPECT_TRUE(test_algorithm(AlgorithmType::ASTAR_MULTI_NOCW));
353+
}
354+
355+
int main(int argc, char ** argv)
356+
{
357+
testing::InitGoogleTest(&argc, argv);
358+
return RUN_ALL_TESTS();
359+
}

0 commit comments

Comments
 (0)