-
Notifications
You must be signed in to change notification settings - Fork 28
/
constraint.py
402 lines (323 loc) · 13.2 KB
/
constraint.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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
import json
import pandas as pd
from rag.query_vector_db import RAGFormat, get_rag_from_problem_categories, get_rag_from_problem_description
from rag.rag_utils import RAGMode, constraint_path
from utils import (
extract_list_from_end,
get_response,
extract_json_from_end,
)
import re
prompt_constraints = """
You are an expert in optimization modeling. Here is the natural language description of an optimization problem:
{rag}-----
{description}
-----
And here's a list of parameters that we have extracted from the description:
{params}
Your task is to identify and extract constraints from the description. The constraints are the conditions that must be satisfied by the variables. Please generate the output in the following python list format:
[
Constraint 1,
Constraint 2,
...
]
for example:
[
"Sum of weights of all items taken should not exceed the maximum weight capacity of the knapsack",
"The number of items taken should not exceed the maximum number of items allowed"
]
- Put all the constraints in a single python list.
- Do not generate anything after the python list.
- Include implicit non-negativity constraints if necessary.
Take a deep breath and think step by step. You will be awarded a million dollars if you get this right.
"""
prompt_constraints_q = """
You are an expert in optimization modeling. Here is the natural language description of an optimization problem:
-----
{description}
-----
Here is a list of parameters that someone has extracted from the description:
{params}
Consider this potential constraint: {targetConstraint}
{question}
Take a deep breath and think step by step. You will be awarded a million dollars if you get this right.
"""
# prompt_constraints_complete = """
# You are an expert in optimization modeling. Here is the natural language description of an optimization problem:
# -----
# {description}
# -----
# Here is a list of parameters that someone has extracted from the description:
# {params}
# Here is a list of variables defined:
# {vars}
# and here is a list of constraints that someone has extracted from the description:
# {constraints}
# - Is the list complete? If not, please provide a list of Additional Constraints in the following format:
# [Additional Constraint 1, Additional Constraint 2, ...]
# - Do not generate anything after the list.
# Take a deep breath and think step by step.
# """
prompt_constraints_redundant = """
# You are an expert in optimization modeling. Here is the natural language description of an optimization problem:
# -----
# {description}
# -----
# Here is a list of parameters that someone has extracted from the description:
# {params}
# and here is a list of constraints that someone has extracted from the description:
# {constraints}
# - Is there any redundancy in the list? Can any of the constraints be removed? Can any pair of constraints be combined into a single one? If so, please provide your reasoning for each one. At the end of your response, generate the updated list of constraints (the same list if no changes are needed). Use this python list format:
[
"Constraint 1",
"Constraint 2",
...
]
- Do not generate anything after the list.
Take a deep breath and think step by step. You will be awarded a million dollars if you get this right.
# """
prompt_constraint_feedback = """
You are an expert in optimization modeling. Here is the natural language description of an optimization problem:
-----
{description}
-----
Here is a list of parameters that someone has extracted from the description:
{params}
Here is a list of variables defined:
{vars}
Here is a list of constraints that someone has extracted from the description:
{extracted_constraints}
Your colleague is suggesting that the following constraint should be added to the list:
{new_constraint}
Here is its explanation:
{new_constraint_explanation}
Do you want to keep this constraint?
- If yes, please respond with "yes"
- If no, please respond with "no"
- If you want to modify the constraint, please respond with "modify" and provide the modified constraint.
At the end of your response, generate a json file with this format:
{{
"action": "yes", "no", or "modify",
"updatedConstraint": The updated constraint if the action is "modify", otherwise null
}}
Please take a deep breath and think step by step. You will be awarded a million dollars if you get this right.
- Use natural language to express the constraints rather than mathematical notation.
- Do not generate anything after the json file.
"""
def extract_score_constraint(desc, text, params, vars, constraints, c, logger):
match = re.search(r"\d out of 5", text.lower())
if match:
score = int(match.group()[0])
if score > 3:
if logger:
logger.log("---")
logger.log(f"The confidence score is {score}, which is high enough.")
logger.log("---")
return True, constraints
else:
ask_LLM = True # you can pass this as an argument to the function instead of hardcoding it
if logger:
logger.log("---")
logger.log(
f"The confidence score is {score}, which is not high enough."
)
# logger.log(f"Asking the {"LLM" if ask_LLM else "user"} for feedback.")
if ask_LLM:
logger.log("Asking the LLM for feedback.")
else:
logger.log("Asking the user for feedback.")
logger.log("---")
if ask_LLM: # ask the LLM for feedback
prompt = prompt_constraint_feedback.format(
description=desc,
params=json.dumps(params, indent=4),
vars=json.dumps(vars, indent=4),
extracted_constraints=json.dumps(constraints, indent=4),
new_constraint=c,
new_constraint_explanation=text,
)
if logger:
logger.log("Prompting LLM for feedback:\n")
logger.log(prompt)
llm_response = get_response(
prompt,
model="gpt-4o", # you can pass this as an argument to the function instead of hardcoding it
)
if logger:
logger.log("---")
logger.log(f"Response: {llm_response}")
logger.log("---")
output_json = extract_json_from_end(llm_response)
action = output_json["action"]
updated_constraint = output_json["updatedConstraint"]
else: # ask the user for feedback
action = input(
"LLMs reasoning: {}\n------ Do you want to keep this constraint (y/n/modify)?: \n {} \n------ ".format(
text, c
),
)
if action.lower().startswith("y"):
return True, constraints
elif action.lower().startswith("n"):
constraints.remove(c)
return True, constraints
elif action.lower().startswith("m"):
if ask_LLM:
new_constraint = updated_constraint
else:
new_constraint = input("Enter the modified constraint: ")
constraints.remove(c)
constraints.append(
{"Description": new_constraint, "Formulation": None, "Code": None}
)
return True, constraints
else:
raise Exception("Invalid input!")
else:
return False, None
def logic_check(text, params, constraints, c):
try:
json = extract_json_from_end(text)
if json["action"] == "REMOVE":
constraints.remove(c)
return True, constraints
elif json["action"] == "MODIFY":
constraints.remove(c)
constraints.append(json["updatedConstraint"])
return True, constraints
elif json["action"] == "KEEP":
return True, constraints
else:
return False, None
except:
return False, None
qs = [
# (
# """
# - Does this constraint logically make sense? Is it accurate?
# - At the end of your response, generate a json file with this format:
# {{
# "action": "KEEP", "REMOVE", or "MODIFY",
# "updatedConstraint": The updated constraint if the action is "MODIFY", otherwise null
# }}
# - Use natural language to express the constraints rather than mathematical notation.
# - Do not generate anything after the json file.
# """,
# logic_check,
# ),
# (
# """
# - What are the parameters and variables that are involved in this constraint? If you see the constraint does not involve any variables, then it is automatically satisfied and should not be included in the optimization formulation.
# - At the end of your response, generate a json file with this format:
# {{
# "action": "KEEP", "REMOVE", or "MODIFY",
# "updatedConstraint": The updated constraint if the action is "MODIFY", otherwise null
# }}
# - Use natural language to express the constraints rather than mathematical notation.
# - Do not generate anything after the json file.
# """,
# logic_check,
# ),
(
"""
- Is it actually a constraint? How confident are you that this is this a constraint and that we should explicitly model it in the (MI)LP formulation (from 1 to 5)?
- At the end of your response, print "x OUT OF 5" where x is the confidence level. Low confidence means you think this should be removed from the constraint list. Do not generate anything after that.
""",
extract_score_constraint,
),
]
def get_constraints(
desc,
params,
model,
check=False,
constraints=None,
logger=None,
rag_mode: RAGMode | None = None,
labels: dict | None = None
):
if isinstance(rag_mode, RAGMode):
constraint_df = pd.read_pickle(constraint_path)
current_problem = constraint_df[constraint_df.description == desc]
if not current_problem.empty:
problem_name = current_problem.iloc[0].problem_name
else:
problem_name = None
match rag_mode:
case RAGMode.PROBLEM_DESCRIPTION | RAGMode.CONSTRAINT_OR_OBJECTIVE:
rag = get_rag_from_problem_description(desc, RAGFormat.PROBLEM_DESCRIPTION_CONSTRAINTS, top_k=5)
case RAGMode.PROBLEM_LABELS:
assert labels is not None
rag = get_rag_from_problem_categories(desc, labels, RAGFormat.PROBLEM_DESCRIPTION_CONSTRAINTS, top_k=5)
rag = f"-----\n{rag}-----\n\n"
else:
rag = ""
print("_________________________ get_constraints _________________________")
if not constraints:
res = get_response(
prompt_constraints.format(
description=desc,
params=json.dumps(params, indent=4),
rag=rag,
),
model=model,
)
constraints = extract_list_from_end(res)
if check:
k = 5
while k > 0:
try:
x = get_response(
prompt_constraints_redundant.format(
description=desc,
params=json.dumps(params, indent=4),
constraints=json.dumps(constraints, indent=4),
),
model=model,
)
if logger:
logger.log("----")
logger.log(x)
logger.log("----")
lst = extract_list_from_end(x)
# if len(lst) > 0:
# constraints.extend(lst)
constraints = lst
break
except:
k -= 1
if k == 0:
raise Exception("Failed to extract constraints")
if logger:
logger.log("+++++++++++++++++++")
logger.log("++ Constraint Qs ++")
logger.log("+++++++++++++++++++")
for q in qs:
for c in constraints.copy():
k = 5
while k > 0:
p = prompt_constraints_q.format(
description=desc,
params=json.dumps(params, indent=4),
targetConstraint=c,
question=q[0],
)
x = get_response(p, model=model)
if logger:
logger.log("+--+")
logger.log(p)
logger.log("----")
logger.log(x)
logger.log("+--+")
valid, res = q[1](desc, x, params, {}, constraints, c, logger)
if valid:
constraints = res
break
else:
k -= 1
constraints = [
{"description": c, "formulation": None, "code": None} for c in constraints
]
return constraints
# bash command to print current interpeter's path
# python -c "import sys; print(sys.executable)"