Skip to content

Commit 38eea96

Browse files
authored
Fix position limits oink constraint for models with continuous joints (#147)
1 parent 802f324 commit 38eea96

File tree

4 files changed

+138
-8
lines changed

4 files changed

+138
-8
lines changed

roboplan/src/core/scene.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,39 @@ Scene::Scene(const std::string& name, const std::string& urdf, const std::string
138138
const auto& mimicked_joint_name = mimic_model.names[mimicked_idx];
139139

140140
auto* mimic_joint = boost::get<pinocchio::JointModelMimic>(&mimicking_joint);
141-
const auto mimicked_joint_type = joint_info_map_.at(mimicked_joint_name).type;
142-
auto info = JointInfo(mimicked_joint_type);
141+
const auto mimicked_joint_info = joint_info_map_.at(mimicked_joint_name);
142+
auto info = JointInfo(mimicked_joint_info.type);
143143
info.mimic_info = JointMimicInfo{
144144
.mimicked_joint_name = mimicked_joint_name,
145145
.scaling = mimic_joint->scaling(),
146146
.offset = mimic_joint->offset(),
147147
};
148+
149+
// Compute derived joint limits.
150+
// If the scaling is negative, the position limits have to be swapped.
151+
if (mimic_joint->scaling() > 0.0) {
152+
info.limits.min_position =
153+
(mimicked_joint_info.limits.min_position.array() * mimic_joint->scaling() +
154+
mimic_joint->offset())
155+
.matrix();
156+
info.limits.max_position =
157+
(mimicked_joint_info.limits.max_position.array() * mimic_joint->scaling() +
158+
mimic_joint->offset())
159+
.matrix();
160+
} else {
161+
info.limits.min_position =
162+
(mimicked_joint_info.limits.max_position.array() * mimic_joint->scaling() +
163+
mimic_joint->offset())
164+
.matrix();
165+
info.limits.max_position =
166+
(mimicked_joint_info.limits.min_position.array() * mimic_joint->scaling() +
167+
mimic_joint->offset())
168+
.matrix();
169+
}
170+
const auto scaling_abs = std::abs(mimic_joint->scaling());
171+
info.limits.max_velocity = mimicked_joint_info.limits.max_velocity * scaling_abs;
172+
info.limits.max_acceleration = mimicked_joint_info.limits.max_acceleration * scaling_abs;
173+
info.limits.max_jerk = mimicked_joint_info.limits.max_jerk * scaling_abs;
148174
joint_info_map_.emplace(mimicking_joint_name, info);
149175
}
150176

roboplan_oink/include/roboplan_oink/constraints/position_limit.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ struct PositionLimit : public Constraints {
3434

3535
double config_limit_gain; /// Gain parameter for steering away from limits
3636
int num_variables; /// Number of variables (cached from model.nv)
37+
mutable Eigen::VectorXd q_max; /// Pre-allocated maximum joint position limits.
38+
mutable Eigen::VectorXd q_min; /// Pre-allocated minimum joint position limits.
3739
mutable Eigen::VectorXd delta_q_max; /// Pre-allocated workspace for max joint deltas
3840
mutable Eigen::VectorXd delta_q_min; /// Pre-allocated workspace for min joint deltas
3941
};

roboplan_oink/src/constraints/position_limit.cpp

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <roboplan_oink/constraints/position_limit.hpp>
22

33
#include <OsqpEigen/OsqpEigen.h>
4+
#include <roboplan/core/scene_utils.hpp>
45

56
namespace roboplan {
67

@@ -16,6 +17,50 @@ tl::expected<void, std::string> PositionLimit::computeQpConstraints(
1617
const auto& model = scene.getModel();
1718
const auto& q = scene.getCurrentJointPositions();
1819

20+
auto maybe_q_collapsed = collapseContinuousJointPositions(scene, "", q);
21+
if (!maybe_q_collapsed) {
22+
return tl::make_unexpected("Failed to compute position constraint: " +
23+
maybe_q_collapsed.error());
24+
}
25+
const auto& q_collapsed = maybe_q_collapsed.value();
26+
27+
// Get joint limits from the model (only do this once)
28+
if (q_min.size() == 0u) {
29+
q_min.resize(num_variables);
30+
q_max.resize(num_variables);
31+
const auto joint_names = scene.getJointNames();
32+
for (int idx = 0; idx < num_variables; ++idx) {
33+
const auto& joint_name = joint_names.at(idx);
34+
const auto maybe_joint_info = scene.getJointInfo(joint_name);
35+
if (!maybe_joint_info) {
36+
return tl::make_unexpected("Failed to get joint limits for position constraint: " +
37+
maybe_joint_info.error());
38+
}
39+
const auto& joint_info = maybe_joint_info.value();
40+
41+
switch (joint_info.type) {
42+
case JointType::FLOATING:
43+
case JointType::PLANAR:
44+
return tl::make_unexpected("Multi-DOF joints not yet supported by position constraints.");
45+
case JointType::CONTINUOUS:
46+
q_min(idx) = -std::numeric_limits<double>::infinity();
47+
q_max(idx) = std::numeric_limits<double>::infinity();
48+
break;
49+
default:
50+
if (joint_info.limits.min_position.size() == 0) {
51+
q_min(idx) = -std::numeric_limits<double>::infinity();
52+
} else {
53+
q_min(idx) = joint_info.limits.min_position(0);
54+
}
55+
if (joint_info.limits.max_position.size() == 0) {
56+
q_max(idx) = std::numeric_limits<double>::infinity();
57+
} else {
58+
q_max(idx) = joint_info.limits.max_position(0);
59+
}
60+
}
61+
}
62+
}
63+
1964
// Validate that model dimensions match constructor
2065
if (model.nv != num_variables) {
2166
return tl::make_unexpected("PositionLimit: model.nv (" + std::to_string(model.nv) +
@@ -42,23 +87,19 @@ tl::expected<void, std::string> PositionLimit::computeQpConstraints(
4287
std::to_string(upper_bounds.size()));
4388
}
4489

45-
// Get joint limits from the model
46-
const Eigen::VectorXd& q_min = model.lowerPositionLimit;
47-
const Eigen::VectorXd& q_max = model.upperPositionLimit;
48-
4990
// Assuming single DOF joints (revolute/prismatic), nq == nv
5091
// Compute distances to limits and scale by gain, then write to bounds
5192
for (int i = 0; i < num_variables; ++i) {
5293
// Compute distance to upper limit
5394
if (std::isfinite(q_max(i))) {
54-
delta_q_max(i) = q_max(i) - q(i);
95+
delta_q_max(i) = q_max(i) - q_collapsed(i);
5596
} else {
5697
delta_q_max(i) = std::numeric_limits<double>::infinity();
5798
}
5899

59100
// Compute distance to lower limit
60101
if (std::isfinite(q_min(i))) {
61-
delta_q_min(i) = q(i) - q_min(i);
102+
delta_q_min(i) = q_collapsed(i) - q_min(i);
62103
} else {
63104
delta_q_min(i) = std::numeric_limits<double>::infinity();
64105
}

roboplan_oink/test/constraints/test_position_limit.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,65 @@ TEST_F(PositionLimitTest, DifferentConfigurationsDifferentBounds) {
393393
EXPECT_FALSE(lower_bounds1.isApprox(lower_bounds2));
394394
}
395395

396+
class KinovaPositionLimitTest : public ::testing::Test {
397+
protected:
398+
void SetUp() override {
399+
// Use Kinova robot here, since it has continuous and mimic joints.
400+
const auto model_prefix = example_models::get_package_models_dir();
401+
urdf_path_ = model_prefix / "kinova_robot_model" / "kinova_robotiq.urdf";
402+
srdf_path_ = model_prefix / "kinova_robot_model" / "kinova_robotiq.srdf";
403+
package_paths_ = {example_models::get_package_share_dir()};
404+
yaml_config_path_ = model_prefix / "kinova_robot_model" / "kinova_robotiq_config.yaml";
405+
406+
scene_ = std::make_shared<Scene>("test_scene", urdf_path_, srdf_path_, package_paths_,
407+
yaml_config_path_);
408+
409+
const auto& model = scene_->getModel();
410+
num_variables_ = model.nv;
411+
412+
// Set initial configuration at the neutral configuration
413+
Eigen::VectorXd q = pinocchio::neutral(model);
414+
scene_->setJointPositions(q);
415+
}
416+
417+
std::filesystem::path urdf_path_;
418+
std::filesystem::path srdf_path_;
419+
std::vector<std::filesystem::path> package_paths_;
420+
std::filesystem::path yaml_config_path_;
421+
std::shared_ptr<Scene> scene_;
422+
int num_variables_;
423+
};
424+
425+
// Test bounds at neutral configuration
426+
TEST_F(KinovaPositionLimitTest, BoundsAtNeutral) {
427+
const auto& model = scene_->getModel();
428+
429+
// Set configuration at the center of joint limits
430+
Eigen::VectorXd q = pinocchio::neutral(model);
431+
scene_->setJointPositions(q);
432+
433+
PositionLimit constraint(num_variables_, 1.0);
434+
435+
Eigen::MatrixXd constraint_matrix(num_variables_, num_variables_);
436+
Eigen::VectorXd lower_bounds(num_variables_);
437+
Eigen::VectorXd upper_bounds(num_variables_);
438+
439+
ASSERT_TRUE(
440+
constraint.computeQpConstraints(*scene_, constraint_matrix, lower_bounds, upper_bounds)
441+
.has_value());
442+
443+
for (int i = 0; i < num_variables_; ++i) {
444+
if (i == 0 || i == 2 || i == 4 || i == 6) {
445+
// Joints 1, 3, 5, and 7 are continuous so should have (virtually) infinite limits.
446+
ASSERT_LE(lower_bounds[i], -1.0e30);
447+
ASSERT_GE(upper_bounds[i], 1.0e30);
448+
} else {
449+
// Joints 2, 3, 4, and all the gripper joints are revolute,
450+
// so they should have finite limits.
451+
ASSERT_GE(lower_bounds[i], -3.0);
452+
ASSERT_LE(upper_bounds[i], 3.0);
453+
}
454+
}
455+
}
456+
396457
} // namespace roboplan

0 commit comments

Comments
 (0)