├── .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 |
--------------------------------------------------------------------------------