You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Conception/drake-master/solvers/test/ipopt_solver_test.cc

368 lines
12 KiB

#include "drake/solvers/ipopt_solver.h"
#include <filesystem>
#include <gtest/gtest.h>
#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 <IpAlgTypes.hpp>
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<IpoptSolver>().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<double>::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 <typename T>
void eval(internal::VecIn<T> const& x, internal::VecOut<T>* 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<double> x_initial_guess = VectorX<double>::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<IpoptSolver>().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<IpoptSolver>().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