-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathparse_header_xpress.py
330 lines (263 loc) · 15.7 KB
/
parse_header_xpress.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#!/usr/bin/env python3
"""Xpress header parser script to generate code for the environment.{cc|h}.
To use, run the script
./parse_header_xpress.py <path to xprs.h>
This will printout on the console 9 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
------------------- string parameters -------------------
to copy in the "getMapStringControls" function of linear_solver/xpress_interface.cc
------------------- string parameters tests -------------------
to copy in the "setStringControls" TEST of linear_solver/unittests/xpress_interface.cc
------------------- double parameters -------------------
to copy in the "getMapDoubleControls" function of linear_solver/xpress_interface.cc
------------------- double parameters tests -------------------
to copy in the "setDoubleControls" TEST of linear_solver/unittests/xpress_interface.cc
------------------- int parameters -------------------
to copy in the "getMapIntControls" function of linear_solver/xpress_interface.cc
------------------- int parameters tests -------------------
to copy in the "setIntControl" TEST of linear_solver/unittests/xpress_interface.cc
------------------- int64 parameters -------------------
to copy in the "getMapInt64Controls" function of linear_solver/xpress_interface.cc
------------------- int64 parameters tests -------------------
to copy in the "setInt64Control" TEST of linear_solver/unittests/xpress_interface.cc
"""
import argparse
import re
from enum import Enum
# from absl import app
# This enum is used to detect different sections in the xprs.h document
class XprsDocumentSection(Enum):
STRING_PARAMS = 1
DOUBLE_PARAMS = 2
INT_PARAMS = 3
INT64_PARAMS = 4
OTHER = 5
class XpressHeaderParser(object):
"""Converts xprs.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 = ''
self.__string_parameters = ''
self.__string_parameters_unittest = ''
self.__double_parameters = ''
self.__double_parameters_unittest = ''
self.__int_parameters = ''
self.__int_parameters_unittest = ''
self.__int64_parameters = ''
self.__int64_parameters_unittest = ''
# These are the definitions required for compiling the XPRESS interface, excluding control parameters
self.__required_defines = {"XPRS_STOP_USER", "XPRS_TYPE_NOTDEFINED", "XPRS_TYPE_INT", "XPRS_TYPE_INT64",
"XPRS_TYPE_DOUBLE", "XPRS_PLUSINFINITY", "XPRS_MINUSINFINITY", "XPRS_MAXBANNERLENGTH", "XPVERSION",
"XPRS_LPOBJVAL", "XPRS_MIPOBJVAL", "XPRS_BESTBOUND", "XPRS_OBJRHS", "XPRS_OBJSENSE",
"XPRS_ROWS", "XPRS_SIMPLEXITER", "XPRS_LPSTATUS", "XPRS_MIPSTATUS", "XPRS_NODES",
"XPRS_COLS", "XPRS_LP_OPTIMAL", "XPRS_LP_INFEAS", "XPRS_LP_UNBOUNDED",
"XPRS_MIP_SOLUTION", "XPRS_MIP_INFEAS", "XPRS_MIP_OPTIMAL", "XPRS_MIP_UNBOUNDED",
"XPRS_OBJ_MINIMIZE", "XPRS_OBJ_MAXIMIZE", "XPRS_NAMES_ROW", "XPRS_NAMES_COLUMN"}
self.__missing_required_defines = self.__required_defines
# These enum will detect control parameters that will all be imported
self.__doc_section = XprsDocumentSection.OTHER
# These parameters are not supported
self.__excluded_defines = {"XPRS_COMPUTE"}
# These are the functions required for compiling the XPRESS interface
self.__required_functions = {"XPRScreateprob", "XPRSdestroyprob", "XPRSinit", "XPRSfree", "XPRSgetlicerrmsg",
"XPRSlicense", "XPRSgetbanner", "XPRSgetversion", "XPRSsetdefaultcontrol",
"XPRSsetintcontrol", "XPRSsetintcontrol64", "XPRSsetdblcontrol",
"XPRSsetstrcontrol", "XPRSgetintcontrol", "XPRSgetintcontrol64",
"XPRSgetdblcontrol", "XPRSgetstringcontrol", "XPRSgetintattrib",
"XPRSgetdblattrib", "XPRSloadlp", "XPRSloadlp64", "XPRSgetobj", "XPRSgetrhs",
"XPRSgetrhsrange", "XPRSgetlb", "XPRSgetub", "XPRSgetcoef", "XPRSaddrows",
"XPRSdelrows", "XPRSaddcols", "XPRSaddnames", "XPRSgetnames", "XPRSdelcols", "XPRSchgcoltype", "XPRSloadbasis",
"XPRSpostsolve", "XPRSchgobjsense", "XPRSgetlasterror", "XPRSgetbasis",
"XPRSwriteprob", "XPRSgetrowtype", "XPRSgetcoltype", "XPRSgetlpsol",
"XPRSgetmipsol", "XPRSchgbounds", "XPRSchgobj", "XPRSchgcoef", "XPRSchgmcoef",
"XPRSchgrhs", "XPRSchgrhsrange", "XPRSchgrowtype", "XPRSaddcbmessage", "XPRSsetcbmessage",
"XPRSaddmipsol", "XPRSaddcbintsol", "XPRSremovecbintsol",
"XPRSinterrupt", "XPRSlpoptimize", "XPRSmipoptimize", "XPRSsetindicators"}
self.__missing_required_functions = self.__required_functions
self.__XPRSprob_section = False
def write_define(self, symbol, value):
if symbol in self.__excluded_defines:
print('skipping ' + symbol)
return
# If it is a control parameter, import it to expose it to the user
# Else import it only if required
if self.__doc_section in [XprsDocumentSection.STRING_PARAMS, XprsDocumentSection.DOUBLE_PARAMS,
XprsDocumentSection.INT_PARAMS, XprsDocumentSection.INT64_PARAMS]:
self.__header += f'#define {symbol} {value}\n'
ortools_symbol = symbol.replace("XPRS_", "")
if self.__doc_section == XprsDocumentSection.STRING_PARAMS:
self.__string_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n'
self.__string_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, "default_value"}},\n'
elif self.__doc_section == XprsDocumentSection.DOUBLE_PARAMS:
self.__double_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n'
self.__double_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, 1.}},\n'
elif self.__doc_section == XprsDocumentSection.INT_PARAMS:
self.__int_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n'
self.__int_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, 1}},\n'
elif self.__doc_section == XprsDocumentSection.INT64_PARAMS:
self.__int64_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n'
self.__int64_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, 1}},\n'
elif symbol in self.__required_defines:
self.__header += f'#define {symbol} {value}\n'
self.__missing_required_defines.remove(symbol)
else:
print('skipping ' + symbol)
def write_fun(self, return_type, name, args):
if name in self.__required_functions:
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' xpress_dynamic_library->GetFunction(&{name}, '
self.__assign += f'"{name}");\n'
self.__missing_required_functions.remove(name)
else:
print('skipping ' + name)
def parse(self, filepath):
"""Main method to parser the Xpress header."""
with open(filepath) as fp:
all_lines = fp.read()
self.__XPRSprob_section = False
for line in all_lines.splitlines():
if not line: # Ignore empty lines.
continue
self.detect_XPRSprob_section(line)
if re.match(r'/\*', line, re.M): # Comments in xprs.h indicate the section
if self.__XPRSprob_section:
if "string control parameters" in line.lower():
self.__doc_section = XprsDocumentSection.STRING_PARAMS
elif "double control parameters" in line.lower():
self.__doc_section = XprsDocumentSection.DOUBLE_PARAMS
elif "integer control parameters" in line.lower() and "64-bit" in line.lower():
self.__doc_section = XprsDocumentSection.INT64_PARAMS
elif "integer control parameters" in line.lower():
self.__doc_section = XprsDocumentSection.INT_PARAMS
else:
self.__doc_section = XprsDocumentSection.OTHER
else:
self.__doc_section = XprsDocumentSection.OTHER
if self.__state == 0:
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.match(
r'([a-z]+) XPRS_CC (XPRS[A-Za-z0-9_]*)\(([^;]*)\);', 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 XPRS_CC).
match_fun = re.match(r'([a-z]+) XPRS_CC\s*$', line, re.M)
if match_fun:
self.__return_type = match_fun.group(1)
self.__state = 1
continue
# Complex type declaration with pointer.
match_fun = re.match(r'([A-Za-z0-9 ]+)\*\s*XPRS_CC\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.match(r'\s*(XPRS[A-Za-z0-9_]*)\(([^;]+)\);', 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.match(r'\s*(XPRS[A-Za-z0-9_]*)\(([^;]+)$', 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.match(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.match(r'\s*([^;]+)$', line, re.M)
if match_fun:
self.__args += match_fun.group(1)
continue
def detect_XPRSprob_section(self, line):
"""This method detects the section between these commented lines:
/***************************************************************************\
* control parameters for XPRSprob *
...
/***************************************************************************\
"""
if " * control parameters for XPRSprob" in line:
self.__XPRSprob_section = True
elif self.__XPRSprob_section and \
"/***************************************************************************\\" in line:
self.__XPRSprob_section = False
def output(self):
"""Output the 3 generated code on standard out."""
print('------------------- header (to copy in environment.h) -------------------')
print(self.__header)
print('------------------- define (to copy in the define part of environment.cc) -------------------')
print(self.__define)
print('------------------- assign (to copy in the assign part of environment.cc) -------------------')
print(self.__assign)
print('------------------- string params (to copy in the "getMapStringControls" function of linear_solver/xpress_interface.cc) -------------------')
print(self.__string_parameters)
print('------------------- string params test (to copy in the "setStringControls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------')
print(self.__string_parameters_unittest)
print('------------------- double params (to copy in the "getMapDoubleControls" function of linear_solver/xpress_interface.cc) -------------------')
print(self.__double_parameters)
print('------------------- double params test (to copy in the "setDoubleControls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------')
print(self.__double_parameters_unittest)
print('------------------- int params (to copy in the "getMapIntControls" function of linear_solver/xpress_interface.cc) -------------------')
print(self.__int_parameters)
print('------------------- int params test (to copy in the "setIntControls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------')
print(self.__int_parameters_unittest)
print('------------------- int64 params (to copy in the "getMapInt64Controls" function of linear_solver/xpress_interface.cc) -------------------')
print(self.__int64_parameters)
print('------------------- int64 params test (to copy in the "setInt64Controls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------')
print(self.__int64_parameters_unittest)
def print_missing_elements(self):
if self.__missing_required_defines:
print('------WARNING------ missing required defines -------------------')
print(self.__missing_required_defines)
if self.__missing_required_functions:
print('------WARNING------ missing required functions -------------------')
print(self.__missing_required_functions)
if self.__missing_required_defines or self.__missing_required_functions:
raise LookupError("Some required defines or functions are missing (see detail above)")
def main(path: str) -> None:
parser = XpressHeaderParser()
parser.parse(path)
parser.output()
parser.print_missing_elements()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Xpress header parser.')
parser.add_argument('filepath', type=str)
args = parser.parse_args()
main(args.filepath)