Files
ortools-clone/ortools/third_party_solvers/gurobi_parse_header.py
2025-08-22 14:24:48 +02:00

282 lines
9.1 KiB
Python

#!/usr/bin/env python3
# Copyright 2010-2025 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Gurobi header parser script to generate code for the environment.{cc|h}.
To use, run the script
copy gurobi_c.h somewhere.
edit the file and add the signature for the GRBisqp:
int __stdcall
GRBisqp(GRBenv**, const char*, const char*, const char*, int, const char*);
blaze run \
ortools/third_party_solvers/gurobi_parse_header \
-- <path to gurobi_c.h>
It will output all methods defined in the EXPORTED_FUNCTIONS field, and all
defined symbols.
The list of symbols to export is found by the following command:
grep -oh -e "GRB[A-Za-z0-9_]*" <path_to>/gurobi_interface.cc \
<path_to>/gurobi_proto_solver.* <path_to>/math_opt/solvers/gurobi* \
<path_to>/math_opt/solvers/gurobi/g_gurobi* | sort -u
This will printout on the console 3 sections:
------------------- header -------------------
to copy paste in environment.h
------------------- define -------------------
to copy in the define part of environment.cc
------------------- assign -------------------
to copy in the assign part of environment.cc
"""
import re
from typing import Sequence
from absl import app
EXPORTED_FUNCTIONS = frozenset(
[
"GRBaddconstr",
"GRBaddconstrs",
"GRBaddgenconstrAbs",
"GRBaddgenconstrAnd",
"GRBaddgenconstrIndicator",
"GRBaddgenconstrMax",
"GRBaddgenconstrMin",
"GRBaddgenconstrOr",
"GRBaddqconstr",
"GRBaddqpterms",
"GRBaddrangeconstr",
"GRBaddsos",
"GRBaddvar",
"GRBaddvars",
"GRBcbcut",
"GRBcbget",
"GRBcblazy",
"GRBcbsolution",
"GRBchgcoeffs",
"GRBcopyparams",
"GRBdelconstrs",
"GRBdelgenconstrs",
"GRBdelq",
"GRBdelqconstrs",
"GRBdelsos",
"GRBdelvars",
"GRBenv",
"GRBenvUniquePtr",
"GRBemptyenv",
"GRBgetnumparams",
"GRBgetparamtype",
"GRBgetparamname",
"GRBgetintparaminfo",
"GRBgetdblparaminfo",
"GRBgetstrparaminfo",
"GRBfreeenv",
"GRBfreemodel",
"GRBgetcharattrarray",
"GRBgetcharattrelement",
"GRBgetdblattr",
"GRBgetdblattrarray",
"GRBgetdblattrelement",
"GRBgetdblparam",
"GRBgetenv",
"GRBgeterrormsg",
"GRBgetintattr",
"GRBgetintattrarray",
"GRBgetintattrelement",
"GRBgetintparam",
"GRBgetstrattr",
"GRBgetstrparam",
"GRBgetvars",
"GRBisattravailable",
"GRBisqp",
"GRBloadenv",
"GRBmodel",
"GRBnewmodel",
"GRBoptimize",
"GRBcomputeIIS",
"GRBplatform",
"GRBresetparams",
"GRBsetcallbackfunc",
"GRBsetcharattrarray",
"GRBsetcharattrelement",
"GRBsetcharattrlist",
"GRBsetdblattr",
"GRBsetdblattrarray",
"GRBsetdblattrelement",
"GRBsetdblattrlist",
"GRBsetdblparam",
"GRBsetintattr",
"GRBsetintattrarray",
"GRBsetintattrelement",
"GRBsetintattrlist",
"GRBsetintparam",
"GRBsetobjectiven",
"GRBsetparam",
"GRBsetstrattr",
"GRBsetstrparam",
"GRBterminate",
"GRBupdatemodel",
"GRBversion",
"GRBwrite",
]
)
# TODO(user): Filter #define too.
class GurobiHeaderParser:
"""Converts gurobi_c.h to something pastable in ./environment.h|.cc."""
def __init__(self):
self.__header = ""
self.__define = ""
self.__assign = ""
self.__state = 0
self.__return_type = ""
self.__args = ""
self.__fun_name = ""
def should_be_exported(self, name: str) -> bool:
return name in EXPORTED_FUNCTIONS
def write_define(self, symbol: str, value: str) -> None:
self.__header += f"#define {symbol} {value}\n"
def write_fun(self, name: str, return_type: str, args: str) -> None:
if not self.should_be_exported(name):
print("skipping " + name)
return
self.__header += f"extern std::function<{return_type}({args})> {name};\n"
self.__define += f"std::function<{return_type}({args})> {name} = nullptr;\n"
self.__assign += f" gurobi_dynamic_library->GetFunction(&{name}, "
self.__assign += f'"{name}");\n'
def parse(self, filepath: str) -> None:
"""Main method to parser the gurobi header."""
with open(filepath) as fp:
all_lines = fp.read()
for line in all_lines.splitlines():
if not line: # Ignore empty lines.
continue
if re.fullmatch(r"/\*", line, re.M): # Ignore comments.
continue
if self.__state == 0:
# Note: fullmatch does not work.
match_def = re.match(r"#define ([A-Z0-9_]*)\s+([^/]+)", line, re.M)
if match_def:
self.write_define(match_def.group(1), match_def.group(2))
continue
# Single line function definition.
match_fun = re.fullmatch(
r"([a-z]+) __stdcall (GRB[A-Za-z_]*)\(([^;]*)\);", line, re.M
)
if match_fun:
self.write_fun(
match_fun.group(1), match_fun.group(2), match_fun.group(3)
)
continue
# Simple type declaration (i.e. int __stdcall).
match_fun = re.fullmatch(r"([a-z]+) __stdcall\s*$", line, re.M)
if match_fun:
self.__return_type = match_fun.group(1)
self.__state = 1
continue
# Complex type declaration with pointer (i.e. GRBModel* __stdcall).
match_fun = re.fullmatch(r"([A-Za-z ]+)\*\s*__stdcall\s*$", line, re.M)
if match_fun:
self.__return_type = match_fun.group(1) + "*"
self.__state = 1
continue
elif self.__state == 1: # The return type was defined at the line before.
# Function definition terminates in this line.
match_fun = re.fullmatch(r"\s*(GRB[A-Za-z_]*)\(([^;]+)\);", line, re.M)
if match_fun:
self.write_fun(
match_fun.group(1), self.__return_type, match_fun.group(2)
)
self.__state = 0
self.__return_type = ""
continue
# Function definition does not terminate in this line.
match_fun = re.fullmatch(r"\s*(GRB[A-Za-z_]*)\(([^;]+)$", line, re.M)
if match_fun:
self.__fun_name = match_fun.group(1)
self.__args = match_fun.group(2)
self.__state = 2
continue
elif self.__state == 2: # Extra arguments.
# Arguments end in this line.
match_fun = re.fullmatch(r"\s*([^;]+)\);", line, re.M)
if match_fun:
self.__args += match_fun.group(1)
self.write_fun(self.__fun_name, self.__return_type, self.__args)
self.__args = ""
self.__fun_name = ""
self.__return_type = ""
self.__state = 0
continue
# Arguments do not end in this line.
match_fun = re.fullmatch(r"\s*([^;]+)$", line, re.M)
if match_fun:
self.__args += match_fun.group(1)
continue
def output(self) -> None:
"""Output the 3 generated code on standard out."""
# replace __stdcall by GUROBI_STDCALL.
self.__header = self.__header.replace("__stdcall", "GUROBI_STDCALL")
self.__define = self.__define.replace("__stdcall", "GUROBI_STDCALL")
print("------------------- header -------------------")
print(self.__header)
print("------------------- define -------------------")
print(self.__define)
print("------------------- assign -------------------")
print(self.__assign)
def main(argv: Sequence[str]) -> None:
if len(argv) > 2:
raise app.UsageError("Too many command-line arguments.")
if len(argv) == 1:
raise app.UsageError("Please supply path to gurobi_c.h on the command line.")
parser = GurobiHeaderParser()
parser.parse(argv[1])
parser.output()
if __name__ == "__main__":
app.run(main)