#include "drake/solvers/ipopt_solver.h" #include #include #include "drake/common/temp_directory.h" #include "drake/solvers/mathematical_program.h" #include "drake/solvers/test/linear_program_examples.h" #include "drake/solvers/test/mathematical_program_test_util.h" #include "drake/solvers/test/quadratic_program_examples.h" #include "drake/solvers/test/second_order_cone_program_examples.h" #ifdef DRAKE_IPOPT_SOLVER_TEST_HAS_IPOPT #include namespace { constexpr int kIpoptMaxiterExceeded = Ipopt::MAXITER_EXCEEDED; constexpr int kIpoptStopAtAcceptablePoint = Ipopt::STOP_AT_ACCEPTABLE_POINT; constexpr int kIpoptLocalInfeasibility = Ipopt::LOCAL_INFEASIBILITY; } // namespace #else namespace { constexpr int kIpoptMaxiterExceeded = -1; constexpr int kIpoptStopAtAcceptablePoint = -1; constexpr int kIpoptLocalInfeasibility = -1; } // namespace #endif namespace drake { namespace solvers { namespace test { TEST_P(LinearProgramTest, TestLP) { IpoptSolver solver; prob()->RunProblem(&solver); } INSTANTIATE_TEST_SUITE_P( IpoptTest, LinearProgramTest, ::testing::Combine(::testing::ValuesIn(linear_cost_form()), ::testing::ValuesIn(linear_constraint_form()), ::testing::ValuesIn(linear_problems()))); TEST_F(InfeasibleLinearProgramTest0, TestIpopt) { prog_->SetInitialGuessForAllVariables(Eigen::Vector2d(1, 2)); IpoptSolver solver; if (solver.available()) { auto result = solver.Solve(*prog_, {}, {}); EXPECT_FALSE(result.is_success()); EXPECT_EQ(result.get_solution_result(), SolutionResult::kInfeasibleConstraints); EXPECT_EQ(result.get_solver_details().status, kIpoptLocalInfeasibility); const Eigen::Vector2d x_val = result.GetSolution(prog_->decision_variables()); EXPECT_NEAR(result.get_optimal_cost(), -x_val(0) - x_val(1), 1E-7); } } TEST_F(UnboundedLinearProgramTest0, TestIpopt) { prog_->SetInitialGuessForAllVariables(Eigen::Vector2d(1, 2)); prog_->SetSolverOption(IpoptSolver::id(), "diverging_iterates_tol", 1E3); prog_->SetSolverOption(IpoptSolver::id(), "max_iter", 1000); IpoptSolver solver; if (solver.available()) { auto result = solver.Solve(*prog_, {}, {}); EXPECT_EQ(result.get_solution_result(), SolutionResult::kUnbounded); EXPECT_EQ(result.get_optimal_cost(), -std::numeric_limits::infinity()); } } TEST_F(DuplicatedVariableLinearProgramTest1, Test) { IpoptSolver solver; if (solver.available()) { CheckSolution(solver); } } TEST_P(QuadraticProgramTest, TestQP) { IpoptSolver solver; prob()->RunProblem(&solver); } INSTANTIATE_TEST_SUITE_P( IpoptTest, QuadraticProgramTest, ::testing::Combine(::testing::ValuesIn(quadratic_cost_form()), ::testing::ValuesIn(linear_constraint_form()), ::testing::ValuesIn(quadratic_problems()))); GTEST_TEST(QPtest, TestUnitBallExample) { IpoptSolver solver; if (solver.available()) { TestQPonUnitBallExample(solver); } } class NoisyQuadraticCost { public: explicit NoisyQuadraticCost(const double max_noise) : max_noise_(max_noise) {} int numInputs() const { return 1; } int numOutputs() const { return 1; } template void eval(internal::VecIn const& x, internal::VecOut* y) const { // Parabola with minimum at (-1, 1) with some deterministic noise applied to // the input so derivatives will be correctish but not easily followable to // the minimum. // The sign of the noise alternates between calls. The magnitude of the // noise increases from 0 to max_noise_ over the course of // 2 * noise_counter_limit_ calls, after which it resets to 0. double noise = std::pow(-1., noise_counter_) * max_noise_ * noise_counter_ / noise_counter_limit_; if (noise_counter_ >= 0) { noise_counter_ = (noise_counter_ + 1) % noise_counter_limit_; noise_counter_ *= -1; } else { noise_counter_ *= -1; } auto noisy_x = x(0) + noise; y->resize(1); (*y)(0) = (noisy_x + 1) * (noisy_x + 1) + 1; } private: double max_noise_{}; mutable int noise_counter_{}; const int noise_counter_limit_{10}; }; GTEST_TEST(IpoptSolverTest, AcceptableResult) { IpoptSolver solver; SolverOptions options; options.SetOption(IpoptSolver::id(), "tol", 1e-6); options.SetOption(IpoptSolver::id(), "dual_inf_tol", 1e-6); options.SetOption(IpoptSolver::id(), "max_iter", 10); const VectorX x_initial_guess = VectorX::Ones(1); if (solver.available()) { double max_noise = 1e-2; { // Set up a program and give it a relatively large amount of noise for // the specified tolerance. MathematicalProgram prog; auto x = prog.NewContinuousVariables(1); prog.AddCost(NoisyQuadraticCost(max_noise), x); auto result = solver.Solve(prog, x_initial_guess, options); // Expect to hit iteration limit EXPECT_FALSE(result.is_success()); EXPECT_EQ(result.get_solution_result(), SolutionResult::kIterationLimit); EXPECT_EQ(result.get_solver_details().status, kIpoptMaxiterExceeded); } options.SetOption(IpoptSolver::id(), "acceptable_tol", 1e-3); options.SetOption(IpoptSolver::id(), "acceptable_dual_inf_tol", 1e-3); options.SetOption(IpoptSolver::id(), "acceptable_iter", 3); { // Set up the same program, but provide acceptability criteria that // should be feasible with even with the noise. MathematicalProgram prog; auto x = prog.NewContinuousVariables(1); prog.AddCost(NoisyQuadraticCost(max_noise), x); auto result = solver.Solve(prog, x_initial_guess, options); EXPECT_EQ(result.get_solver_details().status, kIpoptStopAtAcceptablePoint); // Expect Ipopt's "STOP_AT_ACCEPTABLE_POINT" to be translated to success. EXPECT_TRUE(result.is_success()); } } } GTEST_TEST(IpoptSolverTest, QPDualSolution1) { IpoptSolver solver; TestQPDualSolution1(solver, {} /* solver_options */, 1e-5); } GTEST_TEST(IpoptSolverTest, QPDualSolution2) { IpoptSolver solver; TestQPDualSolution2(solver); } GTEST_TEST(IpoptSolverTest, QPDualSolution3) { IpoptSolver solver; TestQPDualSolution3(solver); } GTEST_TEST(IpoptSolverTest, EqualityConstrainedQPDualSolution1) { IpoptSolver solver; TestEqualityConstrainedQPDualSolution1(solver); } GTEST_TEST(IpoptSolverTest, EqualityConstrainedQPDualSolution2) { IpoptSolver solver; TestEqualityConstrainedQPDualSolution2(solver); } GTEST_TEST(IpoptSolverTest, LPDualSolution1) { IpoptSolver solver; TestLPDualSolution1(solver); } GTEST_TEST(IpoptSolverTest, LPDualSolution2) { IpoptSolver solver; TestLPDualSolution2(solver); } GTEST_TEST(IpoptSolverTest, EckhardtDualSolution) { IpoptSolver solver; TestEckhardtDualSolution(solver, Eigen::Vector3d(1., 1., 5.)); } GTEST_TEST(IpoptSolverTest, TestNonconvexQP) { IpoptSolver solver; if (solver.available()) { TestNonconvexQP(solver, false); } } GTEST_TEST(IpoptSolverTest, TestL2NormCost) { IpoptSolver solver; TestL2NormCost(solver, 1e-6); } /* Tests the solver's processing of the verbosity options. With multiple ways to request verbosity (common options and solver-specific options), we simply apply a smoke test that none of the means causes runtime errors. Note, we don't test the case where we configure the mathematical program itself; that is resolved in SolverBase. We only need to test the options passed into Solve(). The possible configurations are: - No verbosity set at all (this is implicitly tested in all other tests). - Common option explicitly set (on & off) - Solver option explicitly set (on & off) - Both options explicitly set (with all permutations of (on, on), etc.) */ GTEST_TEST(IpoptSolverTest, SolverOptionsVerbosity) { MathematicalProgram prog; auto x = prog.NewContinuousVariables(1); prog.AddLinearConstraint(x(0) <= 3); prog.AddLinearConstraint(x(0) >= -3); prog.AddLinearCost(x(0)); IpoptSolver solver; if (solver.is_available()) { // Setting common options. for (int print_to_console : {0, 1}) { SolverOptions options; options.SetOption(CommonSolverOption::kPrintToConsole, print_to_console); solver.Solve(prog, {}, options); } // Setting solver options. for (int print_to_console : {0, 2}) { SolverOptions options; options.SetOption(IpoptSolver::id(), "print_level", print_to_console); solver.Solve(prog, {}, options); } // Setting both. for (int common_print_to_console : {0, 1}) { for (int solver_print_to_console : {0, 2}) { SolverOptions options; options.SetOption(CommonSolverOption::kPrintToConsole, common_print_to_console); options.SetOption(IpoptSolver::id(), "print_level", solver_print_to_console); solver.Solve(prog, {}, options); } } } } // This is to verify we can set the print out file through CommonSolverOption. GTEST_TEST(IpoptSolverTest, PrintToFile) { MathematicalProgram prog; auto x = prog.NewContinuousVariables(1); prog.AddLinearConstraint(x(0) <= 3); prog.AddLinearConstraint(x(0) >= -3); prog.AddLinearCost(x(0)); const std::string filename = temp_directory() + "/ipopt.log"; EXPECT_FALSE(std::filesystem::exists({filename})); SolverOptions solver_options; solver_options.SetOption(CommonSolverOption::kPrintFileName, filename); IpoptSolver solver; if (solver.is_available()) { const auto result = solver.Solve(prog, {}, solver_options); EXPECT_TRUE(result.is_success()); EXPECT_TRUE(std::filesystem::exists({filename})); } } TEST_P(TestEllipsoidsSeparation, TestSOCP) { IpoptSolver ipopt_solver; if (ipopt_solver.available()) { SolveAndCheckSolution(ipopt_solver, {}, 1.E-8); } } INSTANTIATE_TEST_SUITE_P( IpoptSolverTest, TestEllipsoidsSeparation, ::testing::ValuesIn({EllipsoidsSeparationProblem::kProblem0, EllipsoidsSeparationProblem::kProblem1, EllipsoidsSeparationProblem::kProblem3})); TEST_P(TestQPasSOCP, TestSOCP) { IpoptSolver ipopt_solver; if (ipopt_solver.available()) { SolveAndCheckSolution(ipopt_solver); } } INSTANTIATE_TEST_SUITE_P(IpoptSolverTest, TestQPasSOCP, ::testing::ValuesIn(GetQPasSOCPProblems())); TEST_P(TestFindSpringEquilibrium, TestSOCP) { IpoptSolver ipopt_solver; if (ipopt_solver.available()) { SolveAndCheckSolution(ipopt_solver, {}, 2E-3); } } INSTANTIATE_TEST_SUITE_P( IpoptSolverTest, TestFindSpringEquilibrium, ::testing::ValuesIn(GetFindSpringEquilibriumProblems())); GTEST_TEST(TestSOCP, MaximizeGeometricMeanTrivialProblem1) { MaximizeGeometricMeanTrivialProblem1 prob; IpoptSolver solver; if (solver.available()) { const auto result = solver.Solve(prob.prog(), {}, {}); prob.CheckSolution(result, 4E-6); } } GTEST_TEST(TestSOCP, MaximizeGeometricMeanTrivialProblem2) { MaximizeGeometricMeanTrivialProblem2 prob; IpoptSolver solver; if (solver.available()) { const auto result = solver.Solve(prob.prog(), {}, {}); prob.CheckSolution(result, 1.E-6); } } GTEST_TEST(TestSOCP, SmallestEllipsoidCoveringProblem) { IpoptSolver solver; SolveAndCheckSmallestEllipsoidCoveringProblems(solver, {}, 1E-6); } GTEST_TEST(TestLP, PoorScaling) { IpoptSolver solver; TestLPPoorScaling1(solver, true, 1E-6); TestLPPoorScaling2(solver, true, 1E-4); } TEST_F(QuadraticEqualityConstrainedProgram1, test) { IpoptSolver solver; if (solver.available()) { CheckSolution(solver, Eigen::Vector2d(0.5, 0.8), std::nullopt, 1E-6); } } } // namespace test } // namespace solvers } // namespace drake