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/mosek_solver_internal.h

581 lines
25 KiB

#pragma once
// For external users, please do not include this header file. It only exists so
// that we can expose the internals to mosek_solver_internal_test.cc
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <mosek.h>
#include "drake/solvers/constraint.h"
#include "drake/solvers/mathematical_program.h"
#include "drake/solvers/mathematical_program_result.h"
namespace drake {
namespace solvers {
namespace internal {
// Mosek treats psd matrix variables in a special manner.
// Check https://docs.mosek.com/10.0/capi/tutorial-sdo-shared.html for more
// details. To summarize, Mosek stores a positive semidefinite (psd) matrix
// variable as a "bar var" (as called in Mosek's API, for example
// https://docs.mosek.com/10.0/capi/tutorial-sdo-shared.html). Inside Mosek, it
// accesses each of the psd matrix variable with a unique ID. Moreover, the
// Mosek user cannot access the entries of the psd matrix variable individually;
// instead, the user can only access the matrix X̅ as a whole. To impose
// linear constraints on psd matrix variable X̅ entries, the user must specify a
// "coefficient matrix" A̅ that multiplies X̅ (using the matrix inner product) to
// yield the linear constraint lower ≤ <A̅, X̅> ≤ upper. For example, to impose
// the constraint X̅(0, 0) + X̅(1, 0) = 1, where X̅ is a 2 x 2 psd matrix, Mosek
// requires the user to write it as <A̅, X̅> = 1, where
// A̅ = ⌈1 0.5⌉
// ⌊0.5 0⌋
// On the other hand, drake::solvers::MathematicalProgram doesn't treat psd
// matrix variables in a special manner.
// MatrixVariableEntry stores the data needed to refer to a particular entry of
// a Mosek matrix variable.
class MatrixVariableEntry {
public:
typedef size_t Id;
MatrixVariableEntry(MSKint64t bar_matrix_index, MSKint32t row_index,
MSKint32t col_index, int num_matrix_rows)
: bar_matrix_index_{bar_matrix_index},
row_index_{row_index},
col_index_{col_index},
num_matrix_rows_{num_matrix_rows},
id_{get_next_id()} {
// Mosek only stores the lower triangular part of the symmetric matrix.
DRAKE_ASSERT(row_index_ >= col_index_);
}
MSKint64t bar_matrix_index() const { return bar_matrix_index_; }
MSKint32t row_index() const { return row_index_; }
MSKint32t col_index() const { return col_index_; }
int num_matrix_rows() const { return num_matrix_rows_; }
Id id() const { return id_; }
// Returns the index of the entry in a vector formed by stacking the lower
// triangular part of the symmetric matrix column by column.
int IndexInLowerTrianglePart() const {
return (2 * num_matrix_rows_ - col_index_ + 1) * col_index_ / 2 +
row_index_ - col_index_;
}
private:
static size_t get_next_id();
MSKint64t bar_matrix_index_;
MSKint32t row_index_;
MSKint32t col_index_;
int num_matrix_rows_;
Id id_;
};
// Mosek stores dual variable in different categories, called slc, suc, slx, sux
// and snx. Refer to
// https://docs.mosek.com/10.0/capi/alphabetic-functionalities.html#mosek.task.getsolution
// for more information.
enum class DualVarType {
kLinearConstraint, ///< Corresponds to Mosek's slc and suc.
kVariableBound, ///< Corresponds to Mosek's slx and sux.
kNonlinearConic, ///< Corresponds to Mosek's snx.
};
struct ConstraintDualIndex {
// Type of the dual variable.
DualVarType type;
// Index of the dual variable. We will use -1 to indicate that a constraint
// can never be active.
int index{};
};
using ConstraintDualIndices = std::vector<ConstraintDualIndex>;
template <typename ConstraintType>
using DualMap =
std::unordered_map<Binding<ConstraintType>, ConstraintDualIndices>;
enum class LinearConstraintBoundType {
kEquality,
kInequality,
};
// Mosek treats matrix variables (variables in the psd matrix) in a special
// manner, while MathematicalProgram doesn't. Hence we need to pick out the
// variables in prog.positive_semidefinite_constraint(), record how they will
// be stored in Mosek, and also how the remaining non-matrix variable will be
// stored in Mosek. Note that we only loop through
// PositiveSemidefiniteConstraint, not LinearMatrixInequalityConstraint.
struct MapDecisionVariableToMosekVariable {
public:
explicit MapDecisionVariableToMosekVariable(const MathematicalProgram& prog);
std::unordered_map<int, MatrixVariableEntry>
decision_variable_to_mosek_matrix_variable{};
std::unordered_map<int, int> decision_variable_to_mosek_nonmatrix_variable{};
// Multiple entries in Mosek matrix variables could correspond to the same
// decision variable. We will need to add linear equality constraints to
// equate these entries.
std::unordered_map<int, std::vector<MatrixVariableEntry>>
matrix_variable_entries_for_same_decision_variable{};
};
// MosekSolverProgram is a temporary object used by our MosekSolver
// implementation that is created (and destroyed) once per Solve() operation. It
// provides individual, program-specific helper functions to translate a
// MathematicalProgram into Mosek's API. We've separated it from
// MosekSolver::DoSolve both to make it easier to understand and to unit test
// each function one by one.
class MosekSolverProgram {
public:
MosekSolverProgram(const MathematicalProgram& prog, MSKenv_t env);
~MosekSolverProgram();
// If a matrix variable entry X̅(m, n) appears in a cost or a constraint
// (except the psd constraint), then we need a matrix Eₘₙ stored inside Mosek,
// such that <Eₘₙ, X̅> = X̅(m, n). In this function we add the symmetric matrix
// Eₘₙ into Mosek, and record the index of Eₘₙ in Mosek.
// @param[out] E_idx The index of Eₘₙ for @p matrix_variable_entry.
MSKrescodee AddMatrixVariableEntryCoefficientMatrixIfNonExistent(
const MatrixVariableEntry& matrix_variable_entry, MSKint64t* E_index);
// Add the product c * X̅(i, j) to a constraint.
// This function should be called only if that Mosek matrix variable X̅ appear
// only once in this constraint. Otherwise we should call
// AddLinearConstraintToMosek, which first collects all the
// entries X̅(i, j) belonging to this matrix variable X̅ in this constraint, and
// then forms a matrix A, such that <A, X̅> contains the weighted sum of all
// entries of X̅ in this constraint.
MSKrescodee AddScalarTimesMatrixVariableEntryToMosek(
MSKint32t constraint_index,
const MatrixVariableEntry& matrix_variable_entry, MSKrealt scalar);
// Determine the sense of each constraint. The sense can be equality
// constraint, less than, greater than, or bounded on both side.
MSKrescodee SetMosekLinearConstraintBound(
int linear_constraint_index, double lower, double upper,
LinearConstraintBoundType bound_type);
// Add the linear constraint lower <= A * decision_vars + B * slack_vars <=
// upper.
MSKrescodee AddLinearConstraintToMosek(
const MathematicalProgram& prog, const Eigen::SparseMatrix<double>& A,
const Eigen::SparseMatrix<double>& B, const Eigen::VectorXd& lower,
const Eigen::VectorXd& upper,
const VectorX<symbolic::Variable>& decision_vars,
const std::vector<MSKint32t>& slack_vars_mosek_indices,
LinearConstraintBoundType bound_type);
// Convert the expression A * decicion_vars + B * slack_vars to Mosek affine
// expression format
// ∑ⱼ fᵢⱼ xⱼ + ∑ⱼ<F̅ᵢⱼ, X̅ⱼ>
// Please refer to
// https://docs.mosek.com/latest/capi/tutorial-acc-optimizer.html for an
// introduction on Mosek affine expression, and
// https://docs.mosek.com/latest/capi/tutorial-sdo-shared.html for the affine
// expression with matrix variables.
// F̅ᵢⱼ is a weighted sum of the symmetric matrix E stored inside Mosek.
// bar_F[i][j] stores the indices of E and the weights of E.
// namely F̅ᵢⱼ = ∑ₖbar_F[i][j][k].second* E[bar_F[i][j][k].first]
MSKrescodee ParseLinearExpression(
const solvers::MathematicalProgram& prog,
const Eigen::SparseMatrix<double>& A,
const Eigen::SparseMatrix<double>& B,
const VectorX<symbolic::Variable>& decision_vars,
const std::vector<MSKint32t>& slack_vars_mosek_indices,
std::vector<MSKint32t>* F_subi, std::vector<MSKint32t>* F_subj,
std::vector<MSKrealt>* F_valij,
std::vector<std::unordered_map<
MSKint64t, std::pair<std::vector<MSKint64t>, std::vector<MSKrealt>>>>*
bar_F);
// Same as ParseLinearExpression, but assumes that each entry in
// `decision_vars` is unique.
MSKrescodee ParseLinearExpressionNoDuplication(
const solvers::MathematicalProgram& prog,
const Eigen::SparseMatrix<double>& A,
const Eigen::SparseMatrix<double>& B,
const VectorX<symbolic::Variable>& decision_vars,
const std::vector<MSKint32t>& slack_vars_mosek_indices,
std::vector<MSKint32t>* F_subi, std::vector<MSKint32t>* F_subj,
std::vector<MSKrealt>* F_valij,
std::vector<std::unordered_map<
MSKint64t, std::pair<std::vector<MSKint64t>, std::vector<MSKrealt>>>>*
bar_F);
// Add LinearConstraints and LinearEqualityConstraints to the Mosek task.
// @param[out] dual_indices maps each linear constraint to its dual variable
// indices.
template <typename C>
MSKrescodee AddLinearConstraintsFromBindings(
const std::vector<Binding<C>>& constraint_list,
LinearConstraintBoundType bound_type, const MathematicalProgram& prog,
std::unordered_map<Binding<C>, ConstraintDualIndices>* dual_indices);
// @param[out] lin_eq_con_dual_indices maps each linear equality constraint to
// its dual variable indices.
MSKrescodee AddLinearConstraints(
const MathematicalProgram& prog,
std::unordered_map<Binding<LinearConstraint>, ConstraintDualIndices>*
linear_con_dual_indices,
std::unordered_map<Binding<LinearEqualityConstraint>,
ConstraintDualIndices>* lin_eq_con_dual_indices);
// Add the bounds on the decision variables in @p prog. Note that if a
// decision variable in positive definite matrix has a bound, we need to add
// new linear constraint to Mosek to bound that variable.
// @param[out] dual_indices Map each bounding box constraint to its dual
// variable indices.
MSKrescodee AddBoundingBoxConstraints(
const MathematicalProgram& prog,
std::unordered_map<Binding<BoundingBoxConstraint>,
std::pair<ConstraintDualIndices,
ConstraintDualIndices>>* dual_indices);
/**
Adds the constraint that A * decision_vars + B * slack_vars + c is in an
affine cone.
@param[out] acc_index The index of the newly added affine cone.
*/
MSKrescodee AddAffineConeConstraint(
const MathematicalProgram& prog, const Eigen::SparseMatrix<double>& A,
const Eigen::SparseMatrix<double>& B,
const VectorX<symbolic::Variable>& decision_vars,
const std::vector<MSKint32t>& slack_vars_mosek_indices,
const Eigen::VectorXd& c, MSKconetypee cone_type, MSKint64t* acc_index);
/*
* This is the helper function to add three types of conic constraints
* 1. A Lorentz cone constraint:
* z = A*x+b
* z0 >= sqrt(z1^2 + .. zN^2)
* 2. A rotated Lorentz cone constraint:
* z = A*x+b
* z0*z1 >= z2^2 + .. + zN^2,
* z0 >= 0, z1 >=0
* 3. An exonential cone constraint:
* z = A*x+b
* z0 >= z1 * exp(z2 / z1)
* @param[out] acc_indices Maps each conic constraint to its Affine Cone
* Constraint indices.
*/
template <typename C>
MSKrescodee AddConeConstraints(
const MathematicalProgram& prog,
const std::vector<Binding<C>>& cone_constraints,
std::unordered_map<Binding<C>, MSKint64t>* acc_indices);
// @param[out] psd_barvar_indices maps each psd constraint to Mosek matrix
// variable
MSKrescodee AddPositiveSemidefiniteConstraints(
const MathematicalProgram& prog,
std::unordered_map<Binding<PositiveSemidefiniteConstraint>, MSKint32t>*
psd_barvar_indices);
MSKrescodee AddLinearMatrixInequalityConstraint(
const MathematicalProgram& prog);
MSKrescodee AddLinearCost(const Eigen::SparseVector<double>& linear_coeff,
const VectorX<symbolic::Variable>& linear_vars,
const MathematicalProgram& prog);
MSKrescodee AddQuadraticCostAsLinearCost(
const Eigen::SparseMatrix<double>& Q_lower,
const VectorX<symbolic::Variable>& quadratic_vars,
const MathematicalProgram& prog);
MSKrescodee AddQuadraticCost(
const Eigen::SparseMatrix<double>& Q_quadratic_vars,
const VectorX<symbolic::Variable>& quadratic_vars,
const MathematicalProgram& prog);
MSKrescodee AddCosts(const MathematicalProgram& prog);
// @param[out] with_integer_or_binary_variables True if the program has
// integer or binary variables.
MSKrescodee SpecifyVariableType(const MathematicalProgram& prog,
bool* with_integer_or_binary_variables);
// Some entries in Mosek matrix variables might correspond to the same
// decision variable in MathematicalProgram. Add the equality constraint
// between these matrix variable entries.
MSKrescodee
AddEqualityConstraintBetweenMatrixVariablesForSameDecisionVariable();
// @param[in/out] result Update the dual solution for psd constraints in
// result.
MSKrescodee SetPositiveSemidefiniteConstraintDualSolution(
const MathematicalProgram& prog,
const std::unordered_map<Binding<PositiveSemidefiniteConstraint>,
MSKint32t>& psd_barvar_indices,
MSKsoltypee whichsol, MathematicalProgramResult* result) const;
// @param[in/out] result Update the dual solution in result.
MSKrescodee SetDualSolution(
MSKsoltypee which_sol, const MathematicalProgram& prog,
const std::unordered_map<
Binding<BoundingBoxConstraint>,
std::pair<ConstraintDualIndices, ConstraintDualIndices>>&
bb_con_dual_indices,
const DualMap<LinearConstraint>& linear_con_dual_indices,
const DualMap<LinearEqualityConstraint>& lin_eq_con_dual_indices,
const std::unordered_map<Binding<LorentzConeConstraint>, MSKint64t>&
lorentz_cone_acc_indices,
const std::unordered_map<Binding<RotatedLorentzConeConstraint>,
MSKint64t>& rotated_lorentz_cone_acc_indices,
const std::unordered_map<Binding<ExponentialConeConstraint>, MSKint64t>&
exp_cone_acc_indices,
const std::unordered_map<Binding<PositiveSemidefiniteConstraint>,
MSKint32t>& psd_barvar_indices,
MathematicalProgramResult* result) const;
// @param[out] print_to_console Set to true if solver options requires
// printing the log to the console.
// @param[out] print_file_name Set to the name of the print file store in
// solver options. If solver options doesn't store the print file name, then
// set *print_file_name to an empty string.
// @param[out] msk_writedata If solver options stores the file for writing
// data, then put the file name to msk_writedata for later use.
MSKrescodee UpdateOptions(const SolverOptions& solver_options,
SolverId mosek_id, bool* print_to_console,
std::string* print_file_name,
std::optional<std::string>* msk_writedata);
MSKtask_t task() const { return task_; }
// For each entry of the matrix variable X̅ᵢ(m,n), if this entry is also used
// in the cost or constraint, then we will use the term <E̅ₘₙ,X̅ᵢ>, where
// the inner product <E̅ₘₙ, X̅ᵢ> = X̅ᵢ(m,n). The "selection matrix" E̅ₘₙ helps
// to select the (m, n)'th entry of the psd matrix variable. E̅ₘₙ is stored
// inside Mosek with a unique ID.
// matrix_variable_entry_to_selection_matrix_id maps the matrix variable
// entry to the ID of E̅ₘₙ.
// TODO(hongkai.dai): do not map the matrix variable entry to the selection
// matrix, instead maps the tuple (matrix_size, row_index, column_index) to
// the selection matrix. This will create fewer selection matrices, when
// multiple matrix variables have the same size.
const std::unordered_map<MatrixVariableEntry::Id, MSKint64t>&
matrix_variable_entry_to_selection_matrix_id() const {
return matrix_variable_entry_to_selection_matrix_id_;
}
const std::unordered_map<int, MatrixVariableEntry>&
decision_variable_to_mosek_matrix_variable() const {
return map_decision_var_to_mosek_var_
.decision_variable_to_mosek_matrix_variable;
}
const std::unordered_map<int, int>&
decision_variable_to_mosek_nonmatrix_variable() const {
return map_decision_var_to_mosek_var_
.decision_variable_to_mosek_nonmatrix_variable;
}
const std::unordered_map<int, std::vector<MatrixVariableEntry>>&
matrix_variable_entries_for_same_decision_variable() const {
return map_decision_var_to_mosek_var_
.matrix_variable_entries_for_same_decision_variable;
}
private:
MSKtask_t task_{nullptr};
std::unordered_map<MatrixVariableEntry::Id, MSKint64t>
matrix_variable_entry_to_selection_matrix_id_{};
const MapDecisionVariableToMosekVariable map_decision_var_to_mosek_var_;
};
template <typename C>
MSKrescodee MosekSolverProgram::AddLinearConstraintsFromBindings(
const std::vector<Binding<C>>& constraint_list,
LinearConstraintBoundType bound_type, const MathematicalProgram& prog,
std::unordered_map<Binding<C>, ConstraintDualIndices>* dual_indices) {
MSKrescodee rescode{MSK_RES_OK};
for (const auto& binding : constraint_list) {
const auto& constraint = binding.evaluator();
const Eigen::SparseMatrix<double>& A = constraint->get_sparse_A();
const Eigen::VectorXd& lb = constraint->lower_bound();
const Eigen::VectorXd& ub = constraint->upper_bound();
Eigen::SparseMatrix<double> B_zero(A.rows(), 0);
B_zero.setZero();
int num_linear_constraints{-1};
rescode = MSK_getnumcon(task_, &num_linear_constraints);
if (rescode != MSK_RES_OK) {
return rescode;
}
rescode = AddLinearConstraintToMosek(prog, A, B_zero, lb, ub,
binding.variables(), {}, bound_type);
if (rescode != MSK_RES_OK) {
return rescode;
}
ConstraintDualIndices constraint_dual_indices(lb.rows());
for (int i = 0; i < lb.rows(); ++i) {
constraint_dual_indices[i].type = DualVarType::kLinearConstraint;
constraint_dual_indices[i].index = num_linear_constraints + i;
}
dual_indices->emplace(binding, constraint_dual_indices);
}
return rescode;
}
template <typename C>
MSKrescodee MosekSolverProgram::AddConeConstraints(
const MathematicalProgram& prog,
const std::vector<Binding<C>>& cone_constraints,
std::unordered_map<Binding<C>, MSKint64t>* acc_indices) {
static_assert(std::is_same_v<C, LorentzConeConstraint> ||
std::is_same_v<C, RotatedLorentzConeConstraint> ||
std::is_same_v<C, ExponentialConeConstraint>,
"Should be either Lorentz cone constraint, rotated Lorentz "
"cone or exponential cone constraint");
const bool is_rotated_cone = std::is_same_v<C, RotatedLorentzConeConstraint>;
MSKrescodee rescode = MSK_RES_OK;
for (auto const& binding : cone_constraints) {
MSKint64t acc_index;
if (is_rotated_cone) {
// Drake's RotatedLorentzConeConstraint imposes z₀ * z₁ ≥ z₂² + z₃² + ...
// zₙ₋₁² where z being an affine expression of x, while Mosek's rotated
// quadratic cone imposes 2 * z₀ * z₁ ≥ z₂² + z₃² + ... zₙ₋₁² (notice the
// factor of 2). Hence we will impose the constraint that the vector
// ⌈0.5*(A.row(0)*x + b(0))⌉
// | A.row(1)*x + b(1) |
// | ... |
// ⌊ A.row(n-1)*x + b(n-1)⌋
// is in Mosek's rotated quadratic cone. Namely we multiply 0.5 to the
// first row of A, b.
Eigen::SparseMatrix<double> A = binding.evaluator()->A();
for (int k = 0; k < A.outerSize(); ++k) {
for (Eigen::SparseMatrix<double>::InnerIterator it(A, k); it; ++it) {
if (it.row() == 0) {
it.valueRef() *= 0.5;
}
}
}
Eigen::VectorXd b = binding.evaluator()->b();
b(0) *= 0.5;
rescode = this->AddAffineConeConstraint(
prog, A, Eigen::SparseMatrix<double>(A.rows(), 0),
binding.variables(), {}, b, MSK_CT_RQUAD, &acc_index);
if (rescode != MSK_RES_OK) {
return rescode;
}
} else {
MSKconetypee cone_type;
if (std::is_same_v<C, LorentzConeConstraint>) {
cone_type = MSK_CT_QUAD;
} else if (std::is_same_v<C, ExponentialConeConstraint>) {
cone_type = MSK_CT_PEXP;
}
rescode = this->AddAffineConeConstraint(
prog, binding.evaluator()->A(),
Eigen::SparseMatrix<double>(binding.evaluator()->A().rows(), 0),
binding.variables(), {}, binding.evaluator()->b(), cone_type,
&acc_index);
if (rescode != MSK_RES_OK) {
return rescode;
}
}
acc_indices->emplace(binding, acc_index);
}
return rescode;
}
// @param slx Mosek dual variables for variable lower bound. See
// https://docs.mosek.com/10.0/capi/alphabetic-functionalities.html#mosek.task.getslx
// @param sux Mosek dual variables for variable upper bound. See
// https://docs.mosek.com/10.0/capi/alphabetic-functionalities.html#mosek.task.getsux
// @param slc Mosek dual variables for linear constraint lower bound. See
// https://docs.mosek.com/10.0/capi/alphabetic-functionalities.html#mosek.task.getslc
// @param suc Mosek dual variables for linear constraint upper bound. See
// https://docs.mosek.com/10.0/capi/alphabetic-functionalities.html#mosek.task.getsuc
void SetBoundingBoxDualSolution(
const std::vector<Binding<BoundingBoxConstraint>>& constraints,
const std::vector<MSKrealt>& slx, const std::vector<MSKrealt>& sux,
const std::vector<MSKrealt>& slc, const std::vector<MSKrealt>& suc,
const std::unordered_map<
Binding<BoundingBoxConstraint>,
std::pair<ConstraintDualIndices, ConstraintDualIndices>>&
bb_con_dual_indices,
MathematicalProgramResult* result);
template <typename C>
void SetLinearConstraintDualSolution(
const std::vector<Binding<C>>& bindings, const std::vector<MSKrealt>& slc,
const std::vector<MSKrealt>& suc,
const std::unordered_map<Binding<C>, ConstraintDualIndices>&
linear_con_dual_indices,
MathematicalProgramResult* result) {
for (const auto& binding : bindings) {
const ConstraintDualIndices duals = linear_con_dual_indices.at(binding);
Eigen::VectorXd dual_sol =
Eigen::VectorXd::Zero(binding.evaluator()->num_constraints());
for (int i = 0; i < dual_sol.rows(); ++i) {
DRAKE_DEMAND(duals[i].type == DualVarType::kLinearConstraint);
// Mosek defines all dual solutions as non-negative. However we use
// "reduced cost" as the dual solution, so the dual solution for a
// lower bound should be non-negative, while the dual solution for
// an upper bound should be non-positive.
if (slc[duals[i].index] > suc[duals[i].index]) {
dual_sol[i] = slc[duals[i].index];
} else {
dual_sol[i] = -suc[duals[i].index];
}
}
result->set_dual_solution(binding, dual_sol);
}
}
/*
* Sets the dual solution for the affine cone constraints.
* @param bindings The constraint for which the dual solution will be set.
* @param task The Mosek task.
* @param whichsol The solution type. See
* https://docs.mosek.com/latest/capi/constants.html#mosek.soltype
* @param acc_indices Maps each constraint to the affine cone constraint
* indices.
* @param[out] result The dual solution in `result` will be set.
*/
template <typename C>
MSKrescodee SetAffineConeConstraintDualSolution(
const std::vector<Binding<C>>& bindings, MSKtask_t task,
MSKsoltypee whichsol,
const std::unordered_map<Binding<C>, MSKint64t>& acc_indices,
MathematicalProgramResult* result) {
for (const auto& binding : bindings) {
const MSKint64t acc_index = acc_indices.at(binding);
MSKint64t acc_dim;
auto rescode = MSK_getaccn(task, acc_index, &acc_dim);
if (rescode != MSK_RES_OK) {
return rescode;
}
Eigen::VectorXd dual_sol = Eigen::VectorXd::Zero(acc_dim);
rescode = MSK_getaccdoty(task, whichsol, acc_index, dual_sol.data());
if (rescode != MSK_RES_OK) {
return rescode;
}
if constexpr (std::is_same_v<C, RotatedLorentzConeConstraint>) {
// Drake's rotated Lorentz cone constraint on z = Ax+b is
// K_drake={ z | z₀z₁≥ z₂² + ... zₙ₋₁², z₀≥0, z₁≥0}
// On the other hand, Mosek's rotated Lorentz cone constraint has a
// multiplier of 2
// K_mosek={ z | 2z₀z₁≥ z₂² + ... zₙ₋₁², z₀≥0, z₁≥0} Hence
// we can write K_drake = C*K_mosek where C = diag([2, 1, ..., 1]). By
// duality we know K_drake_dual = C⁻ᵀ*K_mosek_dual, where K_drake_dual is
// the dual cone of K_drake, likewise K_mosek_dual is the dual cone of
// K_mosek.
dual_sol(0) *= 0.5;
}
result->set_dual_solution(binding, dual_sol);
}
return MSK_RES_OK;
}
} // namespace internal
} // namespace solvers
} // namespace drake