forked from RobotLocomotion/drake
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements AddMultibodyPlantConstraints()
This method creates a mapping from the constraints used in MultibodyPlant/SAP to the constraints used in MathematicalProgram. Towards RobotLocomotion#18917.
- Loading branch information
1 parent
7d74241
commit 669231e
Showing
11 changed files
with
286 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
multibody/inverse_kinematics/add_multibody_plant_constraints.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
#include "drake/multibody/inverse_kinematics/add_multibody_plant_constraints.h" | ||
|
||
#include <memory> | ||
|
||
#include "drake/multibody/inverse_kinematics/orientation_constraint.h" | ||
#include "drake/multibody/inverse_kinematics/point_to_point_distance_constraint.h" | ||
#include "drake/multibody/inverse_kinematics/position_constraint.h" | ||
#include "drake/multibody/inverse_kinematics/unit_quaternion_constraint.h" | ||
|
||
namespace drake { | ||
namespace multibody { | ||
|
||
using solvers::Binding; | ||
using solvers::Constraint; | ||
|
||
std::vector<Binding<Constraint>> AddMultibodyPlantConstraints( | ||
const MultibodyPlant<double>& plant, | ||
const solvers::VectorXDecisionVariable& q, | ||
solvers::MathematicalProgram* prog, | ||
systems::Context<double>* plant_context) { | ||
DRAKE_THROW_UNLESS(prog != nullptr); | ||
std::vector<Binding<Constraint>> bindings = | ||
AddUnitQuaternionConstraintOnPlant(plant, q, prog); | ||
int num_multibody_constraints = 0; | ||
for (const auto& [id, spec] : plant.get_coupler_constraint_specs()) { | ||
bindings.emplace_back(prog->AddLinearEqualityConstraint( | ||
q[spec.joint0_index] == | ||
spec.gear_ratio * q[spec.joint1_index] + spec.offset)); | ||
++num_multibody_constraints; | ||
} | ||
for (const auto& [id, spec] : plant.get_distance_constraint_specs()) { | ||
DRAKE_THROW_UNLESS(plant_context != nullptr); | ||
// d(q) == d₀. | ||
bindings.emplace_back(prog->AddConstraint( | ||
std::make_shared<PointToPointDistanceConstraint>( | ||
&plant, plant.get_body(spec.body_A).body_frame(), spec.p_AP, | ||
plant.get_body(spec.body_B).body_frame(), spec.p_BQ, spec.distance, | ||
spec.distance, plant_context), | ||
q)); | ||
++num_multibody_constraints; | ||
} | ||
for (const auto& [id, spec] : plant.get_ball_constraint_specs()) { | ||
DRAKE_THROW_UNLESS(plant_context != nullptr); | ||
bindings.emplace_back(prog->AddConstraint( | ||
std::make_shared<PositionConstraint>( | ||
&plant, plant.get_body(spec.body_A).body_frame(), spec.p_AP, | ||
spec.p_AP, plant.get_body(spec.body_B).body_frame(), *spec.p_BQ, | ||
plant_context), | ||
q)); | ||
++num_multibody_constraints; | ||
} | ||
for (const auto& [id, spec] : plant.get_weld_constraint_specs()) { | ||
DRAKE_THROW_UNLESS(plant_context != nullptr); | ||
// TODO(russt): Consider implementing a WeldConstraint. | ||
bindings.emplace_back(prog->AddConstraint( | ||
std::make_shared<PositionConstraint>( | ||
&plant, plant.get_body(spec.body_A).body_frame(), | ||
spec.X_AP.translation(), spec.X_AP.translation(), | ||
plant.get_body(spec.body_B).body_frame(), spec.X_BQ.translation(), | ||
plant_context), | ||
q)); | ||
bindings.emplace_back(prog->AddConstraint( | ||
std::make_shared<OrientationConstraint>( | ||
&plant, plant.get_body(spec.body_A).body_frame(), | ||
spec.X_AP.rotation(), plant.get_body(spec.body_B).body_frame(), | ||
spec.X_BQ.rotation(), 0.0, plant_context), | ||
q)); | ||
++num_multibody_constraints; | ||
} | ||
if (num_multibody_constraints != plant.num_constraints()) { | ||
// TODO(russt): It would be better to say something specific about the | ||
// unidentified constraint here, but the constraint API for MultibodyPlant | ||
// does not have that surface area (yet). | ||
log()->warn( | ||
"plant.num_constraints() = {}, but only AddMultibodyPlantConstraints() " | ||
"only captured {} of them. This method currently supports Coupler, " | ||
"Distance, Ball, and Weld multibody constraints. If you're seeing " | ||
"this, MultibodyPlant may have gained a new constraint type that is " | ||
"not supported here yet (but perhaps should be)", | ||
plant.num_constraints(), num_multibody_constraints); | ||
} | ||
return bindings; | ||
} | ||
|
||
} // namespace multibody | ||
} // namespace drake |
34 changes: 34 additions & 0 deletions
34
multibody/inverse_kinematics/add_multibody_plant_constraints.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#pragma once | ||
|
||
#include <vector> | ||
|
||
#include "drake/multibody/plant/multibody_plant.h" | ||
#include "drake/solvers/mathematical_program.h" | ||
|
||
namespace drake { | ||
namespace multibody { | ||
|
||
/** For unit quaternion and (holonomic) constraints registered with `plant` adds | ||
a corresponding solver::Constraint to `prog`, using decision variables `q` to | ||
represent the generalized positions of the plant. | ||
Adds constraints for coupler, distance, ball, and weld constraints. The | ||
distance constraint is implemented here as a hard kinematic constraint (i.e., | ||
d(q) == d₀), instead of a soft "spring" force. | ||
@see AddUnitQuaternionConstraintOnPlant() for the unit quaternion constraints. | ||
@pre plant.is_finalized() == true. | ||
@throws std::exception if `prog` is nullptr. | ||
@throws std::exception if `plant_context` is nullptr and one of the | ||
MultibodyPlant constraints requires it. (unit quaternion constraints and coupler | ||
constraints do not). | ||
*/ | ||
std::vector<solvers::Binding<solvers::Constraint>> AddMultibodyPlantConstraints( | ||
const MultibodyPlant<double>& plant, | ||
const solvers::VectorXDecisionVariable& q, | ||
solvers::MathematicalProgram* prog, | ||
systems::Context<double>* plant_context = nullptr); | ||
|
||
} // namespace multibody | ||
} // namespace drake |
114 changes: 114 additions & 0 deletions
114
multibody/inverse_kinematics/test/add_multibody_plant_constraints_test.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
#include "drake/multibody/inverse_kinematics/add_multibody_plant_constraints.h" | ||
|
||
#include <gtest/gtest.h> | ||
|
||
#include "drake/multibody/tree/revolute_joint.h" | ||
#include "drake/solvers/solve.h" | ||
|
||
namespace drake { | ||
namespace multibody { | ||
namespace { | ||
|
||
using Eigen::MatrixXd; | ||
using Eigen::Vector3d; | ||
using Eigen::VectorXd; | ||
using math::RigidTransformd; | ||
|
||
class AddMultibodyPlantConstraintsTest : public ::testing::Test { | ||
public: | ||
void SetUp() override { | ||
plant_.set_discrete_contact_approximation( | ||
DiscreteContactApproximation::kSap); | ||
plant_.SetUseSampledOutputPorts(false); | ||
|
||
body_A_ = &plant_.AddRigidBody( | ||
"body_A", SpatialInertia<double>::SolidBoxWithMass(1, 1, 1, 1)); | ||
body_B_ = &plant_.AddRigidBody( | ||
"body_B", SpatialInertia<double>::SolidBoxWithMass(1, 1, 1, 1)); | ||
world_A_ = &plant_.AddJoint<RevoluteJoint>( | ||
"world_A", plant_.world_body(), RigidTransformd(Vector3d(-1, 0, 0)), | ||
*body_A_, RigidTransformd(), Vector3d::UnitZ()); | ||
A_B_ = &plant_.AddJoint<RevoluteJoint>( | ||
"A_B", *body_A_, RigidTransformd(Vector3d(1, 0, 0)), *body_B_, | ||
RigidTransformd(), Vector3d::UnitZ()); | ||
} | ||
|
||
void CheckConstraints(int expected_num_constraints = 1, double tol = 1e-6) { | ||
if (!plant_.is_finalized()) { | ||
plant_.Finalize(); | ||
} | ||
plant_context_ = plant_.CreateDefaultContext(); | ||
|
||
// First demonstrate that these constraints work with MathematicalProgram, | ||
// and use them to find a valid initial condition. | ||
solvers::MathematicalProgram prog; | ||
auto q = prog.NewContinuousVariables(plant_.num_positions()); | ||
VectorXd q0 = VectorXd::LinSpaced(plant_.num_positions(), 0, 1); | ||
prog.AddQuadraticErrorCost( | ||
MatrixXd::Identity(plant_.num_positions(), plant_.num_positions()), q0, | ||
q); | ||
|
||
auto constraints = | ||
AddMultibodyPlantConstraints(plant_, q, &prog, plant_context_.get()); | ||
EXPECT_EQ(constraints.size(), expected_num_constraints); | ||
auto result = solvers::Solve(prog); | ||
EXPECT_TRUE(result.is_success()); | ||
|
||
// Now step the dynamics forward and verify that the constraint stays | ||
// satisfied. | ||
plant_.SetPositions(plant_context_.get(), result.GetSolution(q)); | ||
VectorXd qn = plant_.EvalUniquePeriodicDiscreteUpdate(*plant_context_) | ||
.value() | ||
.head(plant_.num_positions()); | ||
|
||
prog.SetInitialGuessForAllVariables(qn); | ||
EXPECT_TRUE(prog.CheckSatisfiedAtInitialGuess(constraints, tol)); | ||
|
||
result.set_x_val(qn); | ||
log()->info( | ||
"Infeasible constraints:\n{}", | ||
fmt::join(result.GetInfeasibleConstraintNames(prog, tol), "\n")); | ||
} | ||
|
||
protected: | ||
MultibodyPlant<double> plant_{0.1}; | ||
std::unique_ptr<systems::Context<double>> plant_context_{}; | ||
const RigidBody<double>* body_A_{nullptr}; | ||
const RigidBody<double>* body_B_{nullptr}; | ||
const RevoluteJoint<double>* world_A_{nullptr}; | ||
const RevoluteJoint<double>* A_B_{nullptr}; | ||
}; | ||
|
||
TEST_F(AddMultibodyPlantConstraintsTest, CouplerConstraint) { | ||
plant_.AddCouplerConstraint(*world_A_, *A_B_, 2.3); | ||
CheckConstraints(); | ||
|
||
// Confirm that I also could have called the method with plant_context == | ||
// nullptr. | ||
solvers::MathematicalProgram prog; | ||
auto q = prog.NewContinuousVariables(plant_.num_positions()); | ||
EXPECT_NO_THROW(AddMultibodyPlantConstraints(plant_, q, &prog)); | ||
} | ||
|
||
TEST_F(AddMultibodyPlantConstraintsTest, DistanceConstraint) { | ||
plant_.AddDistanceConstraint(*body_A_, Vector3d(0.0, 1.0, 0.0), *body_B_, | ||
Vector3d(0.0, 1.0, 0.0), 1.5); | ||
CheckConstraints(); | ||
} | ||
|
||
TEST_F(AddMultibodyPlantConstraintsTest, BallConstraint) { | ||
plant_.AddBallConstraint(*body_A_, Vector3d(0.0, 2.0, 0.0), *body_B_); | ||
CheckConstraints(); | ||
} | ||
|
||
TEST_F(AddMultibodyPlantConstraintsTest, WeldConstraint) { | ||
const RigidBody<double>& body_C = plant_.AddRigidBody( | ||
"body_C", SpatialInertia<double>::SolidBoxWithMass(1, 1, 1, 1)); | ||
plant_.AddWeldConstraint(*body_A_, RigidTransformd(Vector3d(1, 2, 3)), body_C, | ||
RigidTransformd(Vector3d(4, 5, 6))); | ||
CheckConstraints(/* expected_num_constraints = */ 3, /* tol = */ 1e-2); | ||
} | ||
|
||
} // namespace | ||
} // namespace multibody | ||
} // namespace drake |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters