Skip to content

Commit 7384e05

Browse files
authored
Rerun optimization until valid solution or timeout (#53)
1 parent 9904c89 commit 7384e05

File tree

4 files changed

+173
-111
lines changed

4 files changed

+173
-111
lines changed

include/pick_ik/robot.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,16 @@ struct Robot {
2222
};
2323
std::vector<Variable> variables;
2424

25-
// Create new Robot from a RobotModel
25+
/** @brief Create new Robot from a RobotModel. */
2626
static auto from(std::shared_ptr<moveit::core::RobotModel const> const& model,
2727
moveit::core::JointModelGroup const* jmg,
2828
std::vector<size_t> tip_link_indices) -> Robot;
29+
30+
/** @brief Returns a random valid configuration. */
31+
auto get_random_valid_configuration() const -> std::vector<double>;
32+
33+
/** @brief Check is a configuration is valid. */
34+
auto is_valid_configuration(std::vector<double> const& config) const -> bool;
2935
};
3036

3137
auto get_link_indices(std::shared_ptr<moveit::core::RobotModel const> const& model,

src/ik_memetic.cpp

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,7 @@ void MemeticIk::initPopulation(Robot const& robot,
9696
std::vector<double> const zero_grad(robot.variables.size(), 0.0);
9797
population_.resize(params_.population_size);
9898
for (size_t i = 0; i < params_.elite_size; ++i) {
99-
auto genotype = initial_guess;
100-
if (i > 0) {
101-
for (size_t j_idx = 0; j_idx < robot.variables.size(); ++j_idx) {
102-
auto const& var = robot.variables[j_idx];
103-
genotype[j_idx] = rsl::uniform_real(var.clip_min, var.clip_max);
104-
}
105-
}
99+
auto const genotype = (i == 0) ? initial_guess : robot.get_random_valid_configuration();
106100
population_[i] = Individual{genotype, cost_fn(genotype), 1.0, zero_grad};
107101
}
108102

@@ -179,10 +173,7 @@ void MemeticIk::reproduce(Robot const& robot, CostFn const& cost_fn) {
179173

180174
} else {
181175
// If the mating pool is empty, roll a new population member randomly.
182-
for (size_t j_idx = 0; j_idx < robot.variables.size(); ++j_idx) {
183-
auto const& var = robot.variables[j_idx];
184-
population_[i].genes[j_idx] = rsl::uniform_real(var.clip_min, var.clip_max);
185-
}
176+
population_[i].genes = robot.get_random_valid_configuration();
186177
population_[i].fitness = cost_fn(population_[i].genes);
187178
for (auto& g : population_[i].gradient) {
188179
g = 0.0;

src/pick_ik_plugin.cpp

Lines changed: 141 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <pluginlib/class_list_macros.hpp>
99
#include <rclcpp/rclcpp.hpp>
1010

11+
#include <chrono>
1112
#include <moveit/kinematics_base/kinematics_base.h>
1213
#include <moveit/robot_model/joint_model_group.h>
1314
#include <moveit/robot_state/robot_state.h>
@@ -164,119 +165,160 @@ class PickIKPlugin : public kinematics::KinematicsBase {
164165
// single function used by gradient descent to calculate cost of solution
165166
auto const cost_fn = make_cost_fn(pose_cost_functions, goals, fk_fn);
166167

167-
// Search for a solution using either the local or global solver.
168-
std::optional<std::vector<double>> maybe_solution;
169-
if (params.mode == "global") {
170-
MemeticIkParams ik_params;
171-
ik_params.population_size = static_cast<size_t>(params.memetic_population_size);
172-
ik_params.elite_size = static_cast<size_t>(params.memetic_elite_size);
173-
ik_params.wipeout_fitness_tol = params.memetic_wipeout_fitness_tol;
174-
ik_params.stop_optimization_on_valid_solution =
175-
params.stop_optimization_on_valid_solution;
176-
ik_params.num_threads = static_cast<size_t>(params.memetic_num_threads);
177-
ik_params.stop_on_first_soln = params.memetic_stop_on_first_solution;
178-
ik_params.max_generations = static_cast<int>(params.memetic_max_generations);
179-
ik_params.max_time = timeout;
180-
181-
ik_params.gd_params.step_size = params.gd_step_size;
182-
ik_params.gd_params.min_cost_delta = params.gd_min_cost_delta;
183-
ik_params.gd_params.max_iterations = static_cast<int>(params.memetic_gd_max_iters);
184-
ik_params.gd_params.max_time = params.memetic_gd_max_time;
185-
186-
maybe_solution = ik_memetic(ik_seed_state,
187-
robot_,
188-
cost_fn,
189-
solution_fn,
190-
ik_params,
191-
options.return_approximate_solution,
192-
false /* No debug print */);
193-
} else if (params.mode == "local") {
194-
GradientIkParams gd_params;
195-
gd_params.step_size = params.gd_step_size;
196-
gd_params.min_cost_delta = params.gd_min_cost_delta;
197-
gd_params.max_time = timeout;
198-
gd_params.max_iterations = static_cast<int>(params.gd_max_iters);
199-
gd_params.stop_optimization_on_valid_solution =
200-
params.stop_optimization_on_valid_solution;
201-
202-
maybe_solution = ik_gradient(ik_seed_state,
203-
robot_,
204-
cost_fn,
205-
solution_fn,
206-
gd_params,
207-
options.return_approximate_solution);
208-
} else {
209-
RCLCPP_ERROR(LOGGER, "Invalid solver mode: %s", params.mode.c_str());
210-
return false;
211-
}
212-
213-
if (maybe_solution.has_value()) {
214-
// Set the output parameter solution.
215-
// Assumes that the angles were already wrapped by the solver.
216-
error_code.val = error_code.SUCCESS;
217-
solution = maybe_solution.value();
218-
} else {
219-
error_code.val = error_code.NO_IK_SOLUTION;
220-
solution = ik_seed_state;
168+
// Set up initial optimization variables
169+
bool done_optimizing = false;
170+
bool found_valid_solution = false;
171+
double remaining_timeout = timeout;
172+
std::chrono::duration<double> total_optim_time{0.0};
173+
std::chrono::duration<double> const total_timeout{timeout};
174+
auto last_optim_time = std::chrono::system_clock::now();
175+
176+
// If the initial state is not valid, restart from a random valid state.
177+
auto init_state = ik_seed_state;
178+
if (!robot_.is_valid_configuration(init_state)) {
179+
RCLCPP_WARN(
180+
LOGGER,
181+
"Initial guess exceeds joint limits. Regenerating a random valid configuration.");
182+
init_state = robot_.get_random_valid_configuration();
221183
}
222184

223-
// If using an approximate solution, check against the maximum allowable pose and joint
224-
// thresholds. If the approximate solution is too far from the goal frame,
225-
// fall back to the initial state.
226-
if (options.return_approximate_solution) {
227-
// Check pose thresholds
228-
std::optional<double> approximate_solution_position_threshold = std::nullopt;
229-
if (test_position) {
230-
approximate_solution_position_threshold =
231-
params.approximate_solution_position_threshold;
232-
}
233-
std::optional<double> approximate_solution_orientation_threshold = std::nullopt;
234-
if (test_rotation) {
235-
approximate_solution_orientation_threshold =
236-
params.approximate_solution_orientation_threshold;
185+
// Optimize until a valid solution is found or we have timed out.
186+
while (!done_optimizing) {
187+
// Search for a solution using either the local or global solver.
188+
std::optional<std::vector<double>> maybe_solution;
189+
if (params.mode == "global") {
190+
MemeticIkParams ik_params;
191+
ik_params.population_size = static_cast<size_t>(params.memetic_population_size);
192+
ik_params.elite_size = static_cast<size_t>(params.memetic_elite_size);
193+
ik_params.wipeout_fitness_tol = params.memetic_wipeout_fitness_tol;
194+
ik_params.stop_optimization_on_valid_solution =
195+
params.stop_optimization_on_valid_solution;
196+
ik_params.num_threads = static_cast<size_t>(params.memetic_num_threads);
197+
ik_params.stop_on_first_soln = params.memetic_stop_on_first_solution;
198+
ik_params.max_generations = static_cast<int>(params.memetic_max_generations);
199+
ik_params.max_time = remaining_timeout;
200+
201+
ik_params.gd_params.step_size = params.gd_step_size;
202+
ik_params.gd_params.min_cost_delta = params.gd_min_cost_delta;
203+
ik_params.gd_params.max_iterations = static_cast<int>(params.memetic_gd_max_iters);
204+
ik_params.gd_params.max_time = params.memetic_gd_max_time;
205+
206+
maybe_solution = ik_memetic(ik_seed_state,
207+
robot_,
208+
cost_fn,
209+
solution_fn,
210+
ik_params,
211+
options.return_approximate_solution,
212+
false /* No debug print */);
213+
} else if (params.mode == "local") {
214+
GradientIkParams gd_params;
215+
gd_params.step_size = params.gd_step_size;
216+
gd_params.min_cost_delta = params.gd_min_cost_delta;
217+
gd_params.max_time = remaining_timeout;
218+
gd_params.max_iterations = static_cast<int>(params.gd_max_iters);
219+
gd_params.stop_optimization_on_valid_solution =
220+
params.stop_optimization_on_valid_solution;
221+
222+
maybe_solution = ik_gradient(ik_seed_state,
223+
robot_,
224+
cost_fn,
225+
solution_fn,
226+
gd_params,
227+
options.return_approximate_solution);
228+
} else {
229+
RCLCPP_ERROR(LOGGER, "Invalid solver mode: %s", params.mode.c_str());
230+
return false;
237231
}
238-
auto const approx_frame_tests =
239-
make_frame_tests(goal_frames,
240-
approximate_solution_position_threshold,
241-
approximate_solution_orientation_threshold);
242-
243-
// If we have no cost threshold, we don't need to check the goals
244-
if (params.approximate_solution_cost_threshold <= 0.0) {
245-
goals.clear();
232+
233+
if (maybe_solution.has_value()) {
234+
// Set the output parameter solution.
235+
// Assumes that the angles were already wrapped by the solver.
236+
error_code.val = error_code.SUCCESS;
237+
solution = maybe_solution.value();
238+
} else {
239+
error_code.val = error_code.NO_IK_SOLUTION;
240+
solution = ik_seed_state;
246241
}
247242

248-
auto const approx_solution_fn =
249-
make_is_solution_test_fn(frame_tests,
250-
goals,
251-
params.approximate_solution_cost_threshold,
252-
fk_fn);
253-
254-
bool approx_solution_valid = approx_solution_fn(solution);
255-
256-
// Check joint thresholds
257-
if (approx_solution_valid && params.approximate_solution_joint_threshold > 0.0) {
258-
for (size_t i = 0; i < solution.size(); ++i) {
259-
if (std::abs(solution[i] - ik_seed_state[i]) >
260-
params.approximate_solution_joint_threshold) {
261-
approx_solution_valid = false;
262-
break;
243+
// If using an approximate solution, check against the maximum allowable pose and joint
244+
// thresholds. If the approximate solution is too far from the goal frame,
245+
// fall back to the initial state.
246+
if (options.return_approximate_solution) {
247+
// Check pose thresholds
248+
std::optional<double> approximate_solution_position_threshold = std::nullopt;
249+
if (test_position) {
250+
approximate_solution_position_threshold =
251+
params.approximate_solution_position_threshold;
252+
}
253+
std::optional<double> approximate_solution_orientation_threshold = std::nullopt;
254+
if (test_rotation) {
255+
approximate_solution_orientation_threshold =
256+
params.approximate_solution_orientation_threshold;
257+
}
258+
auto const approx_frame_tests =
259+
make_frame_tests(goal_frames,
260+
approximate_solution_position_threshold,
261+
approximate_solution_orientation_threshold);
262+
263+
// If we have no cost threshold, we don't need to check the goals
264+
if (params.approximate_solution_cost_threshold <= 0.0) {
265+
goals.clear();
266+
}
267+
268+
auto const approx_solution_fn =
269+
make_is_solution_test_fn(frame_tests,
270+
goals,
271+
params.approximate_solution_cost_threshold,
272+
fk_fn);
273+
274+
bool approx_solution_valid = approx_solution_fn(solution);
275+
276+
// Check joint thresholds
277+
if (approx_solution_valid && params.approximate_solution_joint_threshold > 0.0) {
278+
for (size_t i = 0; i < solution.size(); ++i) {
279+
if (std::abs(solution[i] - ik_seed_state[i]) >
280+
params.approximate_solution_joint_threshold) {
281+
approx_solution_valid = false;
282+
break;
283+
}
263284
}
264285
}
286+
287+
if (!approx_solution_valid) {
288+
error_code.val = error_code.NO_IK_SOLUTION;
289+
solution = ik_seed_state;
290+
}
265291
}
266292

267-
if (!approx_solution_valid) {
268-
error_code.val = error_code.NO_IK_SOLUTION;
269-
solution = ik_seed_state;
293+
// Execute solution callback only on successful solution.
294+
auto const found_solution = error_code.val == error_code.SUCCESS;
295+
if (solution_callback && found_solution) {
296+
solution_callback(ik_poses.front(), solution, error_code);
297+
}
298+
found_valid_solution = error_code.val == error_code.SUCCESS;
299+
300+
// Check for timeout.
301+
auto const current_time = std::chrono::system_clock::now();
302+
total_optim_time += (current_time - last_optim_time);
303+
last_optim_time = current_time;
304+
bool const timeout_elapsed = (total_optim_time >= total_timeout);
305+
306+
// If we found a valid solution or hit the timeout, we are done optimizing.
307+
// Otherwise, pick a random new initial seed and keep optimizing with the remaining
308+
// time.
309+
if (found_valid_solution || timeout_elapsed) {
310+
done_optimizing = true;
311+
} else {
312+
init_state = robot_.get_random_valid_configuration();
313+
remaining_timeout -= total_optim_time.count();
270314
}
271315
}
272316

273-
// Execute solution callback only on successful solution.
274-
auto const found_solution = error_code.val == error_code.SUCCESS;
275-
if (solution_callback && found_solution) {
276-
solution_callback(ik_poses.front(), solution, error_code);
317+
if (!robot_.is_valid_configuration(solution)) {
318+
std::cout << "INVALID SOLUTION!" << std::endl;
277319
}
278320

279-
return error_code.val == error_code.SUCCESS;
321+
return found_valid_solution;
280322
}
281323

282324
virtual std::vector<std::string> const& getJointNames() const { return joint_names_; }

src/robot.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <pick_ik/robot.hpp>
22

3+
#include <rsl/random.hpp>
34
#include <tf2_eigen/tf2_eigen.hpp>
45
#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
56
#include <tl_expected/expected.hpp>
@@ -63,6 +64,28 @@ auto Robot::from(std::shared_ptr<moveit::core::RobotModel const> const& model,
6364
return robot;
6465
}
6566

67+
auto Robot::get_random_valid_configuration() const -> std::vector<double> {
68+
std::vector<double> config;
69+
auto const num_vars = variables.size();
70+
config.reserve(num_vars);
71+
for (size_t idx = 0; idx < num_vars; ++idx) {
72+
auto const var = variables[idx];
73+
config.push_back(rsl::uniform_real(var.clip_min, var.clip_max));
74+
}
75+
return config;
76+
}
77+
78+
auto Robot::is_valid_configuration(std::vector<double> const& config) const -> bool {
79+
auto const num_vars = variables.size();
80+
for (size_t idx = 0; idx < num_vars; ++idx) {
81+
auto const var = variables[idx];
82+
if (config[idx] > var.clip_max || config[idx] < var.clip_min) {
83+
return false;
84+
}
85+
}
86+
return true;
87+
}
88+
6689
auto get_link_indices(std::shared_ptr<moveit::core::RobotModel const> const& model,
6790
std::vector<std::string> const& names)
6891
-> tl::expected<std::vector<size_t>, std::string> {

0 commit comments

Comments
 (0)