├── .gitignore ├── .project ├── GurobiLink ├── PacletInfo.wl.in └── GurobiLink.wl ├── LICENSE ├── Tests └── GurobiLink_Basic.wlt ├── CONTRIBUTING.md ├── CSource ├── GurobiSolution.h ├── GurobiSolution.cpp └── GurobiLink.cpp ├── scripts └── re_build.xml ├── .clang-format ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | build* 4 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | GurobiLink 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /GurobiLink/PacletInfo.wl.in: -------------------------------------------------------------------------------- 1 | (* Built on ${BUILD_TIME} *) 2 | 3 | Paclet[ 4 | "Name" -> "${PROJECT_NAME}", 5 | "Version" -> "${PACLET_VERSION}", 6 | "WolframVersion" -> "${TARGET_WOLFRAM_VERSION}", 7 | "AutoUpdating" -> True, 8 | "SystemID" -> "${SYSTEMID}", 9 | "Qualifier" -> "${QUALIFIER}", 10 | "Extensions" -> 11 | { 12 | { 13 | "Kernel", 14 | "Context" -> "${PROJECT_NAME}`" 15 | } 16 | , 17 | { 18 | "LibraryLink", 19 | "Root" -> "LibraryResources" 20 | } 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Wolfram Research Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /Tests/GurobiLink_Basic.wlt: -------------------------------------------------------------------------------- 1 | 2 | Needs["GurobiLink`"] 3 | 4 | VerificationTest[ 5 | GurobiTestLicense[] 6 | , 7 | True 8 | , 9 | TestID->"GurobiLink_Basic-20210519-C6Z5H7" 10 | ] 11 | 12 | VerificationTest[ 13 | (data = GurobiDataCreate[]) // Head 14 | , 15 | GurobiData 16 | , 17 | TestID->"GurobiLink_Basic-20210519-C3D2B1" 18 | ] 19 | 20 | VerificationTest[ 21 | GurobiDataQ[data] 22 | , 23 | True 24 | , 25 | TestID->"GurobiLink_Basic-20210519-I3N7Q8" 26 | ] 27 | 28 | VerificationTest[ 29 | GurobiSetVariableTypesAndObjectiveVector[data, {}, {1.}] 30 | , 31 | 0 32 | , 33 | TestID->"GurobiLink_Basic-20210519-R4F6X5" 34 | ] 35 | 36 | VerificationTest[ 37 | GurobiAddLinearConstraints[data, SparseArray[{{1}}], ">", {0.}] 38 | , 39 | 0 40 | , 41 | TestID->"GurobiLink_Basic-20210519-F6I2F8" 42 | ] 43 | 44 | VerificationTest[ 45 | GurobiOptimize[data] 46 | , 47 | "Solved" 48 | , 49 | TestID->"GurobiLink_Basic-20210519-W0L9Y9" 50 | ] 51 | 52 | VerificationTest[ 53 | GurobiObjectiveValue[data] 54 | , 55 | 0. 56 | , 57 | TestID->"GurobiLink_Basic-20210519-D6R9J9" 58 | ] 59 | 60 | VerificationTest[ 61 | Gurobix[data] 62 | , 63 | {0.} 64 | , 65 | TestID->"GurobiLink_Basic-20210519-U2T2Z1" 66 | ] 67 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Wolfram® 2 | 3 | Thank you for taking the time to contribute to the [Wolfram Research](https://github.com/wolframresearch) repos on GitHub. 4 | 5 | ## Licensing of Contributions 6 | 7 | By contributing to Wolfram, you agree and affirm that: 8 | 9 | > Wolfram may release your contribution under the terms of the [MIT license](https://opensource.org/licenses/MIT); and 10 | 11 | > You have read and agreed to the [Developer Certificate of Origin](http://developercertificate.org/), version 1.1 or later. 12 | 13 | Please see [LICENSE](LICENSE) for licensing conditions pertaining 14 | to individual repositories. 15 | 16 | 17 | ## Bug reports 18 | 19 | ### Security Bugs 20 | 21 | Please **DO NOT** file a public issue regarding a security issue. 22 | Rather, send your report privately to security@wolfram.com. Security 23 | reports are appreciated and we will credit you for it. We do not offer 24 | a security bounty, but the forecast in your neighborhood will be cloudy 25 | with a chance of Wolfram schwag! 26 | 27 | ### General Bugs 28 | 29 | Please use the repository issues page to submit general bug issues. 30 | 31 | Please do not duplicate issues. 32 | 33 | Please do send a complete and well-written report to us. Note: **the 34 | thoroughness of your report will positively correlate to our willingness 35 | and ability to address it**. 36 | 37 | When reporting issues, always include: 38 | 39 | * Your version of *Mathematica*® or the Wolfram Language. 40 | * Your operating system. 41 | * Your version of Gurobi. -------------------------------------------------------------------------------- /CSource/GurobiSolution.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef GUROBI_SOLUTION_H 3 | #define GUROBI_SOLUTION_H 4 | 5 | #include 6 | #include 7 | 8 | #include "WolframLibrary.h" 9 | #include "WolframSparseLibrary.h" 10 | #include "gurobi_c.h" 11 | 12 | typedef struct GurobiData_struct 13 | { 14 | // GRBenv *env; 15 | GRBmodel* model; 16 | mint nvars; 17 | int optstatus; 18 | int error; 19 | 20 | } * GurobiData; 21 | 22 | typedef struct GurobiEnvironment_struct 23 | { 24 | GRBenv* env; 25 | int error; 26 | } * GurobiEnvironment; 27 | 28 | // Adds a data instance to the solution map if mode is 0 and deletes a solution with given id if mode is 1 29 | EXTERN_C DLLEXPORT void GurobiDataMap_manage(WolframLibraryData libData, mbool mode, mint id); 30 | 31 | // Deletes a data instance with given id; 32 | EXTERN_C DLLEXPORT int GurobiDataMap_delete(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument res); 33 | 34 | // Retrieves a data instance from the solution map with a specified solution id 35 | EXTERN_C DLLEXPORT GurobiData GurobiDataMap_get(mint id); 36 | 37 | // Adds an environment instance to the solution map if mode is 0 and deletes a solution with given id if mode is 1 38 | EXTERN_C DLLEXPORT void GurobiEnvironmentMap_manage(WolframLibraryData libData, mbool mode, mint id); 39 | 40 | // Deletes an environment instance with given id; 41 | EXTERN_C DLLEXPORT int GurobiEnvironmentMap_delete(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument res); 42 | 43 | // Retrieves a data instance from the solution map with a specified solution id 44 | EXTERN_C DLLEXPORT GurobiEnvironment GurobiEnvironmentMap_get(mint id); 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /scripts/re_build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | UseTab: Always 4 | TabWidth: 4 5 | --- 6 | Language: Cpp 7 | 8 | 9 | ###################### 10 | # The major settings 11 | ###################### 12 | 13 | # Always break before braces 14 | BreakBeforeBraces: Allman 15 | 16 | # 120 column limit (counting tabs as 4) 17 | ColumnLimit: 120 18 | 19 | 20 | ######################################### 21 | # Everything else in alphabetical order 22 | ######################################### 23 | 24 | 25 | # When breaking between function arguments, align subsequent lines with the first 26 | # argument on the first line 27 | AlignAfterOpenBracket: Align 28 | 29 | # When line-breaking into multiple lines that require escaping, line up the backslash 30 | # escapes as far left as possible. If the last line is longer than any of the previous 31 | # ones, this means that the backslash might actually be to the left of the last line's 32 | # right most point. 33 | AlignEscapedNewlines: Left 34 | 35 | # Only allow single-line function declarations in classes 36 | AllowShortFunctionsOnASingleLine: Inline 37 | 38 | # 'template <...>' always goes on its own line 39 | AlwaysBreakTemplateDeclarations: true 40 | 41 | # When line-breaking binary operators, put the operator on the new line, except for = 42 | BreakBeforeBinaryOperators: NonAssignment 43 | 44 | # Commas and colons in constructors begin new lines 45 | BreakBeforeInheritanceComma: true 46 | 47 | # We have some very long lines with -----, don't break those 48 | CommentPragmas: --------- 49 | 50 | # Macros which should be treated as for-each constructs... 51 | ForEachMacros: ['forString', 'forString_isASCII'] 52 | 53 | # Indent cases in switch statements 54 | IndentCaseLabels: true 55 | 56 | # If a function declaration is forced to break before the arguments (e.g., 57 | # between the return type and the function name), then indent after the break. 58 | IndentWrappedFunctionNames: true 59 | 60 | # Add comments after the closing brace of a namespace to indicate which 61 | # namespace is being closed 62 | FixNamespaceComments: true 63 | 64 | # This drops the comment breaking penality just enough so that comments will tend to 65 | # get broken rather than other kinds of line-breaking happening. We don't want, 66 | # for example, to break function calls after the open paren in order to get an entire 67 | # comment onto one line. 68 | PenaltyBreakComment: 30 69 | 70 | # We do want it to be able to go a bit over 120 characters...don't penalize too much 71 | PenaltyExcessCharacter: 3 72 | 73 | # We'll still break after the return type when absolutely necessary (and indent 74 | # per the IndentWrappedFunctionNames setting), but we prefer to keep the function 75 | # name with the return type and break after the opening paren. E.g., 76 | # returntype funcname( 77 | # arg1); 78 | PenaltyReturnTypeOnItsOwnLine: 500 79 | 80 | # Not sure if we want to sort includes, but since it could definitely break things 81 | # keep off for now. 82 | SortIncludes: false 83 | 84 | # For comments trailing code, put two spaces between the code and the comment. 85 | SpacesBeforeTrailingComments: 2 86 | 87 | # Use tabs for indentation and line continuation, but not for every whitespace everywhere 88 | UseTab: ForContinuationAndIndentation -------------------------------------------------------------------------------- /CSource/GurobiSolution.cpp: -------------------------------------------------------------------------------- 1 | #include "GurobiSolution.h" 2 | #include 3 | 4 | int GurobiData_initialize(GurobiData Gurobidata) 5 | { 6 | Gurobidata->model = nullptr; 7 | Gurobidata->nvars = 0; 8 | Gurobidata->optstatus = 0; 9 | Gurobidata->error = 0; 10 | return 0; 11 | } 12 | 13 | int GurobiEnvironment_initialize(GurobiEnvironment Gurobienvironment) 14 | { 15 | Gurobienvironment->env = nullptr; 16 | Gurobienvironment->error = 0; 17 | return 0; 18 | } 19 | 20 | GurobiData GurobiData_new() 21 | { 22 | GurobiData Gurobidata; 23 | Gurobidata = (GurobiData)malloc(sizeof(*Gurobidata)); 24 | GurobiData_initialize(Gurobidata); 25 | GurobiEnvironment Gurobienvironment = GurobiEnvironmentMap_get(1); 26 | /* Create new model with 0 variables, no problem info yet */ 27 | Gurobidata->error = 28 | GRBnewmodel(Gurobienvironment->env, &(Gurobidata->model), "Mo", 0, nullptr, nullptr, nullptr, nullptr, nullptr); 29 | return Gurobidata; 30 | } 31 | 32 | GurobiEnvironment GurobiEnvironment_new() 33 | { 34 | GurobiEnvironment Gurobienvironment; 35 | Gurobienvironment = (GurobiEnvironment)malloc(sizeof(*Gurobienvironment)); 36 | GurobiEnvironment_initialize(Gurobienvironment); 37 | Gurobienvironment->error = GRBemptyenv(&(Gurobienvironment->env)); 38 | if (!Gurobienvironment->error) 39 | { 40 | // Gurobienvironment->error = GRBsetstrparam(Gurobienvironment->env, "LogFile", "Mo.log"); 41 | Gurobienvironment->error = GRBstartenv(Gurobienvironment->env); 42 | } 43 | return Gurobienvironment; 44 | } 45 | 46 | int GurobiData_delete(GurobiData gurobidata) 47 | { 48 | GRBfreemodel(gurobidata->model); 49 | free(gurobidata); 50 | gurobidata = nullptr; 51 | return 0; 52 | } 53 | 54 | int GurobiEnvironment_delete(GurobiEnvironment Gurobienvironment) 55 | { 56 | GRBfreeenv(Gurobienvironment->env); 57 | free(Gurobienvironment); 58 | Gurobienvironment = nullptr; 59 | return 0; 60 | } 61 | 62 | static std::unordered_map GurobiDataMap; 63 | static std::unordered_map GurobiEnvironmentMap; 64 | 65 | EXTERN_C DLLEXPORT void GurobiDataMap_manage(WolframLibraryData libData, mbool mode, mint id) 66 | { 67 | if (mode == 0) 68 | { 69 | GurobiDataMap[id] = GurobiData_new(); 70 | } 71 | else if (GurobiDataMap[id] != nullptr) 72 | { 73 | GurobiData_delete(GurobiDataMap[id]); 74 | GurobiDataMap.erase(id); 75 | } 76 | } 77 | 78 | EXTERN_C DLLEXPORT void GurobiEnvironmentMap_manage(WolframLibraryData libData, mbool mode, mint id) 79 | { 80 | if (mode == 0) 81 | { 82 | GurobiEnvironmentMap[id] = GurobiEnvironment_new(); 83 | } 84 | else if (GurobiEnvironmentMap[id] != nullptr) 85 | { 86 | GurobiEnvironment_delete(GurobiEnvironmentMap[id]); 87 | GurobiEnvironmentMap.erase(id); 88 | } 89 | } 90 | 91 | EXTERN_C DLLEXPORT int GurobiDataMap_delete(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument res) 92 | { 93 | mint id; 94 | if (Argc != 1) 95 | { 96 | return LIBRARY_FUNCTION_ERROR; 97 | } 98 | id = MArgument_getInteger(Args[0]); 99 | if (GurobiDataMap[id] == nullptr) 100 | { 101 | return LIBRARY_FUNCTION_ERROR; 102 | } 103 | GurobiData_delete(GurobiDataMap[id]); 104 | return (*libData->releaseManagedLibraryExpression)("Gurobi_data_instance_manager", id); 105 | } 106 | 107 | EXTERN_C DLLEXPORT int GurobiEnvironmentMap_delete(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument res) 108 | { 109 | mint id; 110 | if (Argc != 1) 111 | { 112 | return LIBRARY_FUNCTION_ERROR; 113 | } 114 | id = MArgument_getInteger(Args[0]); 115 | if (GurobiEnvironmentMap[id] == nullptr) 116 | { 117 | return LIBRARY_FUNCTION_ERROR; 118 | } 119 | GurobiEnvironment_delete(GurobiEnvironmentMap[id]); 120 | return (*libData->releaseManagedLibraryExpression)("Gurobi_environment_instance_manager", id); 121 | } 122 | 123 | GurobiData GurobiDataMap_get(mint id) 124 | { 125 | return GurobiDataMap[id]; 126 | } 127 | 128 | GurobiEnvironment GurobiEnvironmentMap_get(mint id) 129 | { 130 | return GurobiEnvironmentMap[id]; 131 | } 132 | 133 | EXTERN_C DLLEXPORT int GurobiDataMap_retIDList(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument res) 134 | { 135 | mint i, num = GurobiDataMap.size(); 136 | mint dims[1]; 137 | MTensor resTen; 138 | 139 | dims[0] = num; 140 | int err = libData->MTensor_new(MType_Integer, 1, dims, &resTen); 141 | if (err) 142 | return err; 143 | mint* elems = libData->MTensor_getIntegerData(resTen); 144 | std::unordered_map::const_iterator iter = GurobiDataMap.begin(); 145 | std::unordered_map::const_iterator end = GurobiDataMap.end(); 146 | for (i = 0; i < num; i++) 147 | { 148 | elems[i] = iter->first; 149 | if (iter != end) 150 | { 151 | iter++; 152 | } 153 | } 154 | MArgument_setMTensor(res, resTen); 155 | return err; 156 | } 157 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | if(POLICY CMP0091) 4 | cmake_policy(SET CMP0091 NEW) 5 | endif() 6 | 7 | project(GurobiLink) 8 | 9 | string(TIMESTAMP BUILD_TIME "%d %b %Y at %H:%M") 10 | 11 | set(GUROBI_VERSION "9.1.2" CACHE STRING "Gurobi version") 12 | string(REGEX REPLACE "([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" GUROBI_MAJOR_VERSION ${GUROBI_VERSION}) 13 | string(REGEX REPLACE "[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" GUROBI_MINOR_VERSION ${GUROBI_VERSION}) 14 | string(REGEX REPLACE "\\." "" GUROBI_VER ${GUROBI_VERSION}) 15 | 16 | set(COMPONENTS_ROOT "/Components" CACHE PATH "Components location") 17 | mark_as_advanced(COMPONENTS_ROOT) 18 | 19 | set(INSTALL_PDB OFF CACHE BOOL "Include PDB file in paclet layout") 20 | 21 | set(PACLET_VERSION 1.0.3 CACHE STRING "Paclet version") 22 | set(TARGET_WOLFRAM_VERSION 12.3+ CACHE STRING "Target Wolfram version") 23 | 24 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 25 | set(CMAKE_INSTALL_PREFIX "${PROJECT_BINARY_DIR}/install" CACHE PATH "Install prefix" FORCE) 26 | endif() 27 | 28 | set(GUROBI_DLL 29 | ${CMAKE_SHARED_LIBRARY_PREFIX}gurobi${GUROBI_MAJOR_VERSION}${GUROBI_MINOR_VERSION}${CMAKE_SHARED_LIBRARY_SUFFIX} 30 | ) 31 | set(GUROBI_LIB ${GUROBI_DLL}) 32 | 33 | if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) 34 | message(FATAL_ERROR "Only 64-bit platforms are supported by ${PROJECT_NAME}") 35 | endif() 36 | 37 | if(WIN32) 38 | set(DEFAULT_SYSTEMID Windows-x86-64) 39 | set(DEFAULT_PLATFORM win64) 40 | set(QUALIFIER Win64) 41 | string(REPLACE "${CMAKE_SHARED_LIBRARY_SUFFIX}" "${CMAKE_IMPORT_LIBRARY_SUFFIX}" GUROBI_LIB ${GUROBI_DLL}) 42 | set(WOLFRAM_INSTALLATION "C:/Program Files/Wolfram Research/Mathematica/12.3" CACHE PATH "Wolfram installation") 43 | elseif(APPLE) 44 | if(CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64") 45 | set(DEFAULT_SYSTEMID MacOSX-x86-64) 46 | elseif(CMAKE_OSX_ARCHITECTURES MATCHES "^arm64e?$") 47 | set(DEFAULT_SYSTEMID MacOSX-ARM64) 48 | elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") 49 | set(DEFAULT_SYSTEMID MacOSX-x86-64) 50 | elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64") 51 | set(DEFAULT_SYSTEMID MacOSX-ARM64) 52 | endif() 53 | if(DEFAULT_SYSTEMID STREQUAL "MacOSX-x86-64") 54 | set(DEFAULT_PLATFORM mac64) 55 | else() 56 | set(DEFAULT_PLATFORM macos_universal2) 57 | if(GUROBI_VERSION VERSION_LESS 9.1) 58 | message(FATAL_ERROR "MacOSX-ARM64 is not supported by Gurobi ${GUROBI_VERSION}") 59 | endif() 60 | endif() 61 | set(QUALIFIER Mac64) 62 | set(CMAKE_BUILD_WITH_INSTALL_RPATH ON) 63 | set(WOLFRAM_INSTALLATION "/Applications/Mathematica.app/Contents" CACHE PATH "Wolfram installation") 64 | elseif(UNIX) 65 | if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm") 66 | message(FATAL_ERROR "Linux-ARM64 is not supported by ${PROJECT_NAME}") 67 | endif() 68 | set(DEFAULT_SYSTEMID Linux-x86-64) 69 | set(DEFAULT_PLATFORM linux64) 70 | set(QUALIFIER Lin64) 71 | set(CMAKE_INSTALL_RPATH "\$ORIGIN") 72 | set(WOLFRAM_INSTALLATION "/usr/local/Wolfram/Mathematica/12.3" CACHE PATH "Wolfram installation") 73 | endif() 74 | 75 | set(SYSTEMID ${DEFAULT_SYSTEMID} CACHE STRING "Machine SystemID") 76 | set(PLATFORM ${DEFAULT_PLATFORM} CACHE STRING "Gurobi platform string") 77 | 78 | find_path(DEFAULT_GUROBI_HOME 79 | include/gurobi_c.h 80 | PATHS 81 | ${COMPONENTS_ROOT}/Gurobi/${GUROBI_VERSION} 82 | ENV GUROBI_HOME 83 | C:/gurobi${GUROBI_VER} 84 | /opt/gurobi${GUROBI_VER} 85 | /Library/gurobi${GUROBI_VER} 86 | PATH_SUFFIXES ${PLATFORM} 87 | REQUIRED 88 | NO_DEFAULT_PATH 89 | ) 90 | 91 | set(GUROBI_HOME ${DEFAULT_GUROBI_HOME} CACHE PATH "Gurobi installation") 92 | 93 | if(WIN32) 94 | set(GUROBI_DLL_PATH "${GUROBI_HOME}/bin") 95 | else() 96 | set(GUROBI_DLL_PATH "${GUROBI_HOME}/lib") 97 | endif() 98 | set(GUROBI_LIBRARY_PATH "${GUROBI_HOME}/lib") 99 | set(GUROBI_INCLUDE_PATH "${GUROBI_HOME}/include") 100 | 101 | if(NOT WOLFRAM_INCLUDE_PATH) 102 | set(WOLFRAM_INCLUDE_PATH "${WOLFRAM_INSTALLATION}/SystemFiles/IncludeFiles/C") 103 | endif() 104 | 105 | message(STATUS "WOLFRAM_INCLUDE_PATH: ${WOLFRAM_INCLUDE_PATH}") 106 | message(STATUS "GUROBI_VERSION: ${GUROBI_VERSION}") 107 | message(STATUS "GUROBI_INCLUDE_PATH: ${GUROBI_INCLUDE_PATH}") 108 | message(STATUS "GUROBI_LIBRARY_PATH: ${GUROBI_LIBRARY_PATH}") 109 | 110 | find_file(WOLFRAM_LIBRARY_HEADER "WolframLibrary.h" PATHS ${WOLFRAM_INCLUDE_PATH} REQUIRED NO_DEFAULT_PATH) 111 | find_file(GUROBI_LIBRARY ${GUROBI_LIB} PATHS ${GUROBI_LIBRARY_PATH} REQUIRED NO_DEFAULT_PATH) 112 | 113 | set(GUROBILINK_SOURCES 114 | CSource/GurobiLink.cpp 115 | CSource/GurobiSolution.cpp 116 | ) 117 | 118 | set(PACLET_DESTINATION ${CMAKE_INSTALL_PREFIX}/${FULL} CACHE PATH "Full build destination") 119 | set(PACLET_LIBDIR ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/LibraryResources/${SYSTEMID}) 120 | 121 | add_library(${PROJECT_NAME} SHARED ${GUROBILINK_SOURCES}) 122 | set_target_properties(${PROJECT_NAME} PROPERTIES 123 | MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" 124 | PREFIX "" 125 | ) 126 | target_include_directories(${PROJECT_NAME} PRIVATE 127 | ${CMAKE_SOURCE_DIR}/CSource 128 | ${GUROBI_INCLUDE_PATH} 129 | ${WOLFRAM_INCLUDE_PATH} 130 | ) 131 | target_link_directories(${PROJECT_NAME} PRIVATE ${GUROBI_LIBRARY_PATH}) 132 | target_link_libraries(${PROJECT_NAME} PRIVATE ${GUROBI_LIB}) 133 | target_compile_definitions(${PROJECT_NAME} PRIVATE "$<$:DEBUG>") 134 | 135 | if(APPLE) 136 | execute_process( 137 | COMMAND otool -D "${GUROBI_LIBRARY_PATH}/${GUROBI_LIB}" 138 | OUTPUT_VARIABLE OTOOL_OUT 139 | ) 140 | string(REGEX REPLACE "^.*:.|.$" "" GUROBI_LIB_INSTALL_NAME ${OTOOL_OUT}) 141 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 142 | COMMAND install_name_tool -change ${GUROBI_LIB_INSTALL_NAME} @loader_path/${GUROBI_LIB} $ 143 | COMMENT "Setting ${PROJECT_NAME} rpath for ${GUROBI_LIB}" 144 | ) 145 | endif() 146 | 147 | install(TARGETS ${PROJECT_NAME} 148 | LIBRARY DESTINATION ${PACLET_LIBDIR} 149 | RUNTIME DESTINATION ${PACLET_LIBDIR} 150 | ) 151 | 152 | install(FILES 153 | ${GUROBI_DLL_PATH}/${GUROBI_DLL} 154 | DESTINATION ${PACLET_LIBDIR} 155 | ) 156 | 157 | if(INSTALL_PDB) 158 | install(FILES $ DESTINATION ${PACLET_LIBDIR} OPTIONAL) 159 | endif() 160 | 161 | configure_file( 162 | ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/PacletInfo.wl.in 163 | ${CMAKE_BINARY_DIR}/generated/PacletInfo.wl 164 | ) 165 | 166 | set(WL_SOURCES 167 | ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${PROJECT_NAME}.wl 168 | ${CMAKE_BINARY_DIR}/generated/PacletInfo.wl 169 | ) 170 | 171 | install(FILES ${WL_SOURCES} DESTINATION ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}) 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # GurobiLink for Wolfram Language 3 | 4 | GurobiLink is a package implemented in [Wolfram Language](https://www.wolfram.com/language/) and C++ 5 | using [LibraryLink](https://reference.wolfram.com/language/guide/LibraryLink.html) that provides an interface 6 | to the [Gurobi](https://www.gurobi.com/) numerical optimization solver. Gurobi can be used 7 | with the same ease as the built-in solvers and has excellent performance for particularly large or complicated 8 | (e.g. mixed-integer) models. It supports continuous and mixed-integer linear programming (LP), 9 | quadratic programming (QP), quadratically-constrained programming (QCP) and other classes of problems. 10 | 11 | The package requires a Gurobi [license](https://reference.wolfram.com/language/workflow/GetALicenseForGUROBI.html) 12 | (free academic licenses and evaluation licenses for commercial users are available, please see the 13 | [Gurobi documentation](https://www.gurobi.com/documentation/quickstart.html) for license installation instructions). 14 | It works with Gurobi version 9.0 and later and is bundled with Wolfram Language products (such as [Mathematica](https://www.wolfram.com/mathematica/) 15 | or [Wolfram Desktop](https://www.wolfram.com/desktop/)) version [12.2](https://writings.stephenwolfram.com/2020/12/launching-version-12-2-of-wolfram-language-mathematica-228-new-functions-and-much-more/) and later. 16 | 17 | GurobiLink makes the solver accessible as a plug-in through the Wolfram Language 18 | [optimization method framework](https://reference.wolfram.com/language/OptimizationMethodFramework/tutorial/OptimizationMethodFramework.html) 19 | and is used to implement [`Method -> "Gurobi"`](https://reference.wolfram.com/language/ref/method/GUROBI.html) in functons like [`NMinimize`](https://reference.wolfram.com/language/ref/NMinimize.html) or 20 | [`ConvexOptimization`](https://reference.wolfram.com/language/ref/ConvexOptimization.html). This allows seamless integration of the Wolfram Language modeling capabilities 21 | with the high performance of Gurobi, for example 22 | 23 | ``` 24 | In[1]:= ConvexOptimization[Total[x], Norm[x] <= 1, Element[x, Vectors[10, Integers]], 25 | Method -> "Gurobi"] // AbsoluteTiming 26 | 27 | Out[1]= {0.004534, {x -> {0, 0, -1, 0, 0, 0, 0, 0, 0, 0}}} 28 | ``` 29 | 30 | ## How to build 31 | 32 | The build requires [CMake](https://cmake.org/) 3.15 or later, a C++ compiler with support for C++11, as well 33 | as installations of Wolfram Language and the [Gurobi Optimizer](https://www.gurobi.com/downloads/gurobi-optimizer-eula/). 34 | 35 | The general steps for building the complete paclet are 36 | 37 | ``` 38 | git clone https://github.com/WolframResearch/GurobiLink.git GurobiLink 39 | cd GurobiLink 40 | mkdir build 41 | cd build 42 | cmake .. 43 | cmake --build . --target install 44 | ``` 45 | 46 | which will place the result by default in the `GurobiLink/build/install` directory. 47 | 48 | The typical CMake options required for building are 49 | 50 | * `WOLFRAM_INSTALLATION` -- Wolfram layout location, for example, `/usr/local/Wolfram/Mathematica/12.3` 51 | * `GUROBI_VERSION` -- version number, for example, `9.1.2` 52 | * `GUROBI_HOME` -- where Gurobi is installed, for example, `/opt/gurobi912/linux64` 53 | 54 | It may not always be necessary to specify all of these, since the build system provides reasonable defaults. 55 | 56 | The built paclet can then be enabled in a particular Wolfram Language session using 57 | [`PacletDirectoryLoad`](https://reference.wolfram.com/language/ref/PacletDirectoryLoad.html) 58 | or packed into a `.paclet` archive using 59 | [`CreatePacletArchive`](https://reference.wolfram.com/language/ref/CreatePacletArchive.html). 60 | 61 | The `.paclet` format is suitable for distribution or permanent installation using 62 | [`PacletInstall`](https://reference.wolfram.com/language/ref/PacletInstall.html). 63 | 64 | Some platform-specific examples follow: 65 | 66 | #### Windows 67 | 68 | Create a build directory, e.g. `C:\dev\git\Paclets\GurobiLink\build` 69 | ``` 70 | cd C:\dev\git\Paclets\GurobiLink 71 | mkdir build 72 | cd build 73 | ``` 74 | Configure the build using the 64-bit Visual Studio 2017 generator (see `cmake --help` for 75 | other possible CMake generators) 76 | ``` 77 | cmake -G "Visual Studio 15 2017 Win64" ^ 78 | -DWOLFRAM_INSTALLATION="C:\Program Files\Wolfram Research\Mathematica\12.3"^ 79 | -DGUROBI_VERSION=9.1.2^ 80 | -DGUROBI_HOME="C:\gurobi912\win64"^ 81 | -DINSTALL_PDB=ON ^ 82 | .. 83 | ``` 84 | The generated solution can be opened in the Visual Studio IDE using 85 | ``` 86 | cmake --open . 87 | ``` 88 | To build, select the appropriate configuration (for example, Debug), right-click 89 | the desired target (for example, INSTALL) and choose 'Build' from the context menu. 90 | 91 | Alternatively, to build the debug configuration from the command line use 92 | ``` 93 | cmake --build . --config Debug --target INSTALL 94 | ``` 95 | The development version of the paclet will be assembled in `build\install` and can be enabled in a 96 | Wolfram Language session by 97 | 98 | ``` 99 | PacletDirectoryLoad["C:\\dev\\git\\Paclets\\GurobiLink\\build\\install\\GurobiLink"]; 100 | ``` 101 | Sanity-check the build by running the included basic test file: 102 | 103 | ``` 104 | TestReport["C:\\dev\\git\\Paclets\\GurobiLink\\Tests\\GurobiLink_Basic.wlt"] 105 | ``` 106 | 107 | #### macOS 108 | 109 | Create a build directory, e.g. `~/git/Paclets/GurobiLink/build` 110 | ``` 111 | cd ~/git/Paclets/GurobiLink 112 | mkdir build 113 | cd build 114 | ``` 115 | Configure the build using the default Unix Makefiles generator (see `cmake --help` for 116 | other possible CMake generators) 117 | ``` 118 | cmake -DWOLFRAM_INSTALLATION="/Applications/Mathematica12.3.app/Contents" \ 119 | -DGUROBI_VERSION=9.1.2 \ 120 | -DGUROBI_HOME=/Library/gurobi912/mac64 \ 121 | .. 122 | ``` 123 | Build the debug configuration from the command line 124 | ``` 125 | cmake --build . --config Debug --target install 126 | ``` 127 | The development version of the paclet will be assembled in `build/install` and can be enabled in a Wolfram Language 128 | session by 129 | 130 | ``` 131 | PacletDirectoryLoad["~/git/Paclets/GurobiLink/build/install/GurobiLink"]; 132 | ``` 133 | Sanity-check the build by running the included basic test file: 134 | 135 | ``` 136 | TestReport["~/git/Paclets/GurobiLink/Tests/GurobiLink_Basic.wlt"] 137 | ``` 138 | 139 | #### Linux 140 | 141 | See the macOS instructions, except the build configuration step would be 142 | 143 | ``` 144 | cmake -DWOLFRAM_INSTALLATION="/usr/local/Wolfram/Mathematica/12.3" \ 145 | -DGUROBI_VERSION=9.1.2 \ 146 | -DGUROBI_HOME=/opt/gurobi912/linux64 \ 147 | .. 148 | ``` 149 | 150 | ### See also 151 | * [LICENSE](LICENSE) - GurobiLink license 152 | * [CONTRIBUTING.md](CONTRIBUTING.md) - Guidelines for contributing to GurobiLink 153 | -------------------------------------------------------------------------------- /GurobiLink/GurobiLink.wl: -------------------------------------------------------------------------------- 1 | 2 | BeginPackage["GurobiLink`"] 3 | 4 | $GurobiLinkLibrary::usage = "$GurobiLinkLibrary is the full path to the loaded GurobiLink library." 5 | $GurobiLinkDirectory::usage = "$GurobiLinkDirectory gives the location of the GurobiLink library." 6 | LoadGurobiLink::usage = "LoadGurobiLink[] loads the GurobiLink library." 7 | GurobiLink::usage = "GurobiLink is a symbol used for message heads, e.g. in messages triggered by Gurobi error codes." 8 | GurobiTestLicense::usage = "GurobiTestLicense[] returns True if a valid Gurobi license was found." 9 | GurobiSetVariableTypesAndObjectiveVector::usage = "GurobiSetVariableTypesAndObjectiveVector[data, vartypes, objvector] sets objective vector and variable types as a string with \"C\" at positions of continuous variables and \"I\" for integer variables." 10 | GurobiSetVariableTypesAndBoundsAndObjectiveVector::usage = "GurobiSetVariableTypesAndBoundsAndObjectiveVector[data, vartypes, lowerbounds, upperbounds, objvector] sets objective vector, vectors of lower and upper bounds for the variables and variable types as a string with \"C\" at positions of continuous variables and \"I\" for integer variables." 11 | GurobiAddQuadraticObjectiveMatrix::usage = "GurobiAddQuadraticObjectiveMatrix[data, Q] adds a quadratic objective matrix Q that must be sparse, symmetric and positive semi-definite." 12 | GurobiAddLinearConstraint::usage = "GurobiAddLinearConstraint[data, avec, sense, bnum] adds a constraint avec.x sense bnum, where sense is \">\", \"<\" or \"=\"." 13 | GurobiAddLinearConstraints::usage = "GurobiAddLinearConstraints[data, amat, sense, bvec] adds a constraint amat.x sense bvec, where sense is \">\", \"<\" or \"=\"." 14 | GurobiAddLinearConstraintIndices::usage = "GurobiAddLinearConstraintIndices[data, nzindices, nzvalues, sense, bnum] add a constraint avec.x sense bnum, where sense is \">\", \"<\" or \"=\" and avec is described by its nonzero values and their positions as {nzindices, nzvalues}." 15 | GurobiAddQuadraticConstraint::usage = "GurobiAddQuadraticConstraint[data, Qmat, qvec, sense, rhs] adds a constraint 1/2 x^T.Q.x + q.x sense b, where sense is \">\", \"<\" or \"=\"." 16 | GurobiAddQuadraticConstraintIndices::usage = "GurobiAddQuadraticConstraintIndices[data, linind, linvals, quadrow, quadcol, quadvals, sense, rhs] adds a constraint 1/2 x^T.Q.x + q.x sense b, where sense is \">\", \"<\" or \"=\", Q is represented by its nonzero values and their positions {quadrow, quadcol, quadvals} and q is represented by its non-zero values and their positioons {linind, linvals}." 17 | GurobiAddSOCMembershipConstraint::usage = "GurobiAddSOCMembershipConstraint[data, ind]" 18 | GurobiAddSOCAffineConstraint::usage = "GurobiAddSOCAffineConstraint[data, A, b] adds the constaraint VectorGreaterEqual[{Ax + b, 0}, {\"NormCone\", n}]" 19 | 20 | GurobiOptimize::usage = "GurobiOptimize[data]" 21 | 22 | GurobiStatusValues::usage ="GurobiStatusValues[data]" 23 | Gurobix::usage ="Gurobix[data]" 24 | GurobiObjectiveValue::usage = "GurobiObjectiveValue[data]" 25 | GurobiSlack::usage = "GurobiSlack[data]" 26 | 27 | GurobiData::usage = "GurobiData[id] represents an instance of an GurobiData expression created by GurobiDataCreate." 28 | GurobiDataID::usage = "GurobiDataID[data] gives the instance id of an GurobiData expression data." 29 | GurobiDataQ::usage = "GurobiDataQ[data] gives True if expr represents an active instance of an GurobiData object." 30 | GurobiDataCreate::usage = "data = GurobiDataCreate[] creates an instance of an GurobiData expression." 31 | GurobiDataExpressions::ussage = "GurobiDataExpressions[] shows all active GurobiData expression instances." 32 | GurobiDataDelete::usage = "GurobiDataDelete[data] removes an instance of an GurobiData expression, freeing up memory." 33 | 34 | GurobiEnvironmentCreate::usage = "env = GurobiEnvironmentCreate[] creates Gurobi environment." 35 | GurobiEnvironmentDelete::usage = "GurobiEnvironmentDelete[env] deletes Gurobi environment." 36 | 37 | Begin["`Private`"] 38 | (* Implementation of the package *) 39 | 40 | $GurobiLinkDirectory = DirectoryName[$InputFileName]; 41 | $targetDir = FileNameJoin[{$GurobiLinkDirectory, "LibraryResources", $SystemID}]; 42 | 43 | $GurobiLinkLibrary = Block[{$LibraryPath = $targetDir}, FindLibrary["GurobiLink"]]; 44 | 45 | $GurobiLibrariesToPreload = Switch[$SystemID, 46 | "Windows-x86-64", 47 | FileNames["gurobi*.dll", $targetDir], 48 | _, 49 | {} 50 | ] 51 | 52 | (* 53 | Load all the functions from the GurobiLink library 54 | *) 55 | 56 | dPrint = Optimization`Debug`OptimizationDebugPrint; 57 | pReset = Optimization`Debug`OptimizationProfileReset; 58 | pPrint = Optimization`Debug`OptimizationProfilePrint; 59 | 60 | needInitialization = True; 61 | 62 | $GurobiInfinity = 1.*10^30; 63 | 64 | 65 | LoadGurobiLink[] := 66 | Block[{$LibraryPath = $targetDir}, 67 | Map[LibraryLoad, $GurobiLibrariesToPreload]; 68 | GurobiCheckLicense0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_CheckLicense", {Integer}, Integer]; 69 | GurobiCheckModel0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_CheckModel", {Integer}, Integer]; 70 | GurobiSetVariableTypesAndObjectiveVector0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_SetVariableTypesAndObjectiveVector", {Integer, {Integer, 1}, {Real, 1, "Constant"}}, Integer]; 71 | GurobiSetVariableTypesAndBoundsAndObjectiveVector0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_SetVariableTypesAndBoundsAndObjectiveVector", {Integer, UTF8String, {Real, 1, "Constant"}, {Real, 1, "Constant"}, {Real, 1, "Constant"}}, Integer]; 72 | GurobiAddQuadraticObjectiveMatrix0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_AddQuadraticObjectiveMatrix", {Integer, {LibraryDataType[SparseArray, Real, 2], "Constant"}}, Integer]; 73 | GurobiAddLinearConstraint0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_AddLinearConstraint", {Integer, {Integer, 1}, {Real, 1, "Constant"}, UTF8String, Real}, Integer]; 74 | GurobiAddLinearConstraint1 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_AddLinearConstraint1", {Integer, {LibraryDataType[SparseArray, Real, 1], "Constant"}, UTF8String, Real}, Integer]; 75 | GurobiAddLinearConstraints0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_AddLinearConstraints", {Integer, {LibraryDataType[SparseArray, Real, 2], "Constant"}, UTF8String, {Real, 1, "Constant"}}, Integer]; 76 | GurobiAddQuadraticConstraint0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_AddQuadraticConstraint", {Integer, {Integer, 1}, {Real, 1, "Constant"}, {Integer, 1}, {Integer, 1}, {Real, 1, "Constant"}, UTF8String, Real}, Integer]; 77 | GurobiAddQuadraticConstraint1 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_AddQuadraticConstraint1", {Integer, {LibraryDataType[SparseArray, Real, 2], "Constant"}, {LibraryDataType[SparseArray, Real, 1], "Constant"}, UTF8String, Real}, Integer]; 78 | GurobiSetParameters0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_SetParameters", {Integer, Integer, Real, Integer, Integer}, Integer]; 79 | GurobiSetStartingPoint0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_SetStartingPoint", {Integer, {Real, 1, "Constant"}}, Integer]; 80 | 81 | GurobiOptimize0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_OptimizeModel", {Integer}, Integer]; 82 | 83 | GurobiStatusValue0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_GetStatusValue", {Integer}, Integer]; 84 | GurobiObjectiveValue0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_GetObjectiveValue", {Integer}, Real]; 85 | Gurobix0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_Getx", {Integer}, {Real, 1}]; 86 | GurobiSlack0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiData_GetSlack", {Integer}, {Real, 1}]; 87 | 88 | GurobiDataDelete0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiDataMap_delete", {Integer}, Integer]; 89 | GurobiDataIDList = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiDataMap_retIDList", {}, {Integer, 1}]; 90 | GurobiEnvironmentDelete0 = LibraryFunctionLoad[$GurobiLinkLibrary, "GurobiEnvironmentMap_delete", {Integer}, Integer]; 91 | needInitialization = False; 92 | ] 93 | 94 | LoadGurobiLink[] 95 | 96 | (* GurobiEnvironment related *) 97 | GurobiEnvironmentID[e_GurobiEnvironment] := ManagedLibraryExpressionID[e, "Gurobi_environment_instance_manager"]; 98 | 99 | GurobiEnvironmentQ[e_GurobiEnvironment] := ManagedLibraryExpressionQ[e, "Gurobi_environment_instance_manager"]; 100 | GurobiEnvironmentQ[_] := False; 101 | 102 | testGurobiEnvironment[][e_] := testGurobiEnvironment[GurobiEnvironment][e]; 103 | testGurobiEnvironment[mhead_Symbol][e_] := 104 | If[TrueQ[GurobiEnvironmentQ[e]], 105 | True, 106 | Message[MessageName[mhead, "gurobienvinst"], e]; False 107 | ]; 108 | testGurobiEnvironment[_][e_] := TrueQ[GurobiEnvironmentQ[e]]; 109 | 110 | General::gurobienvinst = "`1` does not represent an active GurobiEnvironment object."; 111 | 112 | GurobiEnvironmentCreate[] := 113 | Module[{}, 114 | If[needInitialization, LoadGurobiLink[]]; 115 | CreateManagedLibraryExpression["Gurobi_environment_instance_manager", GurobiEnvironment] 116 | ]; 117 | 118 | GurobiEnvironmentDelete[GurobiEnvironment[id_]?(testGurobiEnvironment[GurobiEnvironmentDelete])] := GurobiEnvironmentDelete0[id]; 119 | 120 | (* Create one environment for the entire session, if not already created *) 121 | If[!GurobiEnvironmentQ[env], 122 | env = GurobiEnvironmentCreate[]; 123 | If[GurobiEnvironmentQ[env], 124 | dPrint[5, env, " was created"], 125 | dPrint[5, "Failed to create Gurobi environment"] 126 | ]; 127 | , 128 | dPrint[5, "Gurobi environment is still ", env]; 129 | ]; 130 | 131 | (* Check License *) 132 | 133 | GurobiCheckLicense[GurobiEnvironment[id_]?(testGurobiEnvironment[GurobiCheckLicense])]:= 134 | Module[{error}, 135 | dPrint[1, "Checking for Gurobi license..."]; 136 | error = GurobiCheckLicense0[id]; 137 | If[error === 0, 138 | dPrint[1, "...................license found."]; 139 | True 140 | , 141 | dPrint[1, "Creating Gurobi environment failed with error ", error]; 142 | If[error === 10009, 143 | Message[GurobiLink::license]; 144 | False, 145 | $Failed 146 | ] 147 | ] 148 | ] 149 | 150 | GurobiLink::license = "Cannot find a valid Gurobi license for version 9.0 or greater." 151 | 152 | (* Options *) 153 | Options[GurobiOptimize] = {Method->Automatic, MaxIterations->Automatic, Tolerance->Automatic, 154 | "NonConvex"->Automatic, "StartingPoint"->Automatic, "Caller"-> Automatic, "Threads"->Automatic, 155 | PerformanceGoal:>$PerformanceGoal, WorkingPrecision->MachinePrecision} 156 | 157 | (* Check for license, just once *) 158 | GurobiTestLicense[] := 159 | (GurobiTestLicense[] = TrueQ[GurobiCheckLicense[env]]) 160 | 161 | licenseData = <|"TestFunction"->GurobiTestLicense, "WorkflowName"->"Gurobi"|>; 162 | 163 | (* Register optimization methods *) 164 | 165 | (* Method "Gurobi1" is only for condstraints suppoted by Gurobi -- linear, quadratic and second order cone(SOC) membership. 166 | For general affine SOC constraints 'Ax+b in K', unless A is diagonal, use method "Gurobi", 167 | or try Method -> {"Gurobi1", "NonConvex" -> 2} *) 168 | Optimization`MethodFramework`RegisterOptimizationMethod["Gurobi1", 169 | Association[ 170 | "SolveFunction" -> GurobiSolve1, 171 | "ObjectiveSupport" -> "Quadratic", 172 | "ConstraintSupport" -> Association[{"EqualityConstraint" -> "Affine", "NonNegativeCone" -> "Affine", 173 | "NormCone" -> "Affine", "QuadraticConstraint" -> "Affine"}], 174 | "MixedIntegerSupport" -> True, 175 | "ExtraOptions" -> Optimization`MethodFramework`GetExtraOptions[Options[GurobiOptimize]], 176 | "License"->licenseData 177 | ] 178 | ]; 179 | (* Method "Gurobi" solves problems with linear or quadratic objective and 180 | linear, quadratic and second order cone(SOC) affine constraints. 181 | In order to handle affine SOC constraints it adds new variables y = A.x+b *) 182 | Optimization`MethodFramework`RegisterOptimizationMethod["Gurobi", 183 | Association[ 184 | "SolveFunction" -> GurobiSolve2, 185 | "ObjectiveSupport" -> "Quadratic", 186 | "ConstraintSupport" -> Association[{"EqualityConstraint"->"Affine", "NonNegativeCone"->"Affine", 187 | "NormCone"->"Membership", "QuadraticConstraint" -> "Affine"}], 188 | "MixedIntegerSupport" -> True, 189 | "ExtraOptions" -> Optimization`MethodFramework`GetExtraOptions[Options[GurobiOptimize]], 190 | "License"->licenseData 191 | ] 192 | ]; 193 | 194 | Optimization`MethodFramework`RegisterOptimizationMethodAliases["Gurobi", {"GUROBI"}]; 195 | 196 | (* GurobiData expression (GurobiSolMap) related: *) 197 | 198 | GurobiDataID[e_GurobiData] := ManagedLibraryExpressionID[e, "Gurobi_data_instance_manager"]; 199 | 200 | GurobiDataQ[e_GurobiData] := ManagedLibraryExpressionQ[e, "Gurobi_data_instance_manager"]; 201 | GurobiDataQ[_] := False; 202 | 203 | testGurobiData[][e_] := testGurobiData[GurobiData][e]; 204 | testGurobiData[mhead_Symbol][e_] := 205 | If[TrueQ[GurobiDataQ[e]], 206 | True, 207 | Message[MessageName[mhead, "Gurobiinst"], e]; False 208 | ]; 209 | testGurobiData[_][e_] := TrueQ[GurobiDataQ[e]]; 210 | 211 | General::gurobiinst = "`1` does not represent an active GurobiData object."; 212 | 213 | GurobiDataCreate[] := 214 | Module[{}, 215 | If[needInitialization, LoadGurobiLink[]]; 216 | CreateManagedLibraryExpression["Gurobi_data_instance_manager", GurobiData] 217 | ]; 218 | 219 | GurobiDataDelete[GurobiData[id_]?(testGurobiData[GurobiDataDelete])] := GurobiDataDelete0[id]; 220 | 221 | GurobiDataDelete[l:{_GurobiData..}] := GurobiDataDelete /@ l; 222 | 223 | GurobiDataExpressions[] := 224 | Module[{list}, 225 | If[needInitialization, LoadGurobi[]]; 226 | list = GurobiDataIDList[]; 227 | If[!ListQ[list], 228 | $Failed, 229 | Map[GurobiData, list]] 230 | ] 231 | 232 | GurobiCheckModel[GurobiData[id_]?(testGurobiData[GurobiCheckModel])]:= 233 | Module[{error}, 234 | error = GurobiCheckModel0[id]; 235 | If[!SameQ[error, 0], dPrint[1, "Model creation failed with error ", error]; 236 | If[SameQ[error, 10001], Message[GurobiLink::nomem]];]; 237 | error 238 | ]; 239 | 240 | (* Functions to set up the problem *) 241 | 242 | GurobiSetVariableTypesAndObjectiveVector[GurobiData[id_]?(testGurobiData[GurobiSetVariableTypesAndObjectiveVector]), intvars_, objvector_]:= 243 | Module[{}, 244 | dPrint[5, "Setting variable types and objective vector..."]; 245 | GurobiSetVariableTypesAndObjectiveVector0[id, intvars, Normal[objvector]] 246 | ]; 247 | 248 | GurobiSetVariableTypesAndBoundsAndObjectiveVector[GurobiData[id_]?(testGurobiData[GurobiSetVariableTypesAndBoundsAndObjectiveVector]), vartypes_, lb_, ub_, objvector_]:= 249 | Module[{}, 250 | GurobiSetVariableTypesAndBoundsAndObjectiveVectort0[id, vartypes, lb, ub, objvector] 251 | ]; 252 | 253 | GurobiAddQuadraticObjectiveMatrix[GurobiData[id_]?(testGurobiData[GurobiAddQuadraticObjectiveMatrix]), Qmat_SparseArray]:= 254 | Module[{QGmat = Qmat/2}, 255 | dPrint[5, " In GurobiAddQuadraticObjectiveMatrix"]; 256 | (*the Gurobi Q matrix absorbs the 1/2 coeefficient*) 257 | dPrint[5, "Adding quadratic objective matrix ", QGmat]; 258 | GurobiAddQuadraticObjectiveMatrix0[id, QGmat] 259 | ]; 260 | 261 | GurobiAddLinearConstraintIndices[GurobiData[id_]?(testGurobiData[GurobiAddLinearConstraintIndices]), indices_, values_, sense_, rhs_]:= 262 | Module[{}, 263 | GurobiAddLinearConstraint0[id, indices, values, sense, rhs] 264 | ]; 265 | 266 | GurobiAddLinearConstraint[GurobiData[id_]?(testGurobiData[GurobiAddLinearConstraint]), vector_SparseArray, sense_, rhs_]:= 267 | Module[{}, 268 | GurobiAddLinearConstraint1[id, vector, sense, rhs] 269 | ]; 270 | 271 | GurobiAddLinearConstraints[GurobiData[id_]?(testGurobiData[GurobiAddLinearConstraints]), mat_SparseArray, sense_, rhs_]:= 272 | Module[{}, 273 | dPrint[5, xGurobiAddLinearConstraints0[id, mat, sense, rhs]]; 274 | GurobiAddLinearConstraints0[id, SparseArray[mat], sense, Normal[rhs]] 275 | ]; 276 | 277 | GurobiAddQuadraticConstraintIndices[GurobiData[id_]?(testGurobiData[GurobiAddQuadraticConstraintIndices]), linind_, linvals_, quadrow_, quadcol_, quadvals_, sense_, rhs_] := 278 | Module[{}, 279 | GurobiAddQuadraticConstraint0[id, linind, linvals, quadrow, quadcol, quadvals, sense, rhs] 280 | ] 281 | 282 | GurobiAddQuadraticConstraint[GurobiData[id_]?(testGurobiData[GurobiAddQuadraticOptimizationConstraint]), Q_SparseArray, q_SparseArray, sense_, b_] := 283 | Module[{}, 284 | (* x^T.Q.x + q.x <= b *) 285 | GurobiAddQuadraticConstraint1[id, Q, q, sense, b] 286 | ] 287 | 288 | GurobiAddSOCMembershipConstraint[GurobiData[id_]?(testGurobiData[GurobiAddSOCMembershipConstraint]), ind_]:= 289 | Module[{n, linind, linvals, quadrow, quadcol, quadvals, sense, rhs}, 290 | (* x1^2+...+x(n-1)^2-xn^2 <= 0, xn>=0 *) 291 | dPrint[3, "In GurobiAddSOCMembershipConstraint"]; 292 | n = Length[ind]; 293 | dPrint[5, xGurobiAddLinearConstraint0[id, {ind[[-1]]}, {1}, ">", 0]]; 294 | GurobiAddLinearConstraint0[id, {ind[[-1]]}, {1}, ">", 0]; 295 | linind = {}; 296 | linvals = {}; 297 | quadrow = ind; 298 | quadcol = ind; 299 | quadvals = Append[ConstantArray[1, n-1], -1]; 300 | sense = "<"; 301 | rhs = 0; 302 | dPrint[5, xGurobiAddQuadraticConstraint0[id, linind, linvals, quadrow, quadcol, quadvals, sense, rhs]]; 303 | GurobiAddQuadraticConstraint0[id, linind, linvals, quadrow, quadcol, quadvals, sense, rhs] 304 | ] 305 | 306 | GurobiAddSOCAffineConstraint[GurobiData[id_]?(testGurobiData[GurobiAddSOCAffineConstraint]), A_, b_]:= 307 | Module[{n, Q, q, b1, psd}, 308 | (* This is only accepted for SOC constraint for diagonal A, with non-diagonal A 309 | one may try to set the "NonConvex" parameter to 2 but this should not be very efficient *) 310 | dPrint[3, "In GurobiAddSOCAffineConstraint"]; 311 | (*VectorGreaterEqual[{Ax + b, 0}, {"NormCone", n}] --> x^T.Q.x + q.x <= b *) 312 | (* 313 | an.x + bn >= 0, 314 | ||{a1.x + b1, ..., a (n - 1).x + b (n - 1)}|| <= an.x + bn 315 | Q = a1 a1^T + ... + a (n - 1).a (n - 1)^T - an^2 316 | b = -(b1^2 + ... + b (n - 1)^2 - bn^2) 317 | q = 2 (b1*a1 + ... b (n - 1)*a (n - 1) - bn*an) 318 | *) 319 | n = Length[b]; 320 | dPrint[5, xGurobiAddLinearConstraint1[id, SparseArray[A[[n]]], ">", -b[[n]]]]; 321 | GurobiAddLinearConstraint1[id, SparseArray[A[[n]]], ">", -b[[n]]]; 322 | Q = Sum[Transpose[{A[[i]]}].{A[[i]]}, {i, 1, n-1}] - Transpose[{A[[n]]}].{A[[n]]}; 323 | b1 = -Sum[b[[i]]^2, {i, 1, n-1}] + b[[n]]^2; 324 | q = 2*(Sum[b[[i]]*A[[i]], {i, 1, n-1}] - b[[n]]*A[[n]]); 325 | If[!DiagonalMatrixQ[A] && !PositiveSemidefiniteMatrixQ[Q], 326 | dPrint[3, "Matrix Q is not positive semidefinite and matrix A is not diagonal."]; 327 | Message[GurobiLink::badmethod]; 328 | ]; 329 | dPrint[5, xGurobiAddQuadraticConstraint1[id, SparseArray[Q], SparseArray[q], "<", b1]]; 330 | GurobiAddQuadraticConstraint1[id, SparseArray[Q], SparseArray[q], "<", b1] 331 | ] 332 | 333 | GurobiLink::badmethod = "Method \"Gurobi1\" is not suitable for general affine SOC constraints. \ 334 | Use method \"Gurobi\"." 335 | 336 | GurobiSetParameters[GurobiData[id_]?(testGurobiData[GurobiSetParameters]), maxit_, tol_, nonconvex_, threads_]:= 337 | Module[{error}, 338 | error = GurobiSetParameters0[id, maxit, tol, nonconvex, threads] 339 | ] 340 | 341 | GurobiSetStartingPoint[GurobiData[id_]?(testGurobiData[GurobiSetStartingPoint]), startpt_]:= 342 | Module[{error}, 343 | error = GurobiSetStartingPoint0[id, N[startpt]]; 344 | ] 345 | 346 | (* Function to solve the problem *) 347 | 348 | INTMAX = 2^31-1; 349 | 350 | GurobiOptimize[GurobiData[id_]?(testGurobiData[GurobiOptimize]), OptionsPattern[GurobiOptimize]] := 351 | Module[{tol, maxiter, nonconvex, mhead, verbose, status, error, startpt, threads, data=GurobiData[id]}, 352 | dPrint[3, "In GurobiOptimize"]; 353 | tol = OptionValue[Tolerance]; 354 | maxiter = OptionValue[MaxIterations]; 355 | nonconvex = OptionValue["NonConvex"]; 356 | mhead = OptionValue["Caller"]; 357 | startpt = OptionValue["StartingPoint"]; 358 | threads = OptionValue["Threads"]; 359 | If[SameQ[tol, Automatic], tol = 10.^-6, tol = N[tol]]; 360 | If[SameQ[maxiter, Automatic], maxiter = 1000, If[SameQ[maxiter, Infinity], maxiter = MAXINT]]; 361 | If[TrueQ[nonconvex] || SameQ[nonconvex, 2], nonconvex = 2, nonconvex = 1]; 362 | If[SameQ[mhead, Automatic], mhead = ConicOptimization]; 363 | If[!SameQ[startpt, Automatic], 364 | dPrint[3, "Setting starting point ", startpt]; 365 | error = GurobiSetStartingPoint0[id, N[startpt]]; 366 | ]; 367 | If[SameQ[threads, Automatic], 368 | threads = Lookup[Lookup[SystemOptions["ParallelOptions"], "ParallelOptions"], "ParallelThreadNumber", 0] 369 | ]; 370 | 371 | dPrint[3, "Setting parameters {maxiter, tol, nonconvex} -> ", {maxiter, tol, nonconvex}]; 372 | error = GurobiSetParameters0[id, maxiter, tol, nonconvex, threads]; 373 | 374 | dPrint[1, "Solving with Gurobi..."]; 375 | pReset[5]; 376 | error = GurobiOptimize0[id]; 377 | pPrint[5, "Gurobi solver"]; 378 | dPrint[1, "Gurobi solver error: ", error]; 379 | status = GurobiStatusValue0[id]; 380 | dPrint["status: ", status]; 381 | status = GurobiStringStatus[data]; 382 | dPrint[1, "string status: ", status]; 383 | ReportError[error, status, mhead, maxiter]; 384 | If[error=!=0 || !StringQ[status], Return[$Failed]]; 385 | status 386 | ]; 387 | 388 | (* https://www.gurobi.com/documentation/9.0/refman/optimization_status_codes.html *) 389 | GurobiStringStatus[data_] := 390 | Switch[GurobiStatusValue[data], 391 | 1, "NoSolutionAvailable", 392 | 2, "Solved", 393 | 3, "PrimalInfeasible", 394 | 4, "DualInfeasible", 395 | 5, "Unbounded", 396 | 6, "ObjectiveCutoffReached", 397 | 7, "MaxIterationsReached", 398 | 8, "NodeLimitReached", 399 | 9, "MaxCPUTimeReached", 400 | 10, "SolutionLimit", 401 | 11, "Interrupted", 402 | 12, "NumericalError", 403 | 13, "Solved/Inaccurate", 404 | 14, "AsynchronousOptimizationInProgress", 405 | 15, "ObjectiveLimitReached", 406 | _, "Unknown" 407 | ] 408 | 409 | ReportError[error_, status_, mhead_, maxiter_] := ( 410 | Switch[error, 411 | 10020, Message[mhead::npsd] 412 | ]; 413 | Switch[status, 414 | "Solved/Inaccurate", Message[mhead::inac, maxiter], 415 | "MaxIterationsReached", Message[mhead::maxit, maxiter], 416 | "PrimalInfeasible", Message[mhead::nsolc], 417 | "DualInfeasible", Message[mhead::dinfeas], 418 | "Interrupted", Message[mhead::userstop], 419 | "MaxCPUTimeReached", Message[mhead::maxcput] 420 | ];) 421 | 422 | General::npsd = "The objective matrix is not positive semi-definite." 423 | 424 | (* Selectors *) 425 | 426 | GurobiStatusValue[GurobiData[id_]?(testGurobiData[GurobiStatusValues])] := GurobiStatusValue0[id]; 427 | 428 | Gurobix[GurobiData[id_]?(testGurobiData[Gurobix])] := Gurobix0[id]; 429 | 430 | GurobiObjectiveValue[GurobiData[id_]?(testGurobiData[GurobiObjectiveValue])] := GurobiObjectiveValue0[id]; 431 | 432 | GurobiSlack[GurobiData[id_]?(testGurobiData[GurobiSlack])] := GurobiSlack0[id]; 433 | 434 | (* Determine the NonConvex setting for the Gurobi optimizer: 435 | ttps://www.gurobi.com/documentation/9.0/refman/nonconvex.html*) 436 | 437 | getNonConvexSetting[problemData_] := 438 | Module[{convexity = problemData["Convexity"]}, 439 | dPrint[5, "convexity: ", convexity]; 440 | If[MatchQ[convexity, "Convex"], 1, 2] 441 | ] 442 | 443 | (* Method functions *) 444 | 445 | (* Solve function for method Gurobi1 *) 446 | GurobiSolve1[problemData_, pmopts___] := 447 | Module[{a, b, c, d, q, data, objvec, objmat, ncons, coefficients, coneSpecifications, lpos, intvars, nonconvex, error, status}, 448 | pReset[5]; 449 | dPrint[1, "In GurobiSolve1"]; 450 | dPrint[3, "pmopts: ", pmopts]; 451 | 452 | (* Retrieve and set up the problem data *) 453 | data = GurobiDataCreate[]; 454 | If[!GurobiDataQ[data], Return[$Failed]]; 455 | If[!GurobiCheckModel[data], Return[$Failed]]; 456 | 457 | objvec = problemData["ObjectiveVector"]; 458 | 459 | objmat = problemData["ObjectiveMatrix"]; 460 | intvars = problemData["IntegerVariableColumns"]; 461 | dPrint[3, "intvars: ", intvars]; 462 | 463 | error = GurobiSetVariableTypesAndObjectiveVector[data, intvars, objvec]; 464 | If[error=!=0, Return[$Failed]]; 465 | 466 | error = GurobiAddQuadraticObjectiveMatrix[data, SparseArray[objmat]]; 467 | If[error=!=0, Return[$Failed]]; 468 | 469 | coefficients = problemData["ConstraintCoefficientArrays"]; 470 | coneSpecifications = problemData["ConstraintSpecifications"]; 471 | ncons = Length[coneSpecifications]; 472 | dPrint[3, "coneSpecifications: ", coneSpecifications]; 473 | 474 | nonconvex = getNonConvexSetting[problemData]; 475 | dPrint[5, "nonconvex: ", nonconvex]; 476 | 477 | (* "EqualityConstraint" will always come first, then "NonNegativeCone", 478 | any number of "NormCone"s, any number of quadratic constraints *) 479 | lpos = 1; 480 | If[ncons>=1 && MatchQ[coneSpecifications[[1]], {"EqualityConstraint", _}], 481 | {b, a} = coefficients[[1]]; 482 | dPrint[5, "eq {a, b} -> ", {a, b}]; 483 | error = GurobiAddLinearConstraints[data, a, "=", -b]; 484 | If[error=!=0, Return[$Failed]]; 485 | lpos = 2; 486 | ]; 487 | If[ncons>=lpos && MatchQ[coneSpecifications[[lpos]], {"NonNegativeCone", _}], 488 | {b, a} = coefficients[[lpos]]; 489 | dPrint[5, "ineq {a, b}-> ", {a, b}]; 490 | error = GurobiAddLinearConstraints[data, a, ">", -b]; 491 | If[error=!=0, Return[$Failed]]; 492 | lpos += 1; 493 | ]; 494 | While[ncons>=lpos && MatchQ[coneSpecifications[[lpos]], {"NormCone", _}], 495 | {b, a} = coefficients[[lpos]]; 496 | dPrint[5, "norm {a, b}-> ", {a, b}]; 497 | error = GurobiAddSOCAffineConstraint[data, a, b]; 498 | If[error=!=0, Return[$Failed]]; 499 | lpos += 1; 500 | ]; 501 | While[ncons>=lpos && MatchQ[coneSpecifications[[lpos]], "QuadraticConstraint"], 502 | {d, c, q} = coefficients[[lpos]]; 503 | dPrint[5, "quad {q, c, d}-> ", {q, c, d}]; 504 | error = GurobiAddQuadraticConstraint[data, SparseArray[q], SparseArray[c], "<", -d]; 505 | If[error=!=0, Return[$Failed]]; 506 | lpos += 1; 507 | ]; 508 | 509 | (* Solve *) 510 | status = GurobiOptimize[data, {pmopts, "NonConvex"->nonconvex}]; 511 | 512 | status = GurobiStringStatus[data]; 513 | If[!StringQ[status], Return[$Failed]]; 514 | status = Optimization`SolutionData`WrapStatus[status]; 515 | (*Print["obj val: ", GurobiObjectiveValue[data]]; 516 | Print["x: ", Gurobix[data]];*) 517 | 518 | dPrint[1, "status: ", status]; 519 | {status, Gurobi1Data[data, {}]} 520 | ] 521 | 522 | Gurobi1Data[data_, _]["PrimalMinimumValue"] := GurobiObjectiveValue[data]; 523 | Gurobi1Data[data_, _]["PrimalMinimizerVector"] := Gurobix[data]; 524 | Gurobi1Data[data_, _]["Slack"] := Missing["NotAvailable"]; 525 | Gurobi1Data[data_, _]["DualMaximumValue"] := Missing["NotAvailable"]; 526 | Gurobi1Data[data_, _]["DualityGap"] := Missing["NotAvailable"]; 527 | Gurobi1Data[data_, _]["DualMaximizer"] := Missing["NotAvailable"]; 528 | 529 | (* Solve function for method Gurobi *) 530 | GurobiSolve2[problemData_, pmopts___] := 531 | Module[{a, b, c, d, q, objvec, objmat, data, nvars, status, lpos, nextra, norig, integerColumns, 532 | coefficients, coeff, coneSpecifications, ncons, coneVariableIndexes, nonconvex, error}, 533 | 534 | dPrint[1, "In GurobiSolve2"]; 535 | dPrint[3, "pmopts: ", pmopts]; 536 | pReset[5]; 537 | 538 | (* Retrieve and set up the problem data: *) 539 | data = GurobiDataCreate[]; 540 | If[!GurobiDataQ[data], Return[$Failed]]; 541 | If[!GurobiCheckModel[data], Return[$Failed]]; 542 | 543 | pPrint[5, "Setting up Gurobi: create and check data"]; 544 | objvec = problemData["ObjectiveVector"]; 545 | dPrint[5, "objvec: ", objvec]; 546 | nvars = Length[objvec]; 547 | objmat = problemData["ObjectiveMatrix"]; 548 | nextra = Lookup[problemData, "ExtraColumns", 0]; 549 | norig = nvars - nextra; 550 | coneSpecifications = problemData["ConstraintSpecifications"]; 551 | dPrint[3, "coneSpecifications: ", coneSpecifications]; 552 | coefficients = problemData["ConstraintCoefficientArrays"]; 553 | dPrint[5, "coefficients: ", coefficients]; 554 | ncons = Length[coneSpecifications]; 555 | dPrint[3, "ncons: ", ncons]; 556 | coneVariableIndexes = problemData["ConeVariableColumns"]; 557 | If[MatchQ[coneVariableIndexes, _Missing], coneVariableIndexes = ConstantArray[None, ncons]]; 558 | dPrint[5, "coneVariableIndexes: ", coneVariableIndexes]; 559 | nonconvex = getNonConvexSetting[problemData]; 560 | dPrint[5, "nonconvex: ", nonconvex]; 561 | integerColumns = problemData["IntegerVariableColumns"]; 562 | dPrint[5, "integerColumns: ", integerColumns]; 563 | pPrint[5, "Setting up Gurobi: get problem data"]; 564 | 565 | error = GurobiSetVariableTypesAndObjectiveVector[data, integerColumns, objvec]; 566 | If[error=!=0, Return[$Failed]]; 567 | pPrint[5, "Setting up Gurobi: set vars and linear objective"]; 568 | 569 | error = GurobiAddQuadraticObjectiveMatrix[data, SparseArray[objmat]]; 570 | If[error=!=0, Return[$Failed]]; 571 | pPrint[5, "Setting up Gurobi: set quad objective"]; 572 | 573 | (* "EqualityConstraint" will always come first, then "NonNegativeCone", 574 | any number of "NormCone"s, any number of quadratic constraints *) 575 | lpos = 1; 576 | If[ncons >= 1 && MatchQ[coneSpecifications[[1]], {"EqualityConstraint", _}], 577 | coeff = coefficients[[lpos]]; 578 | If[ListQ[coeff] && Length[coeff] === 2, 579 | {b, a} = coefficients[[1]], 580 | Return[$Failed] 581 | ]; 582 | error = GurobiAddLinearConstraints[data, SparseArray[a], "=", -b]; 583 | If[error=!=0, dPrint[3, "add lin = error: ", error]; Return[$Failed]]; 584 | lpos = 2; 585 | ]; 586 | If[ncons >= lpos && MatchQ[coneSpecifications[[lpos]], {"NonNegativeCone", _}], 587 | coeff = coefficients[[lpos]]; 588 | If[ListQ[coeff] && Length[coeff] === 2, 589 | {b, a} = coefficients[[lpos]], 590 | Return[$Failed] 591 | ]; 592 | error = GurobiAddLinearConstraints[data, SparseArray[a], ">", -b]; 593 | If[error =!= 0, dPrint[3, "add lin > error: ", error]; Return[$Failed]]; 594 | lpos += 1; 595 | ]; 596 | pPrint[5, "Setting up Gurobi: set linear constraints"]; 597 | 598 | While[ncons>=lpos && MatchQ[coneSpecifications[[lpos]], {"NormCone", _}], 599 | dPrint[5, "NormCone indices-> ", coneVariableIndexes[[lpos]]]; 600 | error = GurobiAddSOCMembershipConstraint[data, coneVariableIndexes[[lpos]]]; 601 | If[error=!=0, dPrint[3, "add soc const error: ", error]; Return[$Failed]]; 602 | lpos += 1; 603 | ]; 604 | pPrint[5, "Setting up Gurobi: set SOC constraints"]; 605 | 606 | While[ncons>=lpos && MatchQ[coneSpecifications[[lpos]], "QuadraticConstraint"], 607 | {d, c, q} = coefficients[[lpos]]; 608 | dPrint[5, "quad {q, c, d}-> ", {q, c, d}]; 609 | error = GurobiAddQuadraticConstraint[data, SparseArray[q], SparseArray[c], "<", -d]; 610 | If[error=!=0, dPrint[3, "add quad const error: ", error]; Return[$Failed]]; 611 | lpos += 1; 612 | ]; 613 | pPrint[5, "Setting up Gurobi: set quadratic constraints"]; 614 | 615 | (* Solve: *) 616 | status = GurobiOptimize[data, {pmopts, "NonConvex"->nonconvex}]; 617 | status = GurobiStringStatus[data]; 618 | If[!StringQ[status], Return[$Failed]]; 619 | status = Optimization`SolutionData`WrapStatus[status]; 620 | (*Print["obj val: ", GurobiObjectiveValue[data]]; 621 | Print["x: ", Gurobix[data]];*) 622 | 623 | {status, Gurobi2Data[data, {integerColumns}]} 624 | ]; 625 | 626 | Gurobi2Data[data_, _]["PrimalMinimumValue"] := GurobiObjectiveValue[data]; 627 | Gurobi2Data[data_, {integerColumns_}]["PrimalMinimizerVector"] := 628 | Block[{x = Gurobix[data]}, 629 | If[Length[integerColumns] > 0, 630 | x[[integerColumns]] = Round[x[[integerColumns]]] 631 | ]; 632 | x 633 | ]; 634 | Gurobi2Data[data_, _]["DualMaximumValue"] := Missing["NotAvailable"]; 635 | Gurobi2Data[data_, _]["DualityGap"] := Missing["NotAvailable"]; 636 | Gurobi2Data[data_, _]["DualMaximizer"] := Missing["NotAvailable"]; 637 | Gurobi2Data[data_, _]["Slack"] := Missing["NotAvailable"]; 638 | 639 | End[] 640 | EndPackage[] 641 | -------------------------------------------------------------------------------- /CSource/GurobiLink.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "GurobiSolution.h" 4 | 5 | EXTERN_C DLLEXPORT mint WolframLibrary_getVersion() 6 | { 7 | return WolframLibraryVersion; 8 | } 9 | 10 | EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) 11 | { 12 | 13 | int err; 14 | err = (*libData->registerLibraryExpressionManager)("Gurobi_data_instance_manager", GurobiDataMap_manage); 15 | if (err) 16 | return err; 17 | err = (*libData->registerLibraryExpressionManager)("Gurobi_environment_instance_manager", GurobiEnvironmentMap_manage); 18 | 19 | return err; 20 | } 21 | 22 | EXTERN_C DLLEXPORT void WolframLibrary_uninitialize(WolframLibraryData libData) 23 | { 24 | // remove environment 25 | GurobiEnvironmentMap_manage(libData, 1, 1); 26 | (*libData->unregisterLibraryCallbackManager)("Gurobi_data_instance_manager"); 27 | (*libData->unregisterLibraryCallbackManager)("Gurobi_environment_instance_manager"); 28 | } 29 | 30 | /************************************************************************/ 31 | /* GurobiData_CheckLicense */ 32 | /* */ 33 | /* GurobiCheckLicense[environment] */ 34 | /************************************************************************/ 35 | 36 | EXTERN_C DLLEXPORT int GurobiData_CheckLicense(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 37 | { 38 | mint envID; 39 | GurobiEnvironment Gurobienvironment; 40 | 41 | if (Argc != 1) 42 | { 43 | return LIBRARY_FUNCTION_ERROR; 44 | } 45 | 46 | envID = MArgument_getInteger(Args[0]); 47 | Gurobienvironment = GurobiEnvironmentMap_get(envID); 48 | 49 | // load return values 50 | MArgument_setInteger(Res, Gurobienvironment->error); 51 | 52 | return LIBRARY_NO_ERROR; 53 | } 54 | 55 | /************************************************************************/ 56 | /* GurobiData_CheckModel */ 57 | /* */ 58 | /* GurobiCheckModel[data] */ 59 | /************************************************************************/ 60 | 61 | EXTERN_C DLLEXPORT int GurobiData_CheckModel(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 62 | { 63 | mint dataID; 64 | GurobiData Gurobidata; 65 | 66 | if (Argc != 1) 67 | { 68 | return LIBRARY_FUNCTION_ERROR; 69 | } 70 | 71 | dataID = MArgument_getInteger(Args[0]); 72 | Gurobidata = GurobiDataMap_get(dataID); 73 | 74 | // return the error from GRBnewmodel in GurobiEData_new 75 | MArgument_setInteger(Res, (mint)(Gurobidata->error)); 76 | 77 | return LIBRARY_NO_ERROR; 78 | } 79 | 80 | /******************************************************************************/ 81 | /* GurobiData_SetVariableTypesAndObjectiveVector */ 82 | /* */ 83 | /* GurobiSetVariableTypesAndObjectiveVector[data, intvars, objvector] */ 84 | /******************************************************************************/ 85 | 86 | EXTERN_C DLLEXPORT int GurobiData_SetVariableTypesAndObjectiveVector(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 87 | { 88 | int error; 89 | mint i, nvars, nintvars, dataID, *intvars = nullptr; 90 | char* vartypes; 91 | double *objvec, *lbounds = nullptr; 92 | MTensor pT; 93 | GurobiData Gurobidata; 94 | 95 | if (Argc != 3) 96 | { 97 | return LIBRARY_FUNCTION_ERROR; 98 | } 99 | dataID = MArgument_getInteger(Args[0]); 100 | Gurobidata = GurobiDataMap_get(dataID); 101 | 102 | pT = MArgument_getMTensor(Args[1]); 103 | nintvars = libData->MTensor_getFlattenedLength(pT); 104 | intvars = libData->MTensor_getIntegerData(pT); 105 | 106 | pT = MArgument_getMTensor(Args[2]); 107 | nvars = libData->MTensor_getFlattenedLength(pT); 108 | objvec = libData->MTensor_getRealData(pT); 109 | Gurobidata->nvars = nvars; 110 | 111 | lbounds = (double*)malloc(nvars * sizeof(double)); 112 | // set lb to be a vector of -GRB_INFINITY; if we leave it nullptr the default is a vector of 0.0 113 | std::fill_n(lbounds, nvars, -GRB_INFINITY); 114 | 115 | vartypes = (char*)malloc(nvars * sizeof(char)); 116 | std::memset(vartypes, 'C', nvars); 117 | for (i = 0; i < nintvars; i++) 118 | { 119 | vartypes[intvars[i] - 1] = 'I'; 120 | } 121 | 122 | // Add objective vector and variable types 123 | error = GRBaddvars(Gurobidata->model, (int)nvars, 0, nullptr, nullptr, nullptr, objvec, lbounds, nullptr, vartypes, nullptr); 124 | 125 | // free stuff 126 | if (lbounds) 127 | free(lbounds); 128 | if (vartypes) 129 | free(vartypes); 130 | 131 | // load return values 132 | MArgument_setInteger(Res, (mint)error); 133 | 134 | return LIBRARY_NO_ERROR; 135 | } 136 | 137 | /*****************************************************************************************************/ 138 | /* GurobiData_SetVariableTypesAndBoundsAndObjectiveVector */ 139 | /* */ 140 | /* GurobiSetVariableTypesAndBoundsAndObjectiveVector[data, vartypes, lbounds, ubounds, objvector] */ 141 | /*****************************************************************************************************/ 142 | 143 | EXTERN_C DLLEXPORT int GurobiData_SetVariableTypesAndBoundsAndObjectiveVector(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 144 | { 145 | int error, nvars; 146 | char* vartypes = nullptr; 147 | double *objvec, *lbounds, *ubounds; 148 | mint dataID; 149 | MTensor pT; 150 | GurobiData Gurobidata; 151 | 152 | if (Argc != 5) 153 | { 154 | return LIBRARY_FUNCTION_ERROR; 155 | } 156 | dataID = MArgument_getInteger(Args[0]); 157 | Gurobidata = GurobiDataMap_get(dataID); 158 | 159 | vartypes = MArgument_getUTF8String(Args[1]); 160 | nvars = (int)strlen(vartypes); 161 | 162 | Gurobidata->nvars = nvars; 163 | 164 | pT = MArgument_getMTensor(Args[2]); 165 | lbounds = libData->MTensor_getRealData(pT); 166 | 167 | pT = MArgument_getMTensor(Args[3]); 168 | ubounds = libData->MTensor_getRealData(pT); 169 | 170 | pT = MArgument_getMTensor(Args[4]); 171 | objvec = libData->MTensor_getRealData(pT); 172 | 173 | // Add objective vector and variable types 174 | error = GRBaddvars(Gurobidata->model, nvars, 0, nullptr, nullptr, nullptr, objvec, lbounds, ubounds, vartypes, nullptr); 175 | 176 | // free stuff 177 | libData->UTF8String_disown(vartypes); 178 | 179 | // load return values 180 | MArgument_setInteger(Res, (mint)error); 181 | 182 | return LIBRARY_NO_ERROR; 183 | } 184 | 185 | /**********************************************************************/ 186 | /* GurobiData_AddQuadraticObjectiveMatrix */ 187 | /* */ 188 | /* GurobiAddQuadraticObjectiveMatrix[data, Qmat] */ 189 | /* adds the term 1/2 x^T.Q.x to already specified lin objective c.x */ 190 | /**********************************************************************/ 191 | 192 | EXTERN_C DLLEXPORT int GurobiData_AddQuadraticObjectiveMatrix(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 193 | { 194 | int error = 0, numquadnz, *quadrow = nullptr, *quadcol = nullptr; 195 | double *quadval = nullptr, *explicitValues = nullptr; 196 | mint i, err, dataID, *explicitPositions; 197 | MTensor *pT = nullptr, TQ = nullptr; 198 | GurobiData Gurobidata; 199 | MSparseArray Qmat; 200 | 201 | if (Argc != 2) 202 | return LIBRARY_FUNCTION_ERROR; 203 | dataID = MArgument_getInteger(Args[0]); 204 | Gurobidata = GurobiDataMap_get(dataID); 205 | 206 | Qmat = MArgument_getMSparseArray(Args[1]); 207 | 208 | pT = (*(libData->sparseLibraryFunctions->MSparseArray_getExplicitValues))(Qmat); 209 | quadval = libData->MTensor_getRealData(*pT); 210 | if (*pT != nullptr) 211 | numquadnz = (int)(libData->MTensor_getFlattenedLength(*pT)); 212 | else 213 | numquadnz = 0; 214 | 215 | if (numquadnz > 0) 216 | { 217 | err = (*(libData->sparseLibraryFunctions->MSparseArray_getExplicitPositions))(Qmat, &TQ); 218 | if (err) 219 | return LIBRARY_FUNCTION_ERROR; 220 | explicitPositions = libData->MTensor_getIntegerData(TQ); 221 | 222 | quadrow = (int*)malloc(numquadnz * sizeof(int)); 223 | quadcol = (int*)malloc(numquadnz * sizeof(int)); 224 | for (i = 0; i < numquadnz; i++) 225 | { 226 | quadrow[i] = (int)explicitPositions[2 * i] - 1; 227 | quadcol[i] = (int)explicitPositions[2 * i + 1] - 1; 228 | } 229 | 230 | error = GRBaddqpterms(Gurobidata->model, numquadnz, quadrow, quadcol, quadval); 231 | 232 | if (quadrow) 233 | free(quadrow); 234 | if (quadcol) 235 | free(quadcol); 236 | } 237 | 238 | // free more stuff 239 | if (TQ) 240 | libData->MTensor_free(TQ); 241 | 242 | // load return values 243 | MArgument_setInteger(Res, (mint)error); 244 | 245 | return LIBRARY_NO_ERROR; 246 | } 247 | 248 | /*****************************************************************************/ 249 | /* GurobiData_AddLinearConstraint */ 250 | /* */ 251 | /* GurobiAddLinearConstraint[data, indices, values, sense, rhs] */ 252 | /* sense: GRB_EQUAL -> '=', GRB_LESS_EQUAL -> '<', GRB_GREATER_EQUAL -> '>' */ 253 | /*****************************************************************************/ 254 | 255 | EXTERN_C DLLEXPORT int GurobiData_AddLinearConstraint(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 256 | { 257 | int j, error, nz, *indices = nullptr; 258 | double *values = nullptr, rhs; 259 | mint dataID, *idata = nullptr; 260 | MTensor pT; 261 | GurobiData Gurobidata; 262 | char sense, *senseString; 263 | 264 | if (Argc != 5) 265 | { 266 | return LIBRARY_FUNCTION_ERROR; 267 | } 268 | 269 | dataID = MArgument_getInteger(Args[0]); 270 | Gurobidata = GurobiDataMap_get(dataID); 271 | 272 | pT = MArgument_getMTensor(Args[1]); 273 | nz = (int)(libData->MTensor_getFlattenedLength(pT)); 274 | idata = libData->MTensor_getIntegerData(pT); 275 | indices = (int*)malloc(nz * sizeof(int)); 276 | for (j = 0; j < nz; j++) 277 | { 278 | indices[j] = (int)idata[j] - 1; 279 | } 280 | pT = MArgument_getMTensor(Args[2]); 281 | values = libData->MTensor_getRealData(pT); 282 | 283 | senseString = MArgument_getUTF8String(Args[3]); 284 | sense = senseString[0]; 285 | 286 | rhs = MArgument_getReal(Args[4]); 287 | 288 | error = GRBaddconstr(Gurobidata->model, nz, indices, values, sense, rhs, nullptr); 289 | 290 | // free stuff 291 | if (indices) 292 | free(indices); 293 | libData->UTF8String_disown(senseString); 294 | 295 | // load return values 296 | MArgument_setInteger(Res, (mint)error); 297 | 298 | return LIBRARY_NO_ERROR; 299 | } 300 | 301 | /************************************************************************/ 302 | /* SpArrayData: convenient data wrapper for MSparseArray */ 303 | /************************************************************************/ 304 | typedef struct 305 | { 306 | mint nentries; 307 | mint rank; 308 | mint const* dims; 309 | mint* explicitPositions; 310 | mreal* implicitValue; 311 | mreal* explicitValues; 312 | } SpArrayData; 313 | 314 | SpArrayData SpArrayData_fromMSparseArray(WolframLibraryData libData, MSparseArray S, MTensor* pT1) 315 | { 316 | WolframSparseLibrary_Functions spFuns = libData->sparseLibraryFunctions; 317 | MTensor* pT = nullptr; 318 | SpArrayData sad; 319 | mint error; 320 | 321 | sad.rank = (*(spFuns->MSparseArray_getRank))(S); 322 | sad.dims = (*(spFuns->MSparseArray_getDimensions))(S); 323 | 324 | pT = (*(spFuns->MSparseArray_getImplicitValue))(S); 325 | sad.implicitValue = libData->MTensor_getRealData(*pT); 326 | 327 | pT = (*(spFuns->MSparseArray_getExplicitValues))(S); 328 | sad.explicitValues = libData->MTensor_getRealData(*pT); 329 | if (*pT != nullptr) 330 | { 331 | sad.nentries = libData->MTensor_getFlattenedLength(*pT); 332 | } 333 | else 334 | { 335 | sad.nentries = 0; 336 | } 337 | 338 | error = (*(spFuns->MSparseArray_getExplicitPositions))(S, pT1); 339 | sad.explicitPositions = libData->MTensor_getIntegerData(*pT1); 340 | 341 | return sad; 342 | } 343 | 344 | /************************************************************************/ 345 | /* GurobiData_AddLinearConstraint1 */ 346 | /* */ 347 | /* GurobiAddLinearConstraint1[data, vec, sense, rhs] */ 348 | /* sense: GRB_EQUAL, GRB_LESS_EQUAL, GRB_GREATER_EQUAL */ 349 | /************************************************************************/ 350 | 351 | EXTERN_C DLLEXPORT int GurobiData_AddLinearConstraint1(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 352 | { 353 | int error, nz, *indices = nullptr; 354 | char sense, *senseString = nullptr; 355 | double *values = nullptr, rhs; 356 | mint i, dataID; 357 | MTensor Tvec = nullptr; 358 | GurobiData Gurobidata; 359 | SpArrayData vector; 360 | MSparseArray vectorSA; 361 | 362 | if (Argc != 4) 363 | { 364 | return LIBRARY_FUNCTION_ERROR; 365 | } 366 | 367 | dataID = MArgument_getInteger(Args[0]); 368 | Gurobidata = GurobiDataMap_get(dataID); 369 | 370 | /* find nz, indices and values */ 371 | vectorSA = MArgument_getMSparseArray(Args[1]); 372 | vector = SpArrayData_fromMSparseArray(libData, vectorSA, &Tvec); 373 | nz = (int)vector.nentries; 374 | indices = (int*)malloc(nz * sizeof(int)); 375 | for (i = 0; i < nz; i++) 376 | { 377 | indices[i] = (int)vector.explicitPositions[i] - 1; 378 | } 379 | values = vector.explicitValues; 380 | 381 | senseString = MArgument_getUTF8String(Args[2]); 382 | sense = senseString[0]; 383 | 384 | rhs = MArgument_getReal(Args[3]); 385 | 386 | error = GRBaddconstr(Gurobidata->model, nz, indices, values, sense, rhs, nullptr); 387 | 388 | // free stuff 389 | if (Tvec) 390 | libData->MTensor_free(Tvec); 391 | if (indices) 392 | free(indices); 393 | libData->UTF8String_disown(senseString); 394 | 395 | // load return values 396 | MArgument_setInteger(Res, (mint)error); 397 | 398 | return LIBRARY_NO_ERROR; 399 | } 400 | 401 | /************************************************************************/ 402 | /* GurobiData_AddLinearConstraints */ 403 | /* */ 404 | /* GurobiAddLinearConstraints[data, mat, sense, rhs] */ 405 | /* sense: GRB_EQUAL, GRB_LESS_EQUAL, GRB_GREATER_EQUAL */ 406 | /************************************************************************/ 407 | 408 | EXTERN_C DLLEXPORT int GurobiData_AddLinearConstraints(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 409 | { 410 | char sense, *senseString = nullptr, *sensevec = nullptr; 411 | int error, ncons, Anz, *Ap = nullptr, *Ai = nullptr; 412 | mint j, dataID, *idata = nullptr; 413 | double *rhs = nullptr, *Ax = nullptr; 414 | MTensor pT, *Tp = nullptr; 415 | GurobiData Gurobidata; 416 | MSparseArray AMat = nullptr; 417 | 418 | if (Argc != 4) 419 | return LIBRARY_FUNCTION_ERROR; 420 | 421 | dataID = MArgument_getInteger(Args[0]); 422 | Gurobidata = GurobiDataMap_get(dataID); 423 | 424 | pT = MArgument_getMTensor(Args[3]); 425 | rhs = libData->MTensor_getRealData(pT); 426 | ncons = (int)(libData->MTensor_getFlattenedLength(pT)); 427 | 428 | AMat = MArgument_getMSparseArray(Args[1]); 429 | 430 | Tp = (*libData->sparseLibraryFunctions->MSparseArray_getRowPointers)(AMat); 431 | if (libData->MTensor_getFlattenedLength(*Tp) != ncons + 1) 432 | return LIBRARY_FUNCTION_ERROR; 433 | idata = libData->MTensor_getIntegerData(*Tp); 434 | Ap = (int*)malloc(ncons * sizeof(int)); 435 | 436 | for (j = 0; j < ncons; j++) 437 | Ap[j] = (int)idata[j]; 438 | Anz = (int)idata[ncons]; 439 | if (Anz == 0) 440 | { 441 | free(Ap); 442 | Ap = nullptr; 443 | Ai = nullptr; 444 | Ax = nullptr; 445 | } 446 | else 447 | { 448 | Ai = (int*)malloc(sizeof(int) * Anz); 449 | Tp = (*libData->sparseLibraryFunctions->MSparseArray_getColumnIndices)(AMat); 450 | if (libData->MTensor_getFlattenedLength(*Tp) != Anz) 451 | return LIBRARY_FUNCTION_ERROR; 452 | idata = libData->MTensor_getIntegerData(*Tp); 453 | for (j = 0; j < Anz; j++) 454 | Ai[j] = (int)(idata[j] - 1); 455 | 456 | Tp = (*libData->sparseLibraryFunctions->MSparseArray_getExplicitValues)(AMat); 457 | if (libData->MTensor_getFlattenedLength(*Tp) != Anz) 458 | return LIBRARY_FUNCTION_ERROR; 459 | Ax = libData->MTensor_getRealData(*Tp); 460 | } 461 | 462 | senseString = MArgument_getUTF8String(Args[2]); 463 | sense = senseString[0]; 464 | 465 | sensevec = (char*)malloc(sizeof(char) * ncons); 466 | for (j = 0; j < ncons; j++) 467 | sensevec[j] = (char)sense; 468 | 469 | pT = MArgument_getMTensor(Args[3]); 470 | rhs = libData->MTensor_getRealData(pT); 471 | 472 | error = GRBaddconstrs(Gurobidata->model, ncons, Anz, Ap, Ai, Ax, sensevec, rhs, nullptr); 473 | 474 | // free stuff 475 | if (Ap) 476 | free(Ap); 477 | if (Ai) 478 | free(Ai); 479 | if (sensevec) 480 | free(sensevec); 481 | libData->UTF8String_disown(senseString); 482 | 483 | // load return values 484 | MArgument_setInteger(Res, (mint)error); 485 | 486 | return LIBRARY_NO_ERROR; 487 | } 488 | 489 | /****************************************************************************************************/ 490 | /* GurobiData_AddQuadraticConstraint */ 491 | /* */ 492 | /* GurobiAddQuadraticConstraint[data, linind, linvals, quadrow, quadcol, quadvals, sense, rhs] */ 493 | /* sense: GRB_EQUAL, GRB_LESS_EQUAL, GRB_GREATER_EQUAL */ 494 | /****************************************************************************************************/ 495 | 496 | EXTERN_C DLLEXPORT int GurobiData_AddQuadraticConstraint(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 497 | { 498 | int error, numlinnz, numquadnz, *linind = nullptr, *quadrow = nullptr, *quadcol = nullptr; 499 | char sense, *senseString = nullptr; 500 | double *linval, *quadval, rhs; 501 | mint j, dataID, *idata; 502 | MTensor pT; 503 | GurobiData Gurobidata; 504 | 505 | if (Argc != 8) 506 | { 507 | return LIBRARY_FUNCTION_ERROR; 508 | } 509 | dataID = MArgument_getInteger(Args[0]); 510 | Gurobidata = GurobiDataMap_get(dataID); 511 | 512 | pT = MArgument_getMTensor(Args[1]); 513 | numlinnz = (int)(libData->MTensor_getFlattenedLength(pT)); 514 | 515 | if (numlinnz == 0) 516 | { 517 | linind = nullptr; 518 | linval = nullptr; 519 | } 520 | else 521 | { 522 | idata = libData->MTensor_getIntegerData(pT); 523 | linind = (int*)malloc(numlinnz * sizeof(int)); 524 | for (j = 0; j < numlinnz; j++) 525 | { 526 | linind[j] = (char)idata[j] - 1; 527 | } 528 | pT = MArgument_getMTensor(Args[2]); 529 | linval = libData->MTensor_getRealData(pT); 530 | } 531 | 532 | pT = MArgument_getMTensor(Args[3]); 533 | numquadnz = (int)(libData->MTensor_getFlattenedLength(pT)); 534 | idata = libData->MTensor_getIntegerData(pT); 535 | quadrow = (int*)malloc(numquadnz * sizeof(int)); 536 | quadcol = (int*)malloc(numquadnz * sizeof(int)); 537 | for (j = 0; j < numquadnz; j++) 538 | { 539 | quadrow[j] = (int)idata[j] - 1; 540 | } 541 | pT = MArgument_getMTensor(Args[4]); 542 | idata = libData->MTensor_getIntegerData(pT); 543 | for (j = 0; j < numquadnz; j++) 544 | { 545 | quadcol[j] = (int)idata[j] - 1; 546 | } 547 | pT = MArgument_getMTensor(Args[5]); 548 | quadval = libData->MTensor_getRealData(pT); 549 | 550 | senseString = MArgument_getUTF8String(Args[6]); 551 | sense = senseString[0]; 552 | 553 | rhs = MArgument_getReal(Args[7]); 554 | 555 | error = GRBaddqconstr(Gurobidata->model, numlinnz, linind, linval, numquadnz, quadrow, quadcol, quadval, sense, rhs, "qc"); 556 | 557 | // free stuff 558 | if (linind) 559 | free(linind); 560 | if (quadrow) 561 | free(quadrow); 562 | if (quadcol) 563 | free(quadcol); 564 | libData->UTF8String_disown(senseString); 565 | 566 | // load return values 567 | MArgument_setInteger(Res, (mint)error); 568 | 569 | return LIBRARY_NO_ERROR; 570 | } 571 | 572 | /**********************************************************************/ 573 | /* GurobiData_AddQuadraticConstraint1 */ 574 | /* */ 575 | /* GurobiAddQuadraticConstraint1[data, mat, vec, sense, rhs] */ 576 | /* 1/2 x^T.Q.x + q.x sense b */ 577 | /**********************************************************************/ 578 | 579 | EXTERN_C DLLEXPORT int GurobiData_AddQuadraticConstraint1(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 580 | { 581 | int error, numlinnz, numquadnz, *linind, *quadrow, *quadcol; 582 | char sense, *senseString = nullptr; 583 | double *linval, *quadval, rhs; 584 | mint i, dataID; 585 | MTensor TQ = nullptr, Tq = nullptr; 586 | GurobiData Gurobidata; 587 | SpArrayData Qmat, qvec; 588 | MSparseArray QmatSA, qvecSA; 589 | 590 | if (Argc != 5) 591 | { 592 | return LIBRARY_FUNCTION_ERROR; 593 | } 594 | dataID = MArgument_getInteger(Args[0]); 595 | Gurobidata = GurobiDataMap_get(dataID); 596 | 597 | QmatSA = MArgument_getMSparseArray(Args[1]); 598 | Qmat = SpArrayData_fromMSparseArray(libData, QmatSA, &TQ); 599 | numquadnz = (int)Qmat.nentries; 600 | quadrow = (int*)malloc(numquadnz * sizeof(int)); 601 | quadcol = (int*)malloc(numquadnz * sizeof(int)); 602 | for (i = 0; i < numquadnz; i++) 603 | { 604 | quadrow[i] = (int)Qmat.explicitPositions[2 * i] - 1; 605 | quadcol[i] = (int)Qmat.explicitPositions[2 * i + 1] - 1; 606 | } 607 | quadval = Qmat.explicitValues; 608 | qvecSA = MArgument_getMSparseArray(Args[2]); 609 | qvec = SpArrayData_fromMSparseArray(libData, qvecSA, &Tq); 610 | numlinnz = (int)qvec.nentries; 611 | if (numlinnz == 0) 612 | { 613 | linind = nullptr; 614 | linval = nullptr; 615 | } 616 | else 617 | { 618 | linind = (int*)malloc(numlinnz * sizeof(int)); 619 | for (i = 0; i < numlinnz; i++) 620 | { 621 | linind[i] = (int)qvec.explicitPositions[i] - 1; 622 | } 623 | linval = qvec.explicitValues; 624 | } 625 | 626 | senseString = MArgument_getUTF8String(Args[3]); 627 | sense = senseString[0]; 628 | 629 | rhs = MArgument_getReal(Args[4]); 630 | 631 | error = GRBaddqconstr(Gurobidata->model, numlinnz, linind, linval, numquadnz, quadrow, quadcol, quadval, sense, rhs, "qc1"); 632 | 633 | // free stuff 634 | if (Tq) 635 | libData->MTensor_free(Tq); 636 | if (TQ) 637 | libData->MTensor_free(TQ); 638 | if (linind) 639 | free(linind); 640 | if (quadrow) 641 | free(quadrow); 642 | if (quadcol) 643 | free(quadcol); 644 | libData->UTF8String_disown(senseString); 645 | 646 | // load return values 647 | MArgument_setInteger(Res, (mint)error); 648 | 649 | return LIBRARY_NO_ERROR; 650 | } 651 | 652 | /************************************************************************/ 653 | /* GurobiData_SetParameters */ 654 | /* */ 655 | /* GurobiSetParameters[data, maxit, tol, nonconvex, threads] */ 656 | /************************************************************************/ 657 | 658 | EXTERN_C DLLEXPORT int GurobiData_SetParameters(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 659 | { 660 | int error, maxit, nonconvex, threads; 661 | mint dataID; 662 | double tol; 663 | GurobiData Gurobidata; 664 | 665 | if (Argc != 5) 666 | { 667 | return LIBRARY_FUNCTION_ERROR; 668 | } 669 | dataID = MArgument_getInteger(Args[0]); 670 | Gurobidata = GurobiDataMap_get(dataID); 671 | 672 | maxit = (int)MArgument_getInteger(Args[1]); 673 | error = GRBsetintparam(GRBgetenv(Gurobidata->model), "BarIterLimit", maxit); 674 | if (error) 675 | return LIBRARY_FUNCTION_ERROR; 676 | 677 | tol = MArgument_getReal(Args[2]); 678 | if (tol > 1) 679 | { 680 | tol = 1.; 681 | } 682 | error = GRBsetdblparam(GRBgetenv(Gurobidata->model), "BarConvTol", tol / 100); 683 | if (error) 684 | return LIBRARY_FUNCTION_ERROR; 685 | error = GRBsetdblparam(GRBgetenv(Gurobidata->model), "BarQCPConvTol", tol); 686 | if (error) 687 | return LIBRARY_FUNCTION_ERROR; 688 | 689 | nonconvex = (int)MArgument_getInteger(Args[3]); 690 | error = GRBsetintparam(GRBgetenv(Gurobidata->model), "NonConvex", nonconvex); 691 | if (error) 692 | return LIBRARY_FUNCTION_ERROR; 693 | 694 | threads = (int)MArgument_getInteger(Args[4]); 695 | error = GRBsetintparam(GRBgetenv(Gurobidata->model), "Threads", threads); 696 | if (error) 697 | return LIBRARY_FUNCTION_ERROR; 698 | 699 | /* load return values */ 700 | MArgument_setInteger(Res, 0); 701 | 702 | return LIBRARY_NO_ERROR; 703 | } 704 | 705 | /************************************************************************/ 706 | /* GurobiData_SetStartingPoint */ 707 | /* */ 708 | /* GurobiSetStartingPoint[data, initpt] */ 709 | /************************************************************************/ 710 | 711 | EXTERN_C DLLEXPORT int GurobiData_SetStartingPoint(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 712 | { 713 | int error; 714 | mint dataID; 715 | double* start; 716 | MTensor pT; 717 | GurobiData Gurobidata; 718 | 719 | if (Argc != 2) 720 | { 721 | return LIBRARY_FUNCTION_ERROR; 722 | } 723 | dataID = MArgument_getInteger(Args[0]); 724 | Gurobidata = GurobiDataMap_get(dataID); 725 | 726 | pT = MArgument_getMTensor(Args[1]); 727 | start = libData->MTensor_getRealData(pT); 728 | 729 | error = GRBsetdblattrarray(Gurobidata->model, "Start", 0, (int)(Gurobidata->nvars), start); 730 | 731 | // load return values 732 | MArgument_setInteger(Res, error); 733 | 734 | return LIBRARY_NO_ERROR; 735 | } 736 | 737 | /************************************************************************/ 738 | /* GurobiData_OptimizeModel */ 739 | /* */ 740 | /* GurobiOptimize[data] */ 741 | /************************************************************************/ 742 | 743 | EXTERN_C DLLEXPORT int GurobiData_OptimizeModel(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 744 | { 745 | int error; 746 | mint dataID; 747 | GurobiData Gurobidata; 748 | 749 | if (Argc != 1) 750 | { 751 | return LIBRARY_FUNCTION_ERROR; 752 | } 753 | dataID = MArgument_getInteger(Args[0]); 754 | Gurobidata = GurobiDataMap_get(dataID); 755 | 756 | /* optimize model */ 757 | error = GRBoptimize(Gurobidata->model); 758 | 759 | /* load return values */ 760 | MArgument_setInteger(Res, (mint)error); 761 | 762 | return LIBRARY_NO_ERROR; 763 | } 764 | 765 | /*****************************************************************/ 766 | /* GurobiData_GetStatusValue */ 767 | /* */ 768 | /* GurobiStatusValue[data] */ 769 | /*****************************************************************/ 770 | 771 | EXTERN_C DLLEXPORT int GurobiData_GetStatusValue(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 772 | { 773 | int error; 774 | mint dataID; 775 | GurobiData Gurobidata; 776 | int optimstatus; 777 | 778 | if (Argc != 1) 779 | { 780 | return LIBRARY_FUNCTION_ERROR; 781 | } 782 | dataID = MArgument_getInteger(Args[0]); 783 | Gurobidata = GurobiDataMap_get(dataID); 784 | 785 | /* get solution status */ 786 | error = GRBgetintattr(Gurobidata->model, GRB_INT_ATTR_STATUS, &optimstatus); 787 | 788 | if (error) 789 | return LIBRARY_FUNCTION_ERROR; 790 | 791 | /* load return values */ 792 | MArgument_setInteger(Res, (mint)optimstatus); 793 | 794 | return LIBRARY_NO_ERROR; 795 | } 796 | 797 | /************************************************************************/ 798 | /* GurobiData_GetObjectiveValue */ 799 | /* */ 800 | /* GurobiObjectiveValue[data] */ 801 | /************************************************************************/ 802 | 803 | EXTERN_C DLLEXPORT int GurobiData_GetObjectiveValue(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 804 | { 805 | int error; 806 | mint dataID; 807 | GurobiData Gurobidata; 808 | double objval = -1; 809 | 810 | if (Argc != 1) 811 | { 812 | return LIBRARY_FUNCTION_ERROR; 813 | } 814 | dataID = MArgument_getInteger(Args[0]); 815 | Gurobidata = GurobiDataMap_get(dataID); 816 | 817 | /* get optimal objective value */ 818 | error = GRBgetdblattr(Gurobidata->model, GRB_DBL_ATTR_OBJVAL, &objval); 819 | 820 | if (error) 821 | return LIBRARY_FUNCTION_ERROR; 822 | 823 | /* load return values */ 824 | MArgument_setReal(Res, objval); 825 | 826 | return LIBRARY_NO_ERROR; 827 | } 828 | 829 | /************************************************************************/ 830 | /* GurobiData_Getx */ 831 | /* */ 832 | /* Gurobix[data] */ 833 | /************************************************************************/ 834 | 835 | inline mint setTensor(WolframLibraryData libData, const mreal* t, mint tdim, MTensor& T) 836 | { 837 | if (T) 838 | { 839 | // free the tensor if it is already set 840 | libData->MTensor_free(T); 841 | T = nullptr; 842 | } 843 | // allocate the new tensor 844 | mint dims[1]; 845 | dims[0] = tdim; 846 | libData->MTensor_new(MType_Real, 1, dims, &T); 847 | // fill the tensor 848 | mreal* tdata = libData->MTensor_getRealData(T); 849 | for (int i = 0; i < tdim; i++) 850 | tdata[i] = t[i]; 851 | return 0; 852 | } 853 | 854 | EXTERN_C DLLEXPORT int GurobiData_Getx(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 855 | { 856 | int error; 857 | mint dataID; 858 | GurobiData Gurobidata; 859 | double* sol = nullptr; 860 | MTensor solT = nullptr; 861 | 862 | if (Argc != 1) 863 | { 864 | return LIBRARY_FUNCTION_ERROR; 865 | } 866 | dataID = MArgument_getInteger(Args[0]); 867 | Gurobidata = GurobiDataMap_get(dataID); 868 | 869 | /* get the minimizer vector */ 870 | sol = (double*)malloc(sizeof(double) * (Gurobidata->nvars)); 871 | error = GRBgetdblattrarray(Gurobidata->model, GRB_DBL_ATTR_X, 0, (int)(Gurobidata->nvars), sol); 872 | 873 | if (error) 874 | return LIBRARY_FUNCTION_ERROR; 875 | 876 | setTensor(libData, sol, Gurobidata->nvars, solT); 877 | 878 | // load return values 879 | MArgument_setMTensor(Res, solT); 880 | 881 | // free stuff 882 | if (sol) 883 | free(sol); 884 | 885 | return LIBRARY_NO_ERROR; 886 | } 887 | 888 | /************************************************************************/ 889 | /* GurobiData_GetSlack */ 890 | /* */ 891 | /* GurobiSlack[data] */ 892 | /************************************************************************/ 893 | 894 | EXTERN_C DLLEXPORT int GurobiData_GetSlack(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 895 | { 896 | int error, ncons, nqcons, allcons; 897 | mint dataID; 898 | GurobiData Gurobidata; 899 | double* slack = nullptr; 900 | MTensor slackT = nullptr; 901 | 902 | if (Argc != 1) 903 | { 904 | return LIBRARY_FUNCTION_ERROR; 905 | } 906 | dataID = MArgument_getInteger(Args[0]); 907 | Gurobidata = GurobiDataMap_get(dataID); 908 | 909 | error = GRBgetintattr(Gurobidata->model, "NumConstrs", &ncons); 910 | if (error) 911 | return LIBRARY_FUNCTION_ERROR; 912 | error = GRBgetintattr(Gurobidata->model, "NumQConstrs", &nqcons); 913 | if (error) 914 | return LIBRARY_FUNCTION_ERROR; 915 | allcons = ncons + nqcons; 916 | 917 | /* get the minimizer vector */ 918 | slack = (double*)malloc(sizeof(double) * (allcons)); 919 | error = GRBgetdblattrarray(Gurobidata->model, "Slack", 0, allcons, slack); 920 | 921 | if (error) 922 | return LIBRARY_FUNCTION_ERROR; 923 | 924 | setTensor(libData, slack, allcons, slackT); 925 | 926 | /* load return values */ 927 | MArgument_setMTensor(Res, slackT); 928 | 929 | // free stuff 930 | if (slack) 931 | free(slack); 932 | 933 | return LIBRARY_NO_ERROR; 934 | } 935 | --------------------------------------------------------------------------------