Skip to content

Commit 806b9fa

Browse files
authored
Allow the user to specify the solver to be used by IrisNp2 for the counterexample search program. (#23101)
Also warn the user if the solver is failing frequently, and suggest using IpoptSolver in this case. Also change the convex configuration space test to use IPOPT instead of SNOPT for speed reasons.
1 parent f270249 commit 806b9fa

File tree

5 files changed

+67
-4
lines changed

5 files changed

+67
-4
lines changed

bindings/pydrake/planning/planning_py_iris_np2.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ void DefinePlanningIrisNp2(py::module m) {
3131
.format(self.sampled_iris_options);
3232
});
3333

34+
DefReadWriteKeepAlive(
35+
&iris_np2_options, "solver", &IrisNp2Options::solver, cls_doc.solver.doc);
36+
3437
// The `options` contains a `Parallelism`; we must release the GIL.
3538
m.def("IrisNp2", &IrisNp2, py::arg("checker"), py::arg("starting_ellipsoid"),
3639
py::arg("domain"), py::arg("options") = IrisNp2Options(),

bindings/pydrake/planning/test/iris_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
CollisionCheckerParams,
1212
IrisParameterizationFunction,
1313
)
14+
from pydrake.solvers import IpoptSolver
1415
from pydrake.symbolic import Variable
1516

1617
import numpy as np
@@ -181,6 +182,9 @@ def test_iris_np2(self):
181182
# Feature still TODO.
182183
options.sampled_iris_options.containment_points = None
183184

185+
# For speed reasons -- IPOPT seems to be faster than SNOPT here.
186+
options.solver = IpoptSolver()
187+
184188
domain = HPolyhedron.MakeBox(plant.GetPositionLowerLimits(),
185189
plant.GetPositionUpperLimits())
186190
starting_ellipsoid = Hyperellipsoid.MakeHypersphere(0.01,

planning/iris/iris_np2.cc

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ using geometry::optimization::internal::IrisConvexSetMaker;
3535
using geometry::optimization::internal::SamePointConstraint;
3636
using math::RigidTransform;
3737
using multibody::MultibodyPlant;
38+
using solvers::SolverInterface;
3839
using systems::Context;
3940

4041
namespace {
@@ -232,9 +233,15 @@ HPolyhedron IrisNp2(const SceneGraphCollisionChecker& checker,
232233
VectorXd closest(nq);
233234
RandomGenerator generator(options.sampled_iris_options.random_seed);
234235

235-
// TODO(cohnt): Allow the user to specify a solver.
236-
auto solver = solvers::MakeFirstAvailableSolver(
237-
{solvers::SnoptSolver::id(), solvers::IpoptSolver::id()});
236+
const SolverInterface* solver;
237+
std::unique_ptr<SolverInterface> default_solver =
238+
solvers::MakeFirstAvailableSolver(
239+
{solvers::SnoptSolver::id(), solvers::IpoptSolver::id()});
240+
if (options.solver) {
241+
solver = options.solver;
242+
} else {
243+
solver = default_solver.get();
244+
}
238245

239246
// For debugging visualization.
240247
Vector3d point_to_draw = Vector3d::Zero();
@@ -363,6 +370,9 @@ HPolyhedron IrisNp2(const SceneGraphCollisionChecker& checker,
363370
}
364371

365372
int num_hyperplanes_added = 0;
373+
int num_prog_failures = 0;
374+
int num_prog_successes = 0;
375+
constexpr double kSolverFailRateWarning = 0.1;
366376

367377
for (const auto& particle : particles_in_collision) {
368378
if (num_hyperplanes_added >
@@ -409,7 +419,9 @@ HPolyhedron IrisNp2(const SceneGraphCollisionChecker& checker,
409419
}
410420

411421
// TODO(cohnt): Allow the user to specify the solver options used here.
412-
if (prog.Solve(*solver, particle, {}, &closest)) {
422+
bool solve_succeeded = prog.Solve(*solver, particle, {}, &closest);
423+
if (solve_succeeded) {
424+
++num_prog_successes;
413425
if (do_debugging_visualization) {
414426
point_to_draw.head(nq) = closest;
415427
std::string path = fmt::format("iteration{:02}/{:03}/found",
@@ -436,6 +448,7 @@ HPolyhedron IrisNp2(const SceneGraphCollisionChecker& checker,
436448
prog.UpdatePolytope(A.topRows(num_constraints),
437449
b.head(num_constraints));
438450
} else {
451+
++num_prog_failures;
439452
if (do_debugging_visualization) {
440453
point_to_draw.head(nq) = closest;
441454
std::string path = fmt::format("iteration{:02}/{:03}/closest",
@@ -448,6 +461,18 @@ HPolyhedron IrisNp2(const SceneGraphCollisionChecker& checker,
448461
}
449462
}
450463

464+
const double failure_rate =
465+
static_cast<double>(num_prog_failures) /
466+
static_cast<double>(num_prog_failures + num_prog_successes);
467+
if (failure_rate >= kSolverFailRateWarning) {
468+
log()->warn(fmt::format(
469+
"IrisNp2 WARNING, only {} out of {} closest collision "
470+
"programs solved successfully ({}% failure rate). If you are using "
471+
"SnoptSolver or NloptSolver, consider using IpoptSolver instead.",
472+
num_prog_successes, num_prog_successes + num_prog_failures,
473+
failure_rate));
474+
}
475+
451476
++num_iterations_separating_planes;
452477
}
453478

planning/iris/iris_np2.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "drake/multibody/plant/multibody_plant.h"
66
#include "drake/planning/iris/iris_common.h"
77
#include "drake/planning/scene_graph_collision_checker.h"
8+
#include "drake/solvers/solve.h"
89

910
namespace drake {
1011
namespace planning {
@@ -28,6 +29,11 @@ class IrisNp2Options {
2829

2930
IrisNp2Options() = default;
3031

32+
/** The user can specify a solver to use for the counterexample search
33+
* program. If nullptr (the default value) is given, then
34+
* solvers::MakeFirstAvailableSolver will be used to pick the solver. */
35+
const solvers::SolverInterface* solver{nullptr};
36+
3137
/** Options common to IRIS-type algorithms. */
3238
CommonSampledIrisOptions sampled_iris_options{};
3339

planning/iris/test/iris_np2_test.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
#include "drake/geometry/optimization/hpolyhedron.h"
77
#include "drake/planning/iris/iris_common.h"
88
#include "drake/planning/iris/test/iris_test_utilities.h"
9+
#include "drake/solvers/equality_constrained_qp_solver.h"
10+
#include "drake/solvers/ipopt_solver.h"
11+
#include "drake/solvers/nlopt_solver.h"
912

1013
namespace drake {
1114
namespace planning {
@@ -126,6 +129,22 @@ TEST_F(DoublePendulum, FilterCollisions) {
126129
PlotEnvironmentAndRegion(region);
127130
}
128131

132+
// Verify we can specify the solver for the counterexample search by
133+
// deliberately specifying a solver that can't solve problems of that type, and
134+
// catch the error message.
135+
TEST_F(DoublePendulum, SpecifySolver) {
136+
IrisNp2Options options;
137+
auto sgcc_ptr = dynamic_cast<SceneGraphCollisionChecker*>(checker_.get());
138+
ASSERT_TRUE(sgcc_ptr != nullptr);
139+
140+
solvers::EqualityConstrainedQPSolver invalid_solver;
141+
options.solver = &invalid_solver;
142+
143+
DRAKE_EXPECT_THROWS_MESSAGE(
144+
IrisNp2(*sgcc_ptr, starting_ellipsoid_, domain_, options),
145+
".*EqualityConstrainedQPSolver is unable to solve.*");
146+
}
147+
129148
TEST_F(BlockOnGround, IrisNp2Test) {
130149
IrisNp2Options options;
131150
auto sgcc_ptr = dynamic_cast<SceneGraphCollisionChecker*>(checker_.get());
@@ -156,6 +175,12 @@ TEST_F(ConvexConfigurationSpace, IrisNp2Test) {
156175
meshcat_->Delete();
157176
options.sampled_iris_options.meshcat = meshcat_;
158177
options.sampled_iris_options.verbose = true;
178+
179+
// We use IPOPT for this test since SNOPT has a large number of solve failures
180+
// in this environment.
181+
solvers::IpoptSolver solver;
182+
options.solver = &solver;
183+
159184
HPolyhedron region =
160185
IrisNp2(*sgcc_ptr, starting_ellipsoid_, domain_, options);
161186
CheckRegion(region);

0 commit comments

Comments
 (0)