diff --git a/solvers/ipopt_solver.cc b/solvers/ipopt_solver.cc index 0e55249efc7d..297c8387d611 100644 --- a/solvers/ipopt_solver.cc +++ b/solvers/ipopt_solver.cc @@ -57,7 +57,9 @@ void SetAppOptions(const std::string& default_linear_solver, // Turn off the banner. set_string_option("sb", "yes"); - set_string_option("linear_solver", default_linear_solver); + if (!default_linear_solver.empty()) { + set_string_option("linear_solver", default_linear_solver); + } // The default tolerance. const double tol = 1.05e-10; // Note: SNOPT is only 1e-6, but in #3712 we @@ -147,6 +149,16 @@ const char* IpoptSolverDetails::ConvertStatusToString() const { return "Unknown enumerated SolverReturn value."; } +IpoptSolver::IpoptSolver() + : SolverBase(id(), &is_available, &is_enabled, + &ProgramAttributesSatisfied) { + const std::vector linear_solvers = + internal::GetSupportedIpoptLinearSolvers(); + if (!linear_solvers.empty()) { + default_linear_solver_ = linear_solvers.at(0); + } +} + bool IpoptSolver::is_available() { return true; } diff --git a/solvers/ipopt_solver_common.cc b/solvers/ipopt_solver_common.cc index 00edcdea2a33..d30af959ff86 100644 --- a/solvers/ipopt_solver_common.cc +++ b/solvers/ipopt_solver_common.cc @@ -8,17 +8,6 @@ namespace drake { namespace solvers { -IpoptSolver::IpoptSolver() - : SolverBase(id(), &is_available, &is_enabled, &ProgramAttributesSatisfied), - // The default linear solver is MA27, but it is not freely redistributable - // so we cannot use it. MUMPS is the only compatible linear solver - // guaranteed to be available on both macOS and Ubuntu. In versions of - // IPOPT prior to 3.13, it would correctly determine that MUMPS was the - // only available solver, but its behavior changed to instead error having - // unsuccessfully tried to dlopen a nonexistent hsl library that would - // contain MA27. - default_linear_solver_("mumps") {} - IpoptSolver::~IpoptSolver() = default; void IpoptSolver::SetDefaultLinearSolver(std::string linear_solver) { diff --git a/solvers/ipopt_solver_internal.cc b/solvers/ipopt_solver_internal.cc index 055a68b6714f..06f555540b4e 100644 --- a/solvers/ipopt_solver_internal.cc +++ b/solvers/ipopt_solver_internal.cc @@ -4,6 +4,8 @@ #include #include +#include + #include "drake/common/text_logging.h" using Ipopt::Index; @@ -761,6 +763,27 @@ void IpoptSolver_NLP::EvaluateConstraints(Index n, const Number* x, constraint_cache_->grad_valid = true; } } + +std::vector GetSupportedIpoptLinearSolvers() { + std::vector result; + // IPOPT's upstream default linear solver is MA27, but it is not freely + // redistributable so Drake cannot use it. In Drake's Ubuntu and macOS build + // recipes for IPOPT, one or both of MUMPS or SPRAL linear solvers will be + // available. We'll ask IPOPT which of those two linear solver(s) have been + // built into Drake. + const IpoptLinearSolver solver_mask = + IpoptGetAvailableLinearSolvers(/* buildinonly = */ 1); + // The first item in the result will be Drake's default linear solver. + // We'll prefer MUMPS because it has been our traditional choice. + if (solver_mask & IPOPTLINEARSOLVER_MUMPS) { + result.emplace_back("mumps"); + } + if (solver_mask & IPOPTLINEARSOLVER_SPRAL) { + result.emplace_back("spral"); + } + return result; +} + } // namespace internal } // namespace solvers } // namespace drake diff --git a/solvers/ipopt_solver_internal.h b/solvers/ipopt_solver_internal.h index 248616921275..c5facf4c009e 100644 --- a/solvers/ipopt_solver_internal.h +++ b/solvers/ipopt_solver_internal.h @@ -4,6 +4,7 @@ // that we can expose the internals to ipopt_solver_internal_test. #include +#include #include #include #include @@ -143,6 +144,12 @@ class DRAKE_NO_EXPORT IpoptSolver_NLP : public Ipopt::TNLP { // corresponding dual variables. std::unordered_map, int> constraint_dual_start_index_; }; + +// Returns Drake's supported values for the "linear_solver" IpoptSolver option. +// This will be affected by which options the IPOPT library was compiled with. +// The first item in the result is Drake's default value. +std::vector GetSupportedIpoptLinearSolvers(); + } // namespace internal } // namespace solvers } // namespace drake diff --git a/solvers/no_ipopt.cc b/solvers/no_ipopt.cc index a355b241a4a8..f6337d68917b 100644 --- a/solvers/no_ipopt.cc +++ b/solvers/no_ipopt.cc @@ -13,6 +13,10 @@ const char* IpoptSolverDetails::ConvertStatusToString() const { "solver."); } +IpoptSolver::IpoptSolver() + : SolverBase(id(), &is_available, &is_enabled, + &ProgramAttributesSatisfied) {} + bool IpoptSolver::is_available() { return false; } diff --git a/solvers/test/ipopt_solver_internal_test.cc b/solvers/test/ipopt_solver_internal_test.cc index 9d6cc0f00b94..5459b21a4088 100644 --- a/solvers/test/ipopt_solver_internal_test.cc +++ b/solvers/test/ipopt_solver_internal_test.cc @@ -270,6 +270,27 @@ GTEST_TEST(TestIpoptSolverNlp, NonlinearConstraintDuplicatedVariables) { EXPECT_TRUE(CompareMatrices(jac.toDense(), jac_expected)); } } + +#if defined(__APPLE__) +constexpr bool kApple = true; +#else +constexpr bool kApple = false; +#endif + +// This provides a cross-check of our build system choices for @ipopt. +GTEST_TEST(TestIpoptSolver, SupportedLinearSolvers) { + const std::vector actual = GetSupportedIpoptLinearSolvers(); + std::vector expected; + if (kApple) { + // Homebrew doesn't provide SPRAL. + expected.emplace_back("mumps"); + } else { + expected.emplace_back("mumps"); + expected.emplace_back("spral"); + } + EXPECT_EQ(actual, expected); +} + } // namespace internal } // namespace solvers } // namespace drake