forked from pz4kybsvg/Conception
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.
658 lines
22 KiB
658 lines
22 KiB
#include "drake/solvers/mosek_solver.h"
|
|
|
|
#include <filesystem>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "drake/common/temp_directory.h"
|
|
#include "drake/common/test_utilities/expect_throws_message.h"
|
|
#include "drake/solvers/mathematical_program.h"
|
|
#include "drake/solvers/mixed_integer_optimization_util.h"
|
|
#include "drake/solvers/test/exponential_cone_program_examples.h"
|
|
#include "drake/solvers/test/linear_program_examples.h"
|
|
#include "drake/solvers/test/quadratic_program_examples.h"
|
|
#include "drake/solvers/test/second_order_cone_program_examples.h"
|
|
#include "drake/solvers/test/semidefinite_program_examples.h"
|
|
#include "drake/solvers/test/sos_examples.h"
|
|
|
|
namespace drake {
|
|
namespace solvers {
|
|
namespace test {
|
|
const double kInf = std::numeric_limits<double>::infinity();
|
|
TEST_P(LinearProgramTest, TestLP) {
|
|
MosekSolver solver;
|
|
prob()->RunProblem(&solver);
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
MosekTest, LinearProgramTest,
|
|
::testing::Combine(::testing::ValuesIn(linear_cost_form()),
|
|
::testing::ValuesIn(linear_constraint_form()),
|
|
::testing::ValuesIn(linear_problems())));
|
|
|
|
TEST_F(UnboundedLinearProgramTest0, Test) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const MathematicalProgram& const_prog = *prog_;
|
|
MathematicalProgramResult result;
|
|
solver.Solve(const_prog, {}, {}, &result);
|
|
// Mosek can only detect dual infeasibility, not primal unboundedness.
|
|
EXPECT_FALSE(result.is_success());
|
|
EXPECT_EQ(result.get_solution_result(), SolutionResult::kDualInfeasible);
|
|
const MosekSolverDetails& mosek_solver_details =
|
|
result.get_solver_details<MosekSolver>();
|
|
EXPECT_EQ(mosek_solver_details.rescode, 0);
|
|
// This problem status is defined in
|
|
// https://docs.mosek.com/10.0/capi/constants.html#mosek.prosta
|
|
const int MSK_SOL_STA_DUAL_INFEAS_CER = 6;
|
|
EXPECT_EQ(mosek_solver_details.solution_status,
|
|
MSK_SOL_STA_DUAL_INFEAS_CER);
|
|
const auto x_sol = result.GetSolution(x_);
|
|
EXPECT_FALSE(std::isnan(x_sol(0)));
|
|
EXPECT_FALSE(std::isnan(x_sol(1)));
|
|
}
|
|
}
|
|
|
|
TEST_F(UnboundedLinearProgramTest1, Test) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
MathematicalProgramResult result;
|
|
solver.Solve(*prog_, {}, {}, &result);
|
|
// Mosek can only detect dual infeasibility, not primal unboundedness.
|
|
EXPECT_EQ(result.get_solution_result(), SolutionResult::kDualInfeasible);
|
|
}
|
|
}
|
|
|
|
TEST_F(DuplicatedVariableLinearProgramTest1, Test) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
CheckSolution(solver);
|
|
}
|
|
}
|
|
|
|
TEST_P(QuadraticProgramTest, TestQP) {
|
|
MosekSolver solver;
|
|
prob()->RunProblem(&solver);
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
MosekTest, QuadraticProgramTest,
|
|
::testing::Combine(::testing::ValuesIn(quadratic_cost_form()),
|
|
::testing::ValuesIn(linear_constraint_form()),
|
|
::testing::ValuesIn(quadratic_problems())));
|
|
|
|
GTEST_TEST(QPtest, TestUnitBallExample) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestQPonUnitBallExample(solver);
|
|
}
|
|
}
|
|
|
|
TEST_P(TestEllipsoidsSeparation, TestSOCP) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
SolveAndCheckSolution(mosek_solver);
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
MosekTest, TestEllipsoidsSeparation,
|
|
::testing::ValuesIn(GetEllipsoidsSeparationProblems()));
|
|
|
|
GTEST_TEST(TestDuplicatedVariableQuadraticProgram, Test) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestDuplicatedVariableQuadraticProgram(solver);
|
|
}
|
|
}
|
|
|
|
TEST_P(TestQPasSOCP, TestSOCP) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
SolveAndCheckSolution(mosek_solver);
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(MosekTest, TestQPasSOCP,
|
|
::testing::ValuesIn(GetQPasSOCPProblems()));
|
|
|
|
TEST_P(TestFindSpringEquilibrium, TestSOCP) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
SolveAndCheckSolution(mosek_solver);
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
MosekTest, TestFindSpringEquilibrium,
|
|
::testing::ValuesIn(GetFindSpringEquilibriumProblems()));
|
|
|
|
GTEST_TEST(TestSOCP, MaximizeGeometricMeanTrivialProblem1) {
|
|
MaximizeGeometricMeanTrivialProblem1 prob;
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const auto result = solver.Solve(prob.prog(), {}, {});
|
|
prob.CheckSolution(result, 1E-7);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestSOCP, MaximizeGeometricMeanTrivialProblem2) {
|
|
MaximizeGeometricMeanTrivialProblem2 prob;
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const auto result = solver.Solve(prob.prog(), {}, {});
|
|
prob.CheckSolution(result, 1E-7);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestSOCP, SmallestEllipsoidCoveringProblem) {
|
|
MosekSolver solver;
|
|
// Mosek 10 returns a solution that is accurate up to 1.3E-5 for this specific
|
|
// problem. Might need to change the tolerance when we upgrade Mosek.
|
|
SolveAndCheckSmallestEllipsoidCoveringProblems(solver, {}, 1.3E-5);
|
|
}
|
|
|
|
GTEST_TEST(TestSOCP, TestSocpDuplicatedVariable1) {
|
|
MosekSolver solver;
|
|
TestSocpDuplicatedVariable1(solver, std::nullopt, 1E-6);
|
|
}
|
|
|
|
GTEST_TEST(TestSOCP, TestSocpDuplicatedVariable2) {
|
|
MosekSolver solver;
|
|
TestSocpDuplicatedVariable2(solver, std::nullopt, 1E-6);
|
|
}
|
|
|
|
GTEST_TEST(TestSemidefiniteProgram, TrivialSDP) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
TestTrivialSDP(mosek_solver, 1E-8);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestSemidefiniteProgram, CommonLyapunov) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
FindCommonLyapunov(mosek_solver, {}, 1E-8);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestSemidefiniteProgram, OuterEllipsoid) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
FindOuterEllipsoid(mosek_solver, {}, 1E-6);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestSemidefiniteProgram, EigenvalueProblem) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
SolveEigenvalueProblem(mosek_solver, {}, 1E-7);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestSemidefiniteProgram, SolveSDPwithSecondOrderConeExample1) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
SolveSDPwithSecondOrderConeExample1(mosek_solver, 1E-7);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestSemidefiniteProgram, SolveSDPwithSecondOrderConeExample2) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
SolveSDPwithSecondOrderConeExample2(mosek_solver, 1E-7);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestSemidefiniteProgram, SolveSDPwithOverlappingVariables) {
|
|
MosekSolver mosek_solver;
|
|
if (mosek_solver.available()) {
|
|
SolveSDPwithOverlappingVariables(mosek_solver, 1E-7);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestExponentialConeProgram, ExponentialConeTrivialExample) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
ExponentialConeTrivialExample(solver, 1E-5, true);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestExponentialConeProgram, MinimizeKLDivengence) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
MinimizeKLDivergence(solver, 2E-5);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(TestExponentialConeProgram, MinimalEllipsoidConveringPoints) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
MinimalEllipsoidCoveringPoints(solver, 1E-6);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, TestLogging) {
|
|
// Test if we can print the logging info to a log file.
|
|
MathematicalProgram prog;
|
|
const auto x = prog.NewContinuousVariables<2>();
|
|
prog.AddLinearConstraint(x(0) + x(1) == 1);
|
|
|
|
const std::string log_file = temp_directory() + "/mosek_logging.log";
|
|
EXPECT_FALSE(std::filesystem::exists({log_file}));
|
|
MosekSolver solver;
|
|
MathematicalProgramResult result;
|
|
solver.Solve(prog, {}, {}, &result);
|
|
// By default, no logging file.
|
|
EXPECT_FALSE(std::filesystem::exists({log_file}));
|
|
// Print to console. We can only test this doesn't cause any runtime error. We
|
|
// can't test if the logging message is actually printed to the console.
|
|
SolverOptions solver_options;
|
|
solver_options.SetOption(CommonSolverOption::kPrintToConsole, 1);
|
|
solver.Solve(prog, {}, solver_options, &result);
|
|
solver_options.SetOption(CommonSolverOption::kPrintToConsole, 0);
|
|
// Output the logging to the console
|
|
solver_options.SetOption(CommonSolverOption::kPrintFileName, log_file);
|
|
solver.Solve(prog, {}, solver_options, &result);
|
|
EXPECT_TRUE(std::filesystem::exists({log_file}));
|
|
// Now set both print to console and the log file. This will cause an error.
|
|
solver_options.SetOption(CommonSolverOption::kPrintToConsole, 1);
|
|
DRAKE_EXPECT_THROWS_MESSAGE(
|
|
solver.Solve(prog, {}, solver_options, &result),
|
|
".* cannot print to both the console and the log file.");
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, SolverOptionsTest) {
|
|
// We test that passing solver options change the behavior of
|
|
// MosekSolver::Solve().
|
|
MathematicalProgram prog;
|
|
auto x = prog.NewContinuousVariables<2>();
|
|
prog.AddLinearConstraint(100 * x(0) + 100 * x(1) <= 1);
|
|
prog.AddConstraint(x(0) >= 0);
|
|
prog.AddConstraint(x(1) >= 0);
|
|
prog.AddLinearCost(1E5 * x(0) + x(1));
|
|
|
|
SolverOptions solver_options;
|
|
solver_options.SetOption(MosekSolver::id(), "MSK_DPAR_DATA_TOL_C_HUGE", 1E3);
|
|
MathematicalProgramResult result;
|
|
MosekSolver mosek_solver;
|
|
mosek_solver.Solve(prog, {}, solver_options, &result);
|
|
EXPECT_FALSE(result.is_success());
|
|
// This response code is defined in
|
|
// https://docs.mosek.com/10.0/capi/response-codes.html#mosek.rescode
|
|
const int MSK_RES_ERR_HUGE_C{1375};
|
|
EXPECT_EQ(result.get_solver_details<MosekSolver>().rescode,
|
|
MSK_RES_ERR_HUGE_C);
|
|
solver_options.SetOption(MosekSolver::id(), "MSK_DPAR_DATA_TOL_C_HUGE", 1E6);
|
|
mosek_solver.Solve(prog, {}, solver_options, &result);
|
|
EXPECT_TRUE(result.is_success());
|
|
}
|
|
|
|
GTEST_TEST(MosekSolver, SolverOptionsErrorTest) {
|
|
// Set a non-existing option. Mosek should report error.
|
|
MathematicalProgram prog;
|
|
auto x = prog.NewContinuousVariables<2>();
|
|
prog.AddLinearConstraint(x(0) + x(1) >= 0);
|
|
|
|
MathematicalProgramResult result;
|
|
MosekSolver mosek_solver;
|
|
SolverOptions solver_options;
|
|
solver_options.SetOption(MosekSolver::id(), "non-existing options", 42);
|
|
DRAKE_EXPECT_THROWS_MESSAGE(
|
|
mosek_solver.Solve(prog, {}, solver_options, &result),
|
|
".*cannot set Mosek option \'non-existing options\' to value \'42\'.*");
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, Write) {
|
|
// Write model to a file.
|
|
MathematicalProgram prog;
|
|
auto x = prog.NewContinuousVariables<2>();
|
|
prog.AddLinearEqualityConstraint(x(0) + x(1) == 1);
|
|
prog.AddQuadraticCost(x(0) * x(0) + x(1) * x(1), true /* is_convex */);
|
|
MosekSolver mosek_solver;
|
|
SolverOptions solver_options;
|
|
const std::string file = temp_directory() + "mosek.mps";
|
|
EXPECT_FALSE(std::filesystem::exists(file));
|
|
solver_options.SetOption(MosekSolver::id(), "writedata", file);
|
|
mosek_solver.Solve(prog, {}, solver_options);
|
|
EXPECT_TRUE(std::filesystem::exists(file));
|
|
std::filesystem::remove(file);
|
|
// Set "writedata" to "". Now expect no model file.
|
|
solver_options.SetOption(MosekSolver::id(), "writedata", "");
|
|
mosek_solver.Solve(prog, {}, solver_options);
|
|
EXPECT_FALSE(std::filesystem::exists(file));
|
|
}
|
|
|
|
GTEST_TEST(MosekSolver, TestInitialGuess) {
|
|
// Mosek allows to set initial guess for integer/binary variables.
|
|
// Solve the following mixed-integer problem
|
|
// Find a point C on one of the line segment A1A2, A2A3, A3A4, A4A1 such that
|
|
// the distance from the point C to the point D = (0, 0) is minimized, where
|
|
// A1 = (-1, 0), A2 = (0, 1), A3 = (2, 0), A4 = (1, -0.5)
|
|
MathematicalProgram prog;
|
|
auto lambda = prog.NewContinuousVariables<5>();
|
|
auto y = prog.NewBinaryVariables<4>();
|
|
AddSos2Constraint(&prog, lambda.cast<symbolic::Expression>(),
|
|
y.cast<symbolic::Expression>());
|
|
Eigen::Matrix<double, 2, 5> pts_A;
|
|
pts_A.col(0) << -1, 0;
|
|
pts_A.col(1) << 0, 1;
|
|
pts_A.col(2) << 2, 0;
|
|
pts_A.col(3) << 1, -0.5;
|
|
pts_A.col(4) = pts_A.col(0);
|
|
// point C in the documentation above.
|
|
auto pt_C = prog.NewContinuousVariables<2>();
|
|
prog.AddLinearEqualityConstraint(pt_C == pts_A * lambda);
|
|
prog.AddQuadraticCost(pt_C(0) * pt_C(0) + pt_C(1) * pt_C(1));
|
|
|
|
MosekSolver solver;
|
|
SolverOptions solver_options;
|
|
// Allow only one solution (the one corresponding to the initial guess).
|
|
solver_options.SetOption(solver.id(), "MSK_IPAR_MIO_MAX_NUM_SOLUTIONS", 1);
|
|
// By setting y = (0, 1, 0, 0), pt_C = (0, 1), lambda = (0, 1, 0, 0, 0) point
|
|
// C is on the line segment A2A3. This is a valid integral solution with
|
|
// distance to origin = 1.
|
|
prog.SetInitialGuess(pt_C, Eigen::Vector2d(0, 1));
|
|
prog.SetInitialGuess(
|
|
lambda, (Eigen::Matrix<double, 5, 1>() << 0, 1, 0, 0, 0).finished());
|
|
prog.SetInitialGuess(y, Eigen::Vector4d(0, 1, 0, 0));
|
|
MathematicalProgramResult result;
|
|
solver.Solve(prog, prog.initial_guess(), solver_options, &result);
|
|
const double tol = 1E-8;
|
|
EXPECT_TRUE(result.is_success());
|
|
EXPECT_NEAR(result.get_optimal_cost(), 1, tol);
|
|
|
|
// By setting MSK_IPAR_MIO_CONSTRUCT_SOL=1, Mosek will first try to construct
|
|
// the continuous solution given the initial binary variable solutions. In
|
|
// this case it searches the point on line segment A2A3 that is closest
|
|
// to the origin, which is (0.4, 0.8).
|
|
solver_options.SetOption(solver.id(), "MSK_IPAR_MIO_CONSTRUCT_SOL", 1);
|
|
solver.Solve(prog, prog.initial_guess(), solver_options, &result);
|
|
EXPECT_TRUE(result.is_success());
|
|
EXPECT_NEAR(result.get_optimal_cost(), 0.8, tol);
|
|
|
|
// By setting y = (0, 0, 0, 1), point C is on the line segment A4A1. The
|
|
// minimal squared distance is 1.0 / 17
|
|
prog.SetInitialGuess(y, Eigen::Vector4d(0, 0, 0, 1));
|
|
solver.Solve(prog, prog.initial_guess(), solver_options, &result);
|
|
EXPECT_TRUE(result.is_success());
|
|
EXPECT_NEAR(result.get_optimal_cost(), 1.0 / 17, tol);
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, UnivariateQuarticSos) {
|
|
UnivariateQuarticSos dut;
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const auto result = solver.Solve(dut.prog());
|
|
dut.CheckResult(result, 1E-10);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, BivariateQuarticSos) {
|
|
BivariateQuarticSos dut;
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const auto result = solver.Solve(dut.prog());
|
|
dut.CheckResult(result, 1E-10);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, SimpleSos1) {
|
|
SimpleSos1 dut;
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const auto result = solver.Solve(dut.prog());
|
|
dut.CheckResult(result, 2E-9);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, MotzkinPolynomial) {
|
|
MotzkinPolynomial dut;
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const auto result = solver.Solve(dut.prog());
|
|
dut.CheckResult(result, 1.3E-9);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, UnivariateNonnegative1) {
|
|
UnivariateNonnegative1 dut;
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const auto result = solver.Solve(dut.prog());
|
|
dut.CheckResult(result, 1E-9);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, MinimalDistanceFromSphereProblem) {
|
|
for (bool with_linear_cost : {true, false}) {
|
|
MinimalDistanceFromSphereProblem<3> dut1(Eigen::Vector3d(0, 0, 0),
|
|
Eigen::Vector3d(0, 0, 0), 1,
|
|
with_linear_cost);
|
|
MinimalDistanceFromSphereProblem<3> dut2(Eigen::Vector3d(0, 0, 1),
|
|
Eigen::Vector3d(0, 0, 0), 1,
|
|
with_linear_cost);
|
|
MinimalDistanceFromSphereProblem<3> dut3(Eigen::Vector3d(0, 1, 1),
|
|
Eigen::Vector3d(0, 0, 0), 1,
|
|
with_linear_cost);
|
|
MinimalDistanceFromSphereProblem<3> dut4(Eigen::Vector3d(0, 1, 1),
|
|
Eigen::Vector3d(-1, -1, 0), 1,
|
|
with_linear_cost);
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const double tol = 1E-4;
|
|
dut1.SolveAndCheckSolution(solver, tol);
|
|
dut2.SolveAndCheckSolution(solver, tol);
|
|
dut3.SolveAndCheckSolution(solver, tol);
|
|
dut4.SolveAndCheckSolution(solver, tol);
|
|
}
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, SolveSDPwithQuadraticCosts) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
SolveSDPwithQuadraticCosts(solver, 1E-8);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, LPDualSolution1) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestLPDualSolution1(solver, 1E-8);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, LPDualSolution2) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestLPDualSolution2(solver, 1E-8);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, LPDualSolution2Scaled) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestLPDualSolution2Scaled(solver, 1E-8);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, LPDualSolution3) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestLPDualSolution3(solver, 1E-8);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, LPDualSolution4) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestLPDualSolution4(solver, 1E-8);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, QPDualSolution1) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
SolverOptions solver_options;
|
|
// The default tolerance is 1E-8, and one entry of the optimal solution is
|
|
// 0. This means the error on the primal and dual solution is in the order
|
|
// of 1E-4, that is too large.
|
|
solver_options.SetOption(solver.id(), "MSK_DPAR_INTPNT_QO_TOL_REL_GAP",
|
|
1e-12);
|
|
TestQPDualSolution1(solver, solver_options, 6E-6);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, QPDualSolution2) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestQPDualSolution2(solver);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, QPDualSolution3) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestQPDualSolution3(solver, 1E-6, 3E-4);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, EqualityConstrainedQPDualSolution1) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestEqualityConstrainedQPDualSolution1(solver);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, EqualityConstrainedQPDualSolution2) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestEqualityConstrainedQPDualSolution2(solver);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekSolver, SocpDualSolution1) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
SolverOptions solver_options{};
|
|
TestSocpDualSolution1(solver, solver_options, 1E-7);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekSolver, SocpDualSolution2) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
SolverOptions solver_options{};
|
|
TestSocpDualSolution2(solver, solver_options, 1E-6);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, SDPDualSolution1) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestSDPDualSolution1(solver, 3E-6);
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, TestNonconvexQP) {
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
TestNonconvexQP(solver, true);
|
|
}
|
|
}
|
|
|
|
template <typename C>
|
|
void CheckDualSolutionNotNan(const MathematicalProgramResult& result,
|
|
const Binding<C>& constraint) {
|
|
const auto dual_sol = result.GetDualSolution(constraint);
|
|
for (int i = 0; i < dual_sol.rows(); ++i) {
|
|
EXPECT_FALSE(std::isnan(dual_sol(i)));
|
|
}
|
|
}
|
|
GTEST_TEST(MosekTest, InfeasibleLinearProgramTest) {
|
|
// Solve an infeasible LP, make sure the infeasible solution is returned.
|
|
MathematicalProgram prog;
|
|
auto x = prog.NewContinuousVariables<2>();
|
|
auto constraint1 = prog.AddLinearConstraint(x(0) + x(1) >= 3);
|
|
auto constraint2 = prog.AddBoundingBoxConstraint(0, 1, x);
|
|
prog.AddLinearCost(x(0) + 2 * x(1));
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const auto result = solver.Solve(prog);
|
|
ASSERT_FALSE(result.is_success());
|
|
// Check that the primal solutions are not NAN.
|
|
for (int i = 0; i < x.rows(); ++i) {
|
|
EXPECT_FALSE(std::isnan(result.GetSolution(x(i))));
|
|
}
|
|
// Check that the dual solutions are not NAN.
|
|
CheckDualSolutionNotNan(result, constraint1);
|
|
CheckDualSolutionNotNan(result, constraint2);
|
|
// Check that the optimal cost is not NAN.
|
|
EXPECT_FALSE(std::isnan(result.get_optimal_cost()));
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, InfeasibleSemidefiniteProgramTest) {
|
|
// Solve an infeasible SDP, make sure the infeasible solution is returned.
|
|
MathematicalProgram prog;
|
|
auto X = prog.NewSymmetricContinuousVariables<3>();
|
|
auto constraint1 = prog.AddPositiveSemidefiniteConstraint(X);
|
|
auto constraint2 =
|
|
prog.AddLinearConstraint(X(0, 0) + X(1, 1) + X(2, 2) <= -1);
|
|
prog.AddLinearCost(X(1, 2));
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
const auto result = solver.Solve(prog);
|
|
ASSERT_FALSE(result.is_success());
|
|
// Check that the primal solutions are not NAN.
|
|
const auto X_sol = result.GetSolution(X);
|
|
for (int i = 0; i < X.rows(); ++i) {
|
|
for (int j = 0; j < X.cols(); ++j) {
|
|
EXPECT_FALSE(std::isnan(X_sol(i, j)));
|
|
}
|
|
}
|
|
// Check that the dual solutions are not NAN.
|
|
CheckDualSolutionNotNan(result, constraint1);
|
|
CheckDualSolutionNotNan(result, constraint2);
|
|
|
|
// Check that the optimal cost is not NAN.
|
|
EXPECT_FALSE(std::isnan(result.get_optimal_cost()));
|
|
}
|
|
}
|
|
|
|
GTEST_TEST(MosekTest, LPNoBasisSelection) {
|
|
// We solve an LP using interior point method (IPM), but don't do basis
|
|
// identification (which cleans the solution) after IPM finishes. Hence the
|
|
// basis solution is not available and Mosek can only acquire the IPM
|
|
// solution.
|
|
MathematicalProgram prog;
|
|
auto x = prog.NewContinuousVariables<2>();
|
|
prog.AddBoundingBoxConstraint(0, kInf, x);
|
|
prog.AddLinearConstraint(x(0) + x(1) <= 1);
|
|
prog.AddLinearCost(-x(0) - 2 * x(1));
|
|
|
|
SolverOptions solver_options;
|
|
solver_options.SetOption(MosekSolver::id(), "MSK_IPAR_INTPNT_BASIS", 0);
|
|
MosekSolver solver;
|
|
if (solver.available()) {
|
|
auto result = solver.Solve(prog, std::nullopt, solver_options);
|
|
EXPECT_TRUE(result.is_success());
|
|
const auto x_sol = result.GetSolution(x);
|
|
const double tol = 1E-6;
|
|
EXPECT_TRUE(CompareMatrices(x_sol, Eigen::Vector2d(0, 1), tol));
|
|
}
|
|
}
|
|
} // namespace test
|
|
} // namespace solvers
|
|
} // namespace drake
|
|
|
|
int main(int argc, char** argv) {
|
|
// Ensure that we have the MOSEK license for the entire duration of this test,
|
|
// so that we do not have to release and re-acquire the license for every
|
|
// test.
|
|
auto mosek_license = drake::solvers::MosekSolver::AcquireLicense();
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|