Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for MOSEK, linear, conic and mip #4452

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ endif()
CMAKE_DEPENDENT_OPTION(USE_GUROBI "Use the Gurobi solver" ON "BUILD_CXX" OFF)
message(STATUS "Gurobi support: ${USE_GUROBI}")

## MOSEK
# see: https://mosek.com
CMAKE_DEPENDENT_OPTION(USE_HIGHS "Use the MOSEK solver" ON "BUILD_CXX" OFF)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/USE_HIGHS/USE_MOSEK/ ;)

message(STATUS "MOSEK support: ${USE_MOSEK}")

## HiGHS
# see: https://github.com/ERGO-Code/HiGHS
CMAKE_DEPENDENT_OPTION(USE_HIGHS "Use the HiGHS solver" ON "BUILD_CXX" OFF)
Expand Down
199 changes: 199 additions & 0 deletions cmake/FindMOSEK.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# Copyright 2010-2024 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#[=======================================================================[.rst:
FindMosek
--------

This module determines the Mosek library of the system.

IMPORTED Targets
^^^^^^^^^^^^^^^^

This module defines :prop_tgt:`IMPORTED` target ``mosek::mosek``, if
Mosek has been found.

Result Variables
^^^^^^^^^^^^^^^^

This module defines the following variables:

::

MOSEK_FOUND - True if Mosek found.

Hints
^^^^^

A user may set ``MOSEK_PLATFORM_DIR`` to a Mosek installation platform
directoru to tell this module where to look, or ``MOSEK_BASE`` to point
to path where the ``mosek/`` directory is located.
#]=======================================================================]
set(MOSEK_FOUND FALSE)
message(STATUS "Locating MOSEK")

if(CMAKE_C_COMPILER_LOADED)
include (CheckIncludeFile)
include (CheckCSourceCompiles)
elseif(CMAKE_CXX_COMPILER_LOADED)
include (CheckIncludeFileCXX)
include (CheckCXXSourceCompiles)
else()
message(FATAL_ERROR "FindMosek only works if either C or CXX language is enabled")
endif()

if(APPLE)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)")
SET(MOSEK_PLATFORM_NAME "osxaarch64")
elseif()
message(FATAL_ERROR "Mosek not supported for ${CMAKE_SYSTEM} / ${CMAKE_SYSTEM_PROCESSOR}")
endif()
elseif(UNIX)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)")
SET(MOSEK_PLATFORM_NAME "linuxaarch64")
else()
SET(MOSEK_PLATFORM_NAME "linux64x86")
endif()
elseif(MSVC)
SET(MOSEK_PLATFORM_NAME "win64x86")
else()
message(FATAL_ERROR "Mosek not supported for ${CMAKE_SYSTEM}")
endif()

function(FindMosekPlatformInPath RESULT PATH)
SET(${RESULT} "")
if(EXISTS "${PATH}/mosek")
SET(dirlist "")
FILE(GLOB entries LIST_DIRECTORIES true "${PATH}/mosek/*")
FOREACH(f ${entries})
if(IS_DIRECTORY "${f}")
get_filename_component(bn "${f}" NAME)
if("${bn}" MATCHES "^[0-9]+[.][0-9]+$")
if (${bn} GREATER_EQUAL "10.0")
LIST(APPEND dirlist "${bn}")
endif()
endif()
endif()
ENDFOREACH()
LIST(SORT dirlist COMPARE NATURAL ORDER DESCENDING)

if(MOSEK_PLATFORM_NAME)
foreach(MOSEK_VERSION ${dirlist})
SET(MSKPFDIR "${PATH}/mosek/${MOSEK_VERSION}/tools/platform/${MOSEK_PLATFORM_NAME}")
if(EXISTS "${MSKPFDIR}")
SET(${RESULT} ${MSKPFDIR} PARENT_SCOPE)
return()
endif()
endforeach()
endif()
endif()
endfunction()


function(MosekVersionFromPath RESVMAJOR RESVMINOR PATH)
execute_process(COMMAND "${PATH}/bin/mosek" "-v" RESULT_VARIABLE CMDRES OUTPUT_VARIABLE VERTEXT)
if (${CMDRES} EQUAL 0)
if (${VERTEXT} MATCHES "^MOSEK version [0-9]+[.][0-9]+[.][0-9]+")
string(REGEX REPLACE "^MOSEK version ([0-9]+)[.]([0-9]+).*" "\\1;\\2" MSKVER ${VERTEXT})

list(GET MSKVER 0 VMAJOR)
list(GET MSKVER 1 VMINOR)
set(${RESVMAJOR} "${VMAJOR}" PARENT_SCOPE)
set(${RESVMINOR} "${VMINOR}" PARENT_SCOPE)

#message(STATUS "mskver = ${VMAJOR}.${VMINOR} -> ${${RESVMAJOR}}.${${RESVMINOR}}")
#message(STATUS " ${RESVMAJOR} = ${${RESVMAJOR}}")
#message(STATUS " ${RESVMINOR} = ${${RESVMINOR}}")
return()
endif()
endif()
endfunction()


# Where to look for MOSEK:
# Standard procedure in Linux/OSX is to install MOSEK in the home directory, i.e.
# $HOME/mosek/X.Y/...
# Option 1. The user can specify when running CMake where the MOSEK platform directory is located, e.g.
# -DMOSEK_PLATFORM_DIR=$HOME/mosek/10.2/tools/platform/linux64x86/
# in which case no search is performed.
# Option 2. The user can specify MOSEK_ROOT when running cmake. MOSEK_ROOT is
# the directory where the root mosek/ tree is located.
# Option 3. Automatic search. We will then attempt to search in the default
# locations, and if that fails, assume it is installed in a system location.
# For option 2 and 3, the newest MOSEK version will be chosen if more are available.

if(MOSEK_PLATFORM_DIR)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use MOSEK_ROOT_DIR to follow convention

From CMake head repository:

$ git grep -n "ROOT_DIR\b" | wc -l
209
$ git grep -n "PLATFORM_DIR\b" | wc -l
0

# User defined platform dir directly
elseif(MOSEK_BASE)
# Look under for MOSEK_ROOT/X.Y
FindMosekPlatformInPath(MOSEK_PLATFORM_DIR "${MOSEK_BASE}")
if(NOT MOSEK_PLATFORM_DIR)
message(FATAL_ERROR " Could not locate MOSEK platform directory under ${MOSEK_BASE}")
endif()
endif()
if(NOT MOSEK_PLATFORM_DIR)
# Look in users home dir
if(EXISTS $ENV{HOME})
FindMosekPlatformInPath(MOSEK_PLATFORM_DIR "$ENV{HOME}")
endif()
endif()
if(NOT MOSEK_PLATFORM_DIR)
# Look in users home dir
if(EXISTS "$ENV{HOMEDRIVE}$ENV{HOMEPATH}")
FindMosekPlatformInPath(MOSEK_PLATFORM_DIR "$ENV{HOMEDRIVE}$ENV{HOMEPATH}")
endif()
endif()

if(NOT MOSEK_PLATFORM_DIR)
message(FATAL_ERROR " MOSEK_PLATFORM_DIR could not be detected")
else()
MosekVersionFromPath(MSKVMAJOR MSKVMINOR "${MOSEK_PLATFORM_DIR}")
if(MSKVMAJOR)
message(STATUS " MOSEK ${MSKVMAJOR}.${MSKVMINOR}: ${MOSEK_PLATFORM_DIR}")
set(MOSEK_PFDIR_FOUND TRUE)
else()
message(STATUS " Found MOSEK_PLATFORM_DIR, but failed to determine version")
endif()
endif()

if(MOSEK_PFDIR_FOUND AND MSKVMAJOR AND MSKVMINOR AND NOT TARGET mosek::mosek)
add_library(mosek::mosek UNKNOWN IMPORTED)

find_path(MOSEKINC mosek.h HINTS ${MOSEK_PLATFORM_DIR}/h)
if(WIN32)
find_library(LIBMOSEK mosek64_ HINTS ${MOSEK_PLATFORM_DIR}/bin)
else()
find_library(LIBMOSEK mosek64 HINTS ${MOSEK_PLATFORM_DIR}/bin)
endif()

if(LIBMOSEK)
set_target_properties(mosek::mosek PROPERTIES IMPORTED_LOCATION ${LIBMOSEK})
endif()

if (MOSEKINC)
target_include_directories(mosek::mosek INTERFACE "${MOSEKINC}")
endif()
elseif(NOT TARGET mosek::mosek)
add_library(mosek::mosek UNKNOWN IMPORTED)

find_path(MOSEKINC mosek.h)
find_library(LIBMOSEK mosek64)

if(LIBMOSEK)
set_target_properties(mosek::mosek PROPERTIES IMPORTED_LOCATION ${LIBMOSEK})
endif()

if (MOSEKINC)
target_include_directories(mosek::mosek INTERFACE "${MOSEKINC}")
endif()
endif()
8 changes: 8 additions & 0 deletions cmake/check_deps.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ if(USE_CPLEX AND NOT TARGET CPLEX::CPLEX)
message(FATAL_ERROR "Target CPLEX::CPLEX not available.")
endif()

# Check optional Dependencies
if(USE_MOSEK)
if (NOT TARGET mosek::mosek)
message(FATAL_ERROR "Target mosek::mosek not available.")
endif()
set(MOSEK_DEPS mosek::mosek)
endif()

# CXX Test
if(BUILD_TESTING)
if(NOT TARGET GTest::gtest_main)
Expand Down
4 changes: 4 additions & 0 deletions cmake/cpp.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ endif()
if(USE_CPLEX)
list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "USE_CPLEX")
endif()
if(USE_MOSEK)
list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "USE_MOSEK")
endif()

if(WIN32)
list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "__WIN32__")
Expand Down Expand Up @@ -565,6 +568,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC
${CPLEX_DEPS}
${GLPK_DEPS}
${HIGHS_DEPS}
${MOSEK_DEPS}
${PDLP_DEPS}
${SCIP_DEPS}
Threads::Threads)
Expand Down
4 changes: 4 additions & 0 deletions cmake/system_deps.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ if(USE_CPLEX)
find_package(CPLEX REQUIRED)
endif()

if(USE_MOSEK)
find_package(MOSEK REQUIRED)
endif()

# CXX Test
if(BUILD_TESTING AND NOT BUILD_googletest)
find_package(GTest REQUIRED)
Expand Down
1 change: 1 addition & 0 deletions examples/cpp/integer_programming.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ void RunAllExamples() {
RunIntegerProgrammingExample("GLPK");
RunIntegerProgrammingExample("CPLEX");
RunIntegerProgrammingExample("XPRESS");
RunIntegerProgrammingExample("MOSEK");
}
} // namespace operations_research

Expand Down
1 change: 1 addition & 0 deletions examples/cpp/linear_programming.cc
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ void RunAllExamples() {
RunLinearProgrammingExample("XPRESS_LP");
RunLinearProgrammingExample("PDLP");
RunLinearProgrammingExample("HIGHS");
RunLinearProgrammingExample("MOSEK_LP");
}
} // namespace operations_research

Expand Down
101 changes: 101 additions & 0 deletions examples/cpp/portfolio_1_basic.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include <fstream>
#include <iostream>
#include <ostream>
#include <limits>
#include <sstream>

#include "absl/log/check.h"
#include "absl/memory/memory.h"
#include "absl/status/statusor.h"
#include "ortools/base/init_google.h"
#include "ortools/math_opt/cpp/math_opt.h"

const double inf = std::numeric_limits<double>::infinity();
namespace math_opt = ::operations_research::math_opt;
using math_opt::LinearExpression;


/// Build and solve a basic Markowit portfolio problem.
///
/// # Arguments
/// - mu The vector of length n of expected returns on the assets
/// - GT A vector defining a m x n matrix in row-major format. It is the
/// facored co-variance matrix, i.e. the covariance matrix is Q = G*G^T
/// - x0 A vector of length n of initial investments
/// - w Initial wealth not invested yet.
/// - gamma The risk bound as a bound on the variance of the return of portfolio,
/// gamma > sqrt( xGG^Tx ).
absl::StatusOr<std::tuple<double,std::vector<double>>> basic_markowitz(
const std::vector<double> & mu,
const std::vector<double> & GT,
const std::vector<double> & x0,
double w,
double gamma,
math_opt::SolverType st) {

math_opt::Model model("portfolio_1_basic");
size_t n = mu.size();
size_t m = GT.size()/n;
std::vector<math_opt::Variable> x; x.reserve(n);
for (int i = 0; i < n; ++i)
x.push_back(model.AddContinuousVariable(0.0,inf,(std::ostringstream() << "x" << i).str()));

model.Maximize(LinearExpression::InnerProduct(x,mu));

double totalwealth = w; for (double v : x0) w += v;
model.AddLinearConstraint(LinearExpression::Sum(x) == totalwealth, "Budget");

std::vector<math_opt::LinearExpression> linear_to_norm;
for (int i = 0; i < m; ++i) {
linear_to_norm.push_back(LinearExpression::InnerProduct(absl::Span(GT.data()+m*i,m), x));
}
model.AddSecondOrderConeConstraint(linear_to_norm, gamma, "risk");

// Set parameters, e.g. turn on logging.
math_opt::SolveArguments args;
args.parameters.enable_output = true;

// Solve and ensure an optimal solution was found with no errors.
const absl::StatusOr<math_opt::SolveResult> result =
math_opt::Solve(model, st, args);
if (! result.ok())
return result.status();

double pobj = result->objective_value();
std::vector<double> res(n);
for (int i = 0; i < n; ++i)
res[i] = result->variable_values().at(x[i]);

return std::make_tuple(pobj,std::move(res));
}

int main(int argc, char ** argv) {
const int n = 8;
const double w = 59.0;
std::vector<double> mu = {0.07197349, 0.15518171, 0.17535435, 0.0898094 , 0.42895777, 0.39291844, 0.32170722, 0.18378628};
std::vector<double> x0 = {8.0, 5.0, 3.0, 5.0, 2.0, 9.0, 3.0, 6.0};
double gamma = 36;
std::vector<double> GT = {
0.30758, 0.12146, 0.11341, 0.11327, 0.17625, 0.11973, 0.10435, 0.10638,
0. , 0.25042, 0.09946, 0.09164, 0.06692, 0.08706, 0.09173, 0.08506,
0. , 0. , 0.19914, 0.05867, 0.06453, 0.07367, 0.06468, 0.01914,
0. , 0. , 0. , 0.20876, 0.04933, 0.03651, 0.09381, 0.07742,
0. , 0. , 0. , 0. , 0.36096, 0.12574, 0.10157, 0.0571 ,
0. , 0. , 0. , 0. , 0. , 0.21552, 0.05663, 0.06187,
0. , 0. , 0. , 0. , 0. , 0. , 0.22514, 0.03327,
0. , 0. , 0. , 0. , 0. , 0. , 0. , 0.2202 };


auto res = basic_markowitz(mu,GT,x0,w,gamma,math_opt::SolverType::kMosek);

if (! res.ok()) {
std::cerr << "Failed to solve problem: " << res.status() << std::endl;
}
else {
auto &[pobj,xx] = *res;
std::cout << "Primal Objective value: " << pobj << std::endl;
std::cout << "Solution values:" << std::endl;
for (int i = 0; i < xx.size(); ++i)
std::cout << " x["<< i << "] = " << xx[i] << std::endl;
}
}
6 changes: 6 additions & 0 deletions examples/cpp/strawberry_fields_with_column_generation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,12 @@ int main(int argc, char** argv) {
solver_type = operations_research::MPSolver::CPLEX_LINEAR_PROGRAMMING;
found = true;
}
#endif
#if defined(USE_MOSEK)
if (absl::GetFlag(FLAGS_colgen_solver) == "mosek") {
solver_type = operations_research::MPSolver::MOSEK_LINEAR_PROGRAMMING;
found = true;
}
#endif
if (!found) {
LOG(ERROR) << "Unknown solver " << absl::GetFlag(FLAGS_colgen_solver);
Expand Down
5 changes: 5 additions & 0 deletions examples/cpp/uncapacitated_facility_location.cc
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ void RunAllExamples(int32_t facilities, int32_t clients, double fix_cost) {
LOG(INFO) << "---- Integer programming example with CPLEX ----";
UncapacitatedFacilityLocation(facilities, clients, fix_cost,
MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING);
#endif // USE_CPLEX
#if defined(USE_MOSEK)
LOG(INFO) << "---- Integer programming example with MOSEK ----";
UncapacitatedFacilityLocation(facilities, clients, fix_cost,
MPSolver::MOSEK_MIXED_INTEGER_PROGRAMMING);
#endif // USE_CPLEX
LOG(INFO) << "---- Integer programming example with CP-SAT ----";
UncapacitatedFacilityLocation(facilities, clients, fix_cost,
Expand Down
Loading