Skip to content

Commit 3cf3437

Browse files
authored
Support continuous (unbounded) joints properly (#59)
1 parent f3778cc commit 3cf3437

File tree

6 files changed

+81
-45
lines changed

6 files changed

+81
-45
lines changed

include/pick_ik/robot.hpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,27 @@ namespace pick_ik {
1313

1414
struct Robot {
1515
struct Variable {
16-
double clip_min, clip_max;
17-
double span;
18-
double min;
19-
double max;
16+
/// @brief Min, max, and middle position values of the variable.
17+
double min, max, mid;
18+
19+
/// @brief Whether the variable's position is bounded.
20+
bool bounded;
21+
22+
/// @brief The half-span (min - max) / 2.0 of the variable, or a default value if unbounded.
23+
double half_span;
24+
2025
double max_velocity_rcp;
2126
double minimal_displacement_factor;
27+
28+
/// @brief Generates a valid variable value given an optional initial value (for unbounded
29+
/// joints).
30+
auto generate_valid_value(double init_val = 0.0) const -> double;
31+
32+
/// @brief Returns true if a value is valid given the variable bounds.
33+
auto is_valid(double val) const -> bool;
34+
35+
/// @brief Clamps a configuration to joint limits.
36+
auto clamp_to_limits(double val) const -> double;
2237
};
2338
std::vector<Variable> variables;
2439

@@ -27,8 +42,11 @@ struct Robot {
2742
moveit::core::JointModelGroup const* jmg,
2843
std::vector<size_t> tip_link_indices) -> Robot;
2944

30-
/** @brief Returns a random valid configuration. */
31-
auto get_random_valid_configuration() const -> std::vector<double>;
45+
/**
46+
* @brief Sets a variable vector to a random configuration.
47+
* @details Here, "valid" denotes that the joint values are with their specified limits.
48+
*/
49+
auto set_random_valid_configuration(std::vector<double>& config) const -> void;
3250

3351
/** @brief Check is a configuration is valid. */
3452
auto is_valid_configuration(std::vector<double> const& config) const -> bool;

src/goal.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ auto make_center_joints_cost_fn(Robot robot) -> CostFn {
9494
assert(robot.variables.size() == active_positions.size());
9595
for (size_t i = 0; i < active_positions.size(); ++i) {
9696
auto const& variable = robot.variables[i];
97-
if (variable.clip_max == std::numeric_limits<double>::max()) {
97+
if (!variable.bounded) {
9898
continue;
9999
}
100100

@@ -113,16 +113,16 @@ auto make_avoid_joint_limits_cost_fn(Robot robot) -> CostFn {
113113
assert(robot.variables.size() == active_positions.size());
114114
for (size_t i = 0; i < active_positions.size(); ++i) {
115115
auto const& variable = robot.variables[i];
116-
if (variable.clip_max == std::numeric_limits<double>::max()) {
116+
if (!variable.bounded) {
117117
continue;
118118
}
119119

120120
auto const position = active_positions[i];
121121
auto const weight = variable.minimal_displacement_factor;
122-
auto const mid = (variable.min + variable.max) * 0.5;
123-
auto const span = variable.span;
124-
sum +=
125-
std::pow(std::fmax(0.0, std::fabs(position - mid) * 2.0 - span * 0.5) * weight, 2);
122+
sum += std::pow(
123+
std::fmax(0.0, std::fabs(position - variable.mid) * 2.0 - variable.half_span) *
124+
weight,
125+
2);
126126
}
127127
return sum;
128128
};

src/ik_gradient.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ auto step(GradientIk& self, Robot const& robot, CostFn const& cost_fn, double st
2525
auto const count = self.local.size();
2626

2727
// compute gradient direction
28-
self.working = self.local;
2928
for (size_t i = 0; i < count; ++i) {
3029
// test negative displacement
3130
self.working[i] = self.local[i] - step_size;
@@ -55,8 +54,6 @@ auto step(GradientIk& self, Robot const& robot, CostFn const& cost_fn, double st
5554
[&](auto value) { return value * f; });
5655

5756
// initialize line search
58-
self.working = self.local;
59-
6057
for (size_t i = 0; i < count; ++i) {
6158
self.working[i] = self.local[i] - self.gradient[i];
6259
}
@@ -78,9 +75,9 @@ auto step(GradientIk& self, Robot const& robot, CostFn const& cost_fn, double st
7875
// apply optimization step
7976
// (move along gradient direction by estimated step size)
8077
for (size_t i = 0; i < count; ++i) {
81-
self.working[i] = std::clamp(self.local[i] - self.gradient[i] * joint_diff,
82-
robot.variables[i].clip_min,
83-
robot.variables[i].clip_max);
78+
auto const& var = robot.variables[i];
79+
auto updated_value = self.local[i] - self.gradient[i] * joint_diff;
80+
self.working[i] = var.clamp_to_limits(updated_value);
8481
}
8582

8683
// Always accept the solution and continue

src/ik_memetic.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ 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 const genotype = (i == 0) ? initial_guess : robot.get_random_valid_configuration();
99+
auto genotype = initial_guess;
100+
if (i > 0) {
101+
robot.set_random_valid_configuration(genotype);
102+
}
100103
population_[i] = Individual{genotype, cost_fn(genotype), 1.0, zero_grad};
101104
}
102105

@@ -105,6 +108,10 @@ void MemeticIk::initPopulation(Robot const& robot,
105108
population_[i] = Individual{initial_guess, 0.0, 1.0, zero_grad};
106109
}
107110

111+
// Initialize fitnesses and extinctions
112+
for (auto& individual : population_) {
113+
individual.fitness = cost_fn(individual.genes);
114+
}
108115
computeExtinctions();
109116
previous_fitness_.reset();
110117
}
@@ -149,11 +156,11 @@ void MemeticIk::reproduce(Robot const& robot, CostFn const& cost_fn) {
149156

150157
// Mutate
151158
if (rsl::uniform_real(0.0, 1.0) < mutation_prob) {
152-
gene += extinction * joint.span * rsl::uniform_real(-0.5, 0.5);
159+
gene += extinction * joint.half_span * rsl::uniform_real(-1.0, 1.0);
153160
}
154161

155162
// Clamp to valid joint values
156-
gene = std::clamp(gene, joint.clip_min, joint.clip_max);
163+
gene = robot.variables[j_idx].clamp_to_limits(gene);
157164

158165
// Approximate gradient
159166
population_[i].gradient[j_idx] = gene - original_gene;
@@ -173,7 +180,7 @@ void MemeticIk::reproduce(Robot const& robot, CostFn const& cost_fn) {
173180

174181
} else {
175182
// If the mating pool is empty, roll a new population member randomly.
176-
population_[i].genes = robot.get_random_valid_configuration();
183+
robot.set_random_valid_configuration(population_[i].genes);
177184
population_[i].fitness = cost_fn(population_[i].genes);
178185
for (auto& g : population_[i].gradient) {
179186
g = 0.0;

src/pick_ik_plugin.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class PickIKPlugin : public kinematics::KinematicsBase {
179179
RCLCPP_WARN(
180180
LOGGER,
181181
"Initial guess exceeds joint limits. Regenerating a random valid configuration.");
182-
init_state = robot_.get_random_valid_configuration();
182+
robot_.set_random_valid_configuration(init_state);
183183
}
184184

185185
// Optimize until a valid solution is found or we have timed out.
@@ -309,15 +309,11 @@ class PickIKPlugin : public kinematics::KinematicsBase {
309309
if (found_valid_solution || timeout_elapsed) {
310310
done_optimizing = true;
311311
} else {
312-
init_state = robot_.get_random_valid_configuration();
312+
robot_.set_random_valid_configuration(init_state);
313313
remaining_timeout -= total_optim_time.count();
314314
}
315315
}
316316

317-
if (!robot_.is_valid_configuration(solution)) {
318-
std::cout << "INVALID SOLUTION!" << std::endl;
319-
}
320-
321317
return found_valid_solution;
322318
}
323319

src/robot.cpp

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,34 @@
1313
#include <moveit/robot_model/robot_model.h>
1414
#include <moveit/robot_state/robot_state.h>
1515

16+
namespace {
17+
constexpr double kUnboundedVariableHalfSpan = M_PI;
18+
constexpr double kUnboundedJointSampleSpread = M_PI;
19+
} // namespace
20+
1621
namespace pick_ik {
1722

23+
auto Robot::Variable::generate_valid_value(double init_val /* = 0.0*/) const -> double {
24+
if (bounded) {
25+
return rsl::uniform_real(min, max);
26+
} else {
27+
return rsl::uniform_real(init_val - kUnboundedJointSampleSpread,
28+
init_val + kUnboundedJointSampleSpread);
29+
}
30+
}
31+
32+
auto Robot::Variable::is_valid(double val) const -> bool {
33+
return (!bounded) || (val <= max && val >= min);
34+
}
35+
36+
auto Robot::Variable::clamp_to_limits(double val) const -> double {
37+
if (bounded) {
38+
return std::clamp(val, min, max);
39+
} else {
40+
return std::clamp(val, val - half_span, val + half_span);
41+
}
42+
}
43+
1844
auto Robot::from(std::shared_ptr<moveit::core::RobotModel const> const& model,
1945
moveit::core::JointModelGroup const* jmg,
2046
std::vector<size_t> tip_link_indices) -> Robot {
@@ -33,17 +59,11 @@ auto Robot::from(std::shared_ptr<moveit::core::RobotModel const> const& model,
3359

3460
auto var = Variable{};
3561

36-
bool bounded = bounds.position_bounded_;
37-
62+
var.bounded = bounds.position_bounded_;
3863
var.min = bounds.min_position_;
3964
var.max = bounds.max_position_;
40-
41-
var.clip_min = bounded ? var.min : std::numeric_limits<double>::lowest();
42-
var.clip_max = bounded ? var.max : std::numeric_limits<double>::max();
43-
44-
var.span = var.max - var.min;
45-
46-
if (!(var.span >= 0 && var.span < std::numeric_limits<double>::max())) var.span = 1;
65+
var.mid = 0.5 * (var.min + var.max);
66+
var.half_span = var.bounded ? (var.max - var.min) / 2.0 : kUnboundedVariableHalfSpan;
4767

4868
auto const max_velocity = bounds.max_velocity_;
4969
var.max_velocity_rcp = max_velocity > 0.0 ? 1.0 / max_velocity : 0.0;
@@ -64,22 +84,20 @@ auto Robot::from(std::shared_ptr<moveit::core::RobotModel const> const& model,
6484
return robot;
6585
}
6686

67-
auto Robot::get_random_valid_configuration() const -> std::vector<double> {
68-
std::vector<double> config;
87+
auto Robot::set_random_valid_configuration(std::vector<double>& config) const -> void {
6988
auto const num_vars = variables.size();
70-
config.reserve(num_vars);
89+
if (config.size() != num_vars) {
90+
config.resize(num_vars);
91+
}
7192
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));
93+
config[idx] = variables[idx].generate_valid_value(config[idx]);
7494
}
75-
return config;
7695
}
7796

7897
auto Robot::is_valid_configuration(std::vector<double> const& config) const -> bool {
7998
auto const num_vars = variables.size();
8099
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) {
100+
if (!variables[idx].is_valid(config[idx])) {
83101
return false;
84102
}
85103
}

0 commit comments

Comments
 (0)