text2model 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. text2model/__init__.py +1 -0
  2. text2model/grammar.mzn +277 -0
  3. text2model/knowledge_graphs/CSPLib_12.ttl +41 -0
  4. text2model/knowledge_graphs/CSPLib_14.ttl +51 -0
  5. text2model/knowledge_graphs/CSPLib_2.ttl +59 -0
  6. text2model/knowledge_graphs/CSPLib_22.ttl +62 -0
  7. text2model/knowledge_graphs/CSPLib_28.ttl +28 -0
  8. text2model/knowledge_graphs/CSPLib_30.ttl +72 -0
  9. text2model/knowledge_graphs/CSPLib_34.ttl +52 -0
  10. text2model/knowledge_graphs/CSPLib_39.ttl +51 -0
  11. text2model/knowledge_graphs/CSPLib_54.ttl +50 -0
  12. text2model/knowledge_graphs/CSPLib_6.ttl +36 -0
  13. text2model/knowledge_graphs/CSPLib_8.ttl +53 -0
  14. text2model/knowledge_graphs/LPWP_128.ttl +79 -0
  15. text2model/knowledge_graphs/LPWP_232.ttl +95 -0
  16. text2model/knowledge_graphs/LPWP_53.ttl +58 -0
  17. text2model/knowledge_graphs/LPWP_66.ttl +52 -0
  18. text2model/knowledge_graphs/LPWP_85.ttl +95 -0
  19. text2model/knowledge_graphs/OR_LP_IP_Scheduling_problem_1.ttl +34 -0
  20. text2model/knowledge_graphs/OR_LP_IP_Scheduling_problem_18.ttl +59 -0
  21. text2model/knowledge_graphs/OR_LP_IP_Scheduling_problem_2.ttl +43 -0
  22. text2model/knowledge_graphs/OR_LP_IP_Scheduling_problem_28.ttl +42 -0
  23. text2model/knowledge_graphs/OR_LP_IP_Scheduling_problem_31.ttl +44 -0
  24. text2model/knowledge_graphs/OR_LP_IP_Scheduling_problem_4.ttl +48 -0
  25. text2model/knowledge_graphs/OR_LP_IP_Scheduling_problem_5.ttl +40 -0
  26. text2model/knowledge_graphs/OR_LP_IP_Scheduling_problem_6.ttl +61 -0
  27. text2model/knowledge_graphs/OR_LP_IP_Scheduling_problem_8.ttl +54 -0
  28. text2model/knowledge_graphs/combinatorial_problem_1.ttl +48 -0
  29. text2model/knowledge_graphs/combinatorial_problem_31.ttl +43 -0
  30. text2model/knowledge_graphs/combinatorial_problem_32.ttl +39 -0
  31. text2model/knowledge_graphs/combinatorial_problem_7.ttl +52 -0
  32. text2model/knowledge_graphs/complexor_AircraftAssignment.ttl +73 -0
  33. text2model/knowledge_graphs/complexor_AircraftLanding.ttl +53 -0
  34. text2model/knowledge_graphs/complexor_CFLP.ttl +43 -0
  35. text2model/knowledge_graphs/complexor_CapacitatedWarehouseLocation.ttl +59 -0
  36. text2model/knowledge_graphs/complexor_CarSelection.ttl +30 -0
  37. text2model/knowledge_graphs/complexor_TSP.ttl +52 -0
  38. text2model/knowledge_graphs/complexor_VRPTW.ttl +67 -0
  39. text2model/knowledge_graphs/nlp4lp_1.ttl +56 -0
  40. text2model/knowledge_graphs/nlp4lp_10.ttl +46 -0
  41. text2model/knowledge_graphs/nlp4lp_11.ttl +57 -0
  42. text2model/knowledge_graphs/nlp4lp_12.ttl +48 -0
  43. text2model/knowledge_graphs/nlp4lp_13.ttl +48 -0
  44. text2model/knowledge_graphs/nlp4lp_14.ttl +82 -0
  45. text2model/knowledge_graphs/nlp4lp_15.ttl +60 -0
  46. text2model/knowledge_graphs/nlp4lp_16.ttl +64 -0
  47. text2model/knowledge_graphs/nlp4lp_17.ttl +72 -0
  48. text2model/knowledge_graphs/nlp4lp_18.ttl +64 -0
  49. text2model/knowledge_graphs/nlp4lp_19.ttl +98 -0
  50. text2model/knowledge_graphs/nlp4lp_2.ttl +103 -0
  51. text2model/knowledge_graphs/nlp4lp_20.ttl +129 -0
  52. text2model/knowledge_graphs/nlp4lp_21.ttl +65 -0
  53. text2model/knowledge_graphs/nlp4lp_22.ttl +72 -0
  54. text2model/knowledge_graphs/nlp4lp_23.ttl +63 -0
  55. text2model/knowledge_graphs/nlp4lp_24.ttl +99 -0
  56. text2model/knowledge_graphs/nlp4lp_25.ttl +77 -0
  57. text2model/knowledge_graphs/nlp4lp_27.ttl +75 -0
  58. text2model/knowledge_graphs/nlp4lp_28.ttl +50 -0
  59. text2model/knowledge_graphs/nlp4lp_29.ttl +63 -0
  60. text2model/knowledge_graphs/nlp4lp_3.ttl +49 -0
  61. text2model/knowledge_graphs/nlp4lp_30.ttl +49 -0
  62. text2model/knowledge_graphs/nlp4lp_31.ttl +35 -0
  63. text2model/knowledge_graphs/nlp4lp_32.ttl +33 -0
  64. text2model/knowledge_graphs/nlp4lp_33.ttl +49 -0
  65. text2model/knowledge_graphs/nlp4lp_34.ttl +67 -0
  66. text2model/knowledge_graphs/nlp4lp_35.ttl +75 -0
  67. text2model/knowledge_graphs/nlp4lp_36.ttl +56 -0
  68. text2model/knowledge_graphs/nlp4lp_37.ttl +52 -0
  69. text2model/knowledge_graphs/nlp4lp_38.ttl +55 -0
  70. text2model/knowledge_graphs/nlp4lp_39.ttl +58 -0
  71. text2model/knowledge_graphs/nlp4lp_4.ttl +64 -0
  72. text2model/knowledge_graphs/nlp4lp_40.ttl +49 -0
  73. text2model/knowledge_graphs/nlp4lp_41.ttl +62 -0
  74. text2model/knowledge_graphs/nlp4lp_42.ttl +49 -0
  75. text2model/knowledge_graphs/nlp4lp_43.ttl +98 -0
  76. text2model/knowledge_graphs/nlp4lp_44.ttl +41 -0
  77. text2model/knowledge_graphs/nlp4lp_45.ttl +56 -0
  78. text2model/knowledge_graphs/nlp4lp_46.ttl +35 -0
  79. text2model/knowledge_graphs/nlp4lp_47.ttl +68 -0
  80. text2model/knowledge_graphs/nlp4lp_48.ttl +74 -0
  81. text2model/knowledge_graphs/nlp4lp_49.ttl +66 -0
  82. text2model/knowledge_graphs/nlp4lp_5.ttl +69 -0
  83. text2model/knowledge_graphs/nlp4lp_50.ttl +51 -0
  84. text2model/knowledge_graphs/nlp4lp_51.ttl +53 -0
  85. text2model/knowledge_graphs/nlp4lp_52.ttl +51 -0
  86. text2model/knowledge_graphs/nlp4lp_53.ttl +55 -0
  87. text2model/knowledge_graphs/nlp4lp_54.ttl +74 -0
  88. text2model/knowledge_graphs/nlp4lp_55.ttl +50 -0
  89. text2model/knowledge_graphs/nlp4lp_56.ttl +67 -0
  90. text2model/knowledge_graphs/nlp4lp_57.ttl +59 -0
  91. text2model/knowledge_graphs/nlp4lp_58.ttl +65 -0
  92. text2model/knowledge_graphs/nlp4lp_6.ttl +63 -0
  93. text2model/knowledge_graphs/nlp4lp_60.ttl +36 -0
  94. text2model/knowledge_graphs/nlp4lp_61.ttl +83 -0
  95. text2model/knowledge_graphs/nlp4lp_62.ttl +44 -0
  96. text2model/knowledge_graphs/nlp4lp_63.ttl +49 -0
  97. text2model/knowledge_graphs/nlp4lp_64.ttl +41 -0
  98. text2model/knowledge_graphs/nlp4lp_65.ttl +78 -0
  99. text2model/knowledge_graphs/nlp4lp_66.ttl +51 -0
  100. text2model/knowledge_graphs/nlp4lp_67.ttl +40 -0
  101. text2model/knowledge_graphs/nlp4lp_7.ttl +53 -0
  102. text2model/knowledge_graphs/nlp4lp_8.ttl +51 -0
  103. text2model/knowledge_graphs/nlp4lp_9.ttl +58 -0
  104. text2model/knowledge_graphs/non_linear_problem_10.ttl +57 -0
  105. text2model/knowledge_graphs/non_linear_problem_15.ttl +55 -0
  106. text2model/knowledge_graphs/non_linear_problem_23.ttl +42 -0
  107. text2model/knowledge_graphs/non_linear_problem_9.ttl +61 -0
  108. text2model/knowledge_graphs/puzzles_problem_1.ttl +64 -0
  109. text2model/knowledge_graphs/puzzles_problem_11.ttl +65 -0
  110. text2model/knowledge_graphs/puzzles_problem_12.ttl +79 -0
  111. text2model/knowledge_graphs/puzzles_problem_13.ttl +50 -0
  112. text2model/knowledge_graphs/puzzles_problem_31.ttl +62 -0
  113. text2model/main.py +771 -0
  114. text2model/prompts/assembler_prompt.txt +104 -0
  115. text2model/prompts/code_stitching_prompt.txt +49 -0
  116. text2model/prompts/code_validation_prompt.txt +36 -0
  117. text2model/prompts/constraint_generation_prompt.txt +35 -0
  118. text2model/prompts/cot_prompt.txt +45 -0
  119. text2model/prompts/global_constraint_prompts/all_different_prompt.txt +67 -0
  120. text2model/prompts/global_constraint_prompts/count_prompt.txt +75 -0
  121. text2model/prompts/global_constraint_prompts/cumulative_prompt.txt +88 -0
  122. text2model/prompts/global_constraint_prompts/global_cardinality_prompt.txt +76 -0
  123. text2model/prompts/global_constraint_prompts/increasing_prompt.txt +81 -0
  124. text2model/prompts/global_constraint_prompts/lex_less_prompt.txt +68 -0
  125. text2model/prompts/grammar_validation_prompt.txt +18 -0
  126. text2model/prompts/kg_code_generation_prompt.txt +20 -0
  127. text2model/prompts/kg_generation_prompt.txt +40 -0
  128. text2model/prompts/objective_generation_prompt.txt +34 -0
  129. text2model/prompts/parameter_and_variable_generation_prompt.txt +35 -0
  130. text2model/utils.py +350 -0
  131. text2model-0.1.0.dist-info/METADATA +221 -0
  132. text2model-0.1.0.dist-info/RECORD +135 -0
  133. text2model-0.1.0.dist-info/WHEEL +5 -0
  134. text2model-0.1.0.dist-info/entry_points.txt +2 -0
  135. text2model-0.1.0.dist-info/top_level.txt +1 -0
text2model/main.py ADDED
@@ -0,0 +1,771 @@
1
+ import argparse
2
+ import ast
3
+ import json
4
+ import os
5
+ import sys
6
+ import tempfile
7
+ import time
8
+ from pathlib import Path
9
+
10
+ import openai
11
+ from datasets import DatasetDict, load_dataset
12
+ from langchain_ollama import ChatOllama
13
+ from tqdm import tqdm
14
+
15
+ from text2model import utils
16
+
17
+
18
+ ###########################################################
19
+ # Single-call Strategy
20
+ ###########################################################
21
+ def run_baseline_strategy(client, model, problem, problem_identifier, output_dir):
22
+ """Run the baseline single-prompt strategy"""
23
+ try:
24
+ prompt = utils.create_baseline_prompt(problem)
25
+ solution = utils.call_api(client, model, prompt)
26
+
27
+ if solution:
28
+ utils.save_solution(output_dir, problem_identifier, solution)
29
+ return True
30
+ return False
31
+ except Exception as e:
32
+ print(f"Error in baseline strategy for problem {problem_identifier}: {e}")
33
+ return False
34
+
35
+
36
+ ###########################################################
37
+ # Two-call Strategies
38
+ ###########################################################
39
+ def run_knowledge_graph_strategy(client, model, problem, problem_identifier, output_dir):
40
+ """Run the strategy using knowledge graphs"""
41
+ try:
42
+ kg_path = utils._resolve_path(f"knowledge_graphs/{problem_identifier}.ttl")
43
+ if not kg_path.exists():
44
+ print(f"Knowledge graph not found for problem {problem_identifier}")
45
+ return False
46
+
47
+ problem_data = utils.prepare_problem_data(problem)
48
+ effective_input_data = utils.get_effective_input_data(problem_data)
49
+ knowledge_graph = utils.load_file(str(kg_path))
50
+
51
+ kg_prompt = utils.load_file('prompts/kg_code_generation_prompt.txt')
52
+ prompt = kg_prompt.format(
53
+ problem_description=problem_data['description'],
54
+ knowledge_graph=knowledge_graph,
55
+ input_data=effective_input_data
56
+ )
57
+
58
+ solution = utils.call_api(client, model, prompt)
59
+
60
+ if solution:
61
+ utils.save_solution(output_dir, problem_identifier, solution)
62
+ return True
63
+ return False
64
+
65
+ except Exception as e:
66
+ print(f"Error in knowledge graph strategy for problem {problem_identifier}: {e}")
67
+ return False
68
+
69
+
70
+ def run_cot_with_code_validation_strategy(client, model, problem, problem_identifier, output_dir):
71
+ """Run the cot strategy with conditional code validation (only if compilation fails)"""
72
+ try:
73
+ problem_data = utils.prepare_problem_data(problem)
74
+ effective_input_data = utils.get_effective_input_data(problem_data)
75
+
76
+ cot_prompt = utils.load_file('prompts/cot_prompt.txt')
77
+ initial_code = utils.call_api(
78
+ client,
79
+ model,
80
+ cot_prompt.format(
81
+ problem_description=problem_data['description'],
82
+ input_data=effective_input_data
83
+ )
84
+ )
85
+
86
+ if not initial_code:
87
+ return False
88
+
89
+ current_code = initial_code
90
+
91
+ dzn_data = problem.get('data.dzn') or ""
92
+ syntax_error_message = utils.check_syntax(initial_code, dzn_data)
93
+
94
+ if syntax_error_message:
95
+ time.sleep(2)
96
+
97
+ validation_prompt = utils.load_file('prompts/code_validation_prompt.txt')
98
+ validated_code = utils.call_api(
99
+ client,
100
+ model,
101
+ validation_prompt.format(
102
+ problem_description=problem_data['description'],
103
+ input_data=effective_input_data,
104
+ objective_type=problem_data['objective_type'],
105
+ final_code=initial_code,
106
+ syntax_error_message=syntax_error_message
107
+ )
108
+ )
109
+
110
+ if validated_code:
111
+ current_code = validated_code
112
+
113
+ utils.save_solution(output_dir, problem_identifier, current_code)
114
+ return True
115
+
116
+ except Exception as e:
117
+ print(f"Error in two-stage strategy for problem {problem_identifier}: {e}")
118
+ return False
119
+
120
+
121
+ def run_cot_strategy(client, model, problem, problem_identifier, output_dir):
122
+ """Run the Chain of Thought strategy (single-stage)"""
123
+ try:
124
+ problem_data = utils.prepare_problem_data(problem)
125
+ effective_input_data = utils.get_effective_input_data(problem_data)
126
+
127
+ cot_prompt = utils.load_file('prompts/cot_prompt.txt')
128
+ code = utils.call_api(
129
+ client,
130
+ model,
131
+ cot_prompt.format(
132
+ problem_description=problem_data['description'],
133
+ input_data=effective_input_data
134
+ )
135
+ )
136
+
137
+ if not code:
138
+ return False
139
+
140
+ utils.save_solution(output_dir, problem_identifier, code)
141
+ return True
142
+
143
+ except Exception as e:
144
+ print(f"Error in CoT strategy for problem {problem_identifier}: {e}")
145
+ return False
146
+
147
+
148
+ def run_cot_with_grammar_validation_strategy(client, model, problem, problem_identifier, output_dir):
149
+ """Run the CoT + Grammar Validation strategy (2-stage)"""
150
+ try:
151
+ problem_data = utils.prepare_problem_data(problem)
152
+ effective_input_data = utils.get_effective_input_data(problem_data)
153
+
154
+ cot_prompt = utils.load_file('prompts/cot_prompt.txt')
155
+ initial_code = utils.call_api(
156
+ client,
157
+ model,
158
+ cot_prompt.format(
159
+ problem_description=problem_data['description'],
160
+ input_data=effective_input_data
161
+ )
162
+ )
163
+
164
+ if not initial_code:
165
+ return False
166
+
167
+ time.sleep(2)
168
+
169
+ current_code = initial_code
170
+
171
+ dzn_data = problem.get('data.dzn') or ""
172
+ syntax_error_message = utils.check_syntax(initial_code, dzn_data)
173
+
174
+ if syntax_error_message:
175
+ grammar_prompt = utils.load_file('prompts/grammar_validation_prompt.txt')
176
+ minizinc_grammar = utils.load_file('grammar.mzn')
177
+
178
+ grammar_corrected_code = utils.call_api(
179
+ client,
180
+ model,
181
+ grammar_prompt.format(
182
+ problem_description=problem_data['description'],
183
+ input_data=effective_input_data,
184
+ current_code=current_code,
185
+ syntax_error_message=syntax_error_message,
186
+ minizinc_grammar=minizinc_grammar
187
+ )
188
+ )
189
+
190
+ if grammar_corrected_code:
191
+ current_code = grammar_corrected_code
192
+
193
+ utils.save_solution(output_dir, problem_identifier, current_code)
194
+ return True
195
+
196
+ except Exception as e:
197
+ print(f"Error in CoT + Grammar Check strategy for problem {problem_identifier}: {e}")
198
+ return False
199
+
200
+
201
+ ###########################################################
202
+ # Three-call Strategies
203
+ ###########################################################
204
+ def run_cot_with_code_and_grammar_validation_strategy(client, model, problem, problem_identifier, output_dir):
205
+ """Run the CoT + Code Validation + Grammar Validation strategy (3-stage)"""
206
+ try:
207
+ problem_data = utils.prepare_problem_data(problem)
208
+ effective_input_data = utils.get_effective_input_data(problem_data)
209
+
210
+ cot_prompt = utils.load_file('prompts/cot_prompt.txt')
211
+ initial_code = utils.call_api(
212
+ client,
213
+ model,
214
+ cot_prompt.format(
215
+ problem_description=problem_data['description'],
216
+ input_data=effective_input_data
217
+ )
218
+ )
219
+
220
+ if not initial_code:
221
+ return False
222
+
223
+ time.sleep(2)
224
+
225
+ current_code = initial_code
226
+
227
+ dzn_data = problem.get('data.dzn') or ""
228
+ syntax_error_message = utils.check_syntax(initial_code, dzn_data)
229
+
230
+ if syntax_error_message:
231
+ validation_prompt = utils.load_file('prompts/code_validation_prompt.txt')
232
+ validated_code = utils.call_api(
233
+ client,
234
+ model,
235
+ validation_prompt.format(
236
+ problem_description=problem_data['description'],
237
+ input_data=effective_input_data,
238
+ objective_type=problem_data['objective_type'],
239
+ final_code=initial_code,
240
+ syntax_error_message=syntax_error_message
241
+ )
242
+ )
243
+
244
+ if validated_code:
245
+ current_code = validated_code
246
+ dzn_data2 = problem.get('data.dzn') or ""
247
+ syntax_error_message = utils.check_syntax(validated_code, dzn_data2)
248
+
249
+ time.sleep(2)
250
+
251
+ if syntax_error_message:
252
+ grammar_prompt = utils.load_file('prompts/grammar_validation_prompt.txt')
253
+ minizinc_grammar = utils.load_file('grammar.mzn')
254
+
255
+ grammar_corrected_code = utils.call_api(
256
+ client,
257
+ model,
258
+ grammar_prompt.format(
259
+ problem_description=problem_data['description'],
260
+ input_data=effective_input_data,
261
+ current_code=current_code,
262
+ syntax_error_message=syntax_error_message,
263
+ minizinc_grammar=minizinc_grammar
264
+ )
265
+ )
266
+
267
+ if grammar_corrected_code:
268
+ current_code = grammar_corrected_code
269
+
270
+ utils.save_solution(output_dir, problem_identifier, current_code)
271
+ return True
272
+
273
+ except Exception as e:
274
+ print(f"Error in CoT + Validation + Grammar Check strategy for problem {problem_identifier}: {e}")
275
+ return False
276
+
277
+
278
+ ###########################################################
279
+ # Four and Five-call Strategies
280
+ ###########################################################
281
+ def run_agents_strategy(client, model, problem, problem_identifier, output_dir, validate=True):
282
+ """Run the agents strategy"""
283
+ try:
284
+ problem_data = utils.prepare_problem_data(problem)
285
+ effective_input_data = utils.get_effective_input_data(problem_data)
286
+
287
+ param_prompt = utils.load_file('prompts/parameter_and_variable_generation_prompt.txt')
288
+ params_vars = utils.call_api(
289
+ client,
290
+ model,
291
+ param_prompt.format(
292
+ problem_description=problem_data['description'],
293
+ input_data=effective_input_data
294
+ )
295
+ )
296
+ if not params_vars:
297
+ return False
298
+ time.sleep(2)
299
+
300
+ constraint_prompt = utils.load_file('prompts/constraint_generation_prompt.txt')
301
+ constraints = utils.call_api(
302
+ client,
303
+ model,
304
+ constraint_prompt.format(
305
+ problem_description=problem_data['description'],
306
+ input_data=effective_input_data,
307
+ parameters_and_variables=params_vars
308
+ )
309
+ )
310
+ if not constraints:
311
+ return False
312
+ time.sleep(2)
313
+
314
+ objective_prompt = utils.load_file('prompts/objective_generation_prompt.txt')
315
+ objective = utils.call_api(
316
+ client,
317
+ model,
318
+ objective_prompt.format(
319
+ problem_description=problem_data['description'],
320
+ input_data=effective_input_data,
321
+ parameters_and_variables=params_vars,
322
+ constraints=constraints
323
+ )
324
+ )
325
+ if not objective:
326
+ return False
327
+ time.sleep(2)
328
+
329
+ code_prompt = utils.load_file('prompts/code_stitching_prompt.txt')
330
+ final_code = utils.call_api(
331
+ client,
332
+ model,
333
+ code_prompt.format(
334
+ problem_description=problem_data['description'],
335
+ input_data=effective_input_data,
336
+ parameters_and_variables=params_vars,
337
+ constraints=constraints,
338
+ objective=objective
339
+ )
340
+ )
341
+ if not final_code:
342
+ return False
343
+
344
+ dzn_data = problem.get('data.dzn') or ""
345
+ syntax_error_message = utils.check_syntax(final_code, dzn_data)
346
+
347
+ if syntax_error_message and validate:
348
+ time.sleep(2)
349
+ validation_prompt = utils.load_file('prompts/code_validation_prompt.txt')
350
+ validated_code = utils.call_api(
351
+ client,
352
+ model,
353
+ validation_prompt.format(
354
+ problem_description=problem_data['description'],
355
+ input_data=effective_input_data,
356
+ objective_type=problem_data['objective_type'],
357
+ final_code=final_code,
358
+ syntax_error_message=syntax_error_message
359
+ )
360
+ )
361
+
362
+ if validated_code:
363
+ utils.save_solution(output_dir, problem_identifier, validated_code)
364
+ return True
365
+ return False
366
+ else:
367
+ utils.save_solution(output_dir, problem_identifier, final_code)
368
+ return True
369
+
370
+ except Exception as e:
371
+ print(f"Error in agents strategy for problem {problem_identifier}: {e}")
372
+ return False
373
+
374
+
375
+ ###########################################################
376
+ # Global Agentic (GALA) Strategies
377
+ ###########################################################
378
+ def run_gala_strategy(client, model, problem, problem_identifier, output_dir):
379
+ """Run the gala strategy (workers -> assembler)"""
380
+ try:
381
+ problem_data = utils.prepare_problem_data(problem)
382
+ effective_input_data = utils.get_effective_input_data(problem_data)
383
+
384
+ hints = ""
385
+
386
+ prompt_dir = utils._resolve_path("prompts/global_constraint_prompts")
387
+ for prompt in prompt_dir.glob("*.txt"):
388
+ ind_prompt = utils.load_file(str(prompt))
389
+ ind_prompt = ind_prompt + f"""
390
+ **Problem description**:
391
+ {problem_data['description']}
392
+
393
+ **Input data**:
394
+ {effective_input_data}
395
+ """
396
+
397
+ code = utils.call_api(client, model, ind_prompt)
398
+ code = utils.extract_global_constraint(ind_prompt) + ": \n" + code + "\n"
399
+ if "FALSE" not in code:
400
+ hints += code
401
+
402
+ assembler_prompt = utils.load_file('prompts/assembler_prompt.txt')
403
+ assembler_prompt = assembler_prompt + f"""
404
+ **Problem description**:
405
+ {problem_data['description']}
406
+
407
+ **Input data**:
408
+ {effective_input_data}
409
+
410
+ **Hints**:
411
+ {hints}
412
+ """
413
+
414
+ code = utils.call_api(client, model, assembler_prompt)
415
+
416
+ if not code:
417
+ return False
418
+
419
+ utils.save_solution(output_dir, problem_identifier, code)
420
+ return True
421
+
422
+ except Exception as e:
423
+ print(f"Error in gala strategy for problem {problem_identifier}: {e}")
424
+ return False
425
+
426
+
427
+ ###########################################################
428
+ # Helpers
429
+ ###########################################################
430
+ def check_already_processed(output_dir, problem_identifier):
431
+ """Check if a problem has already been successfully processed"""
432
+ solution_path = os.path.join(output_dir, f"{problem_identifier}.mzn")
433
+ return os.path.exists(solution_path) and os.path.getsize(solution_path) > 0
434
+
435
+
436
+ _STRATEGY_MAP = {
437
+ 'baseline': run_baseline_strategy,
438
+ 'cot': run_cot_strategy,
439
+ 'knowledge_graph': run_knowledge_graph_strategy,
440
+ 'cot_with_code_validation': run_cot_with_code_validation_strategy,
441
+ 'cot_with_grammar_validation': run_cot_with_grammar_validation_strategy,
442
+ 'cot_with_code_and_grammar_validation': run_cot_with_code_and_grammar_validation_strategy,
443
+ 'agents': lambda c, m, p, i, o: run_agents_strategy(c, m, p, i, o, validate=False),
444
+ 'agents_with_code_validation': run_agents_strategy,
445
+ 'gala': run_gala_strategy,
446
+ }
447
+
448
+
449
+ def _init_client(args):
450
+ """Create and configure the LLM client from parsed args."""
451
+ if args.model in ["gpt-4", "gpt-4o", "gpt-5.2"]:
452
+ if not args.api_key:
453
+ raise ValueError(
454
+ "OpenAI API key not provided. "
455
+ "Set OPENAI_API_KEY environment variable or use --api-key"
456
+ )
457
+ client = openai.OpenAI(api_key=args.api_key)
458
+ utils.API_CONFIG['temperature'] = args.temperature
459
+ utils.API_CONFIG['max_tokens'] = args.max_tokens
460
+ utils.API_CONFIG['sleep_time'] = args.sleep_time
461
+ utils.API_CONFIG['model'] = args.model
462
+ else:
463
+ client = ChatOllama(
464
+ model=args.model,
465
+ temperature=args.temperature,
466
+ num_predict=args.max_tokens,
467
+ )
468
+ return client
469
+
470
+
471
+ def _run_problem_mode(args, client):
472
+ """Handle --problem mode: generate MiniZinc from a single problem description."""
473
+ # Accept a file path or a literal description string
474
+ problem_text = args.problem
475
+ if os.path.isfile(problem_text):
476
+ with open(problem_text, 'r') as f:
477
+ problem_text = f.read()
478
+
479
+ problem = utils.create_problem_from_text(problem_text)
480
+
481
+ # Use the first requested strategy; default to 'cot'
482
+ strategy = (args.strategies or ['cot'])[0]
483
+
484
+ if strategy == 'knowledge_graph':
485
+ print(
486
+ "Warning: 'knowledge_graph' strategy requires pre-built TTL files. "
487
+ "Falling back to 'cot'.",
488
+ file=sys.stderr,
489
+ )
490
+ strategy = 'cot'
491
+
492
+ strategy_fn = _STRATEGY_MAP[strategy]
493
+
494
+ print(f"Generating MiniZinc model using strategy '{strategy}' with model '{args.model}'...",
495
+ file=sys.stderr)
496
+
497
+ with tempfile.TemporaryDirectory() as tmpdir:
498
+ success = strategy_fn(client, args.model, problem, 'output', tmpdir)
499
+ if success:
500
+ output_path = os.path.join(tmpdir, 'output.mzn')
501
+ with open(output_path) as f:
502
+ code = f.read()
503
+ print(code)
504
+ else:
505
+ print("Failed to generate MiniZinc code.", file=sys.stderr)
506
+ sys.exit(1)
507
+
508
+
509
+ ###########################################################
510
+ # Entry point
511
+ ###########################################################
512
+ def main():
513
+ parser = argparse.ArgumentParser(
514
+ prog='text2model',
515
+ description=(
516
+ 'text2model: turn a natural-language optimization problem description '
517
+ 'into a MiniZinc constraint model using an LLM.\n\n'
518
+ 'Two modes:\n'
519
+ ' 1) Single problem - pass --problem to translate one description and\n'
520
+ ' print the generated .mzn code to stdout.\n'
521
+ ' 2) Batch - omit --problem to run one or more prompting\n'
522
+ ' strategies over the skadio/text2zinc benchmark dataset.'
523
+ ),
524
+ epilog=(
525
+ 'Examples:\n'
526
+ ' # Translate a problem description given directly on the command line\n'
527
+ ' text2model --problem "Pack items of given weights into the fewest bins "\\\n'
528
+ ' "of capacity 10." --model gpt-4o --api-key sk-...\n\n'
529
+ ' # Translate a problem description stored in a file\n'
530
+ ' text2model --problem problem.txt --model gpt-4o\n\n'
531
+ ' # List the sources available in the benchmark dataset\n'
532
+ ' text2model --list-sources\n\n'
533
+ ' # Batch-run the chain-of-thought strategy over one source\n'
534
+ ' text2model --strategies cot --source "csplib" --output-dir results/\n\n'
535
+ 'The OpenAI API key can also be set via the OPENAI_API_KEY environment '
536
+ 'variable. Local models (e.g. phi4) are served through Ollama and need '
537
+ 'no API key.'
538
+ ),
539
+ formatter_class=argparse.RawDescriptionHelpFormatter,
540
+ )
541
+
542
+ # ── Simple single-problem mode ──────────────────────────────────────────
543
+ parser.add_argument(
544
+ '--problem', type=str, default=None,
545
+ help='Problem description string, or path to a .txt file containing the description. '
546
+ 'When provided, generates MiniZinc code and prints it to stdout.'
547
+ )
548
+
549
+ # ── Model / API ─────────────────────────────────────────────────────────
550
+ parser.add_argument('--model', default='gpt-4',
551
+ choices=['gpt-4', 'gpt-4o', 'gpt-5.2', 'phi4'],
552
+ help='LLM model to use')
553
+ parser.add_argument('--api-key', default=os.getenv('OPENAI_API_KEY'),
554
+ help='OpenAI API key')
555
+ parser.add_argument('--temperature', type=float, default=0,
556
+ help='Temperature for API calls')
557
+ parser.add_argument('--max-tokens', type=int, default=4096,
558
+ help='Max tokens for API calls')
559
+ parser.add_argument('--sleep-time', type=float, default=3,
560
+ help='Sleep time between API calls')
561
+
562
+ # ── Strategy ────────────────────────────────────────────────────────────
563
+ parser.add_argument(
564
+ '--strategies', nargs='+',
565
+ default=['cot'],
566
+ choices=[
567
+ 'baseline', 'cot', 'knowledge_graph',
568
+ 'cot_with_code_validation', 'cot_with_grammar_validation',
569
+ 'cot_with_code_and_grammar_validation',
570
+ 'agents', 'agents_with_code_validation', 'gala', 'all',
571
+ ],
572
+ help='Strategy (or strategies for batch mode). '
573
+ 'In --problem mode only the first strategy is used; default is cot.'
574
+ )
575
+
576
+ # ── Batch-mode dataset arguments ────────────────────────────────────────
577
+ parser.add_argument('--problem-ids', nargs='+', type=int,
578
+ help='Specific problem IDs to process (batch mode)')
579
+ parser.add_argument('--source', type=str, nargs='*', default=None,
580
+ help='Filter problems by source (batch mode). Supports partial matching.')
581
+ parser.add_argument('--list-sources', action='store_true',
582
+ help='List all available sources in the dataset and exit')
583
+ parser.add_argument('--include-unverified', action='store_true',
584
+ help='Include unverified problems (batch mode)')
585
+ parser.add_argument('--all-sources', action='store_true',
586
+ help='Run on all sources (batch mode)')
587
+ parser.add_argument('--output-dir', default=None,
588
+ help='Base output directory for batch mode (must not already exist)')
589
+
590
+ args = parser.parse_args()
591
+
592
+ # ── --problem mode: skip dataset loading ────────────────────────────────
593
+ if args.problem:
594
+ client = _init_client(args)
595
+ _run_problem_mode(args, client)
596
+ return
597
+
598
+ # ── Batch mode ───────────────────────────────────────────────────────────
599
+ print("Loading dataset...")
600
+ dataset = load_dataset("skadio/text2zinc")
601
+
602
+ if args.include_unverified:
603
+ print("Including ALL problems (verified and unverified)")
604
+ filtered_train = dataset["train"]
605
+ else:
606
+ print("Including only VERIFIED problems (use --include-unverified to include all)")
607
+ filtered_train = dataset["train"].filter(lambda x: x["is_verified"])
608
+
609
+ dataset = DatasetDict({"train": filtered_train})
610
+ print(f"Loaded dataset with {len(dataset['train'])} examples")
611
+
612
+ if args.list_sources:
613
+ print("\nAvailable sources in the dataset:")
614
+ sources = utils.get_available_sources(dataset['train'])
615
+ for source in sources:
616
+ count = sum(1 for p in dataset['train'] if utils.get_problem_source(p) == source)
617
+ print(f" - {source}: {count} instances")
618
+ return
619
+
620
+ if not args.output_dir:
621
+ parser.error("--output-dir is required in batch mode (not needed with --problem)")
622
+
623
+ while os.path.exists(args.output_dir):
624
+ print(f"Output directory '{args.output_dir}' already exists. Please choose a different name.")
625
+ new_dir = input("Enter a new output directory name: ").strip()
626
+ if new_dir:
627
+ args.output_dir = new_dir
628
+
629
+ if args.source:
630
+ print(f"\nFiltering dataset by sources: {args.source}")
631
+
632
+ def matches_any_source(problem):
633
+ source = utils.get_problem_source(problem)
634
+ if source is None:
635
+ return False
636
+ return any(s.lower() in source.lower() for s in args.source)
637
+
638
+ filtered_train = dataset['train'].filter(matches_any_source)
639
+ dataset = DatasetDict({"train": filtered_train})
640
+ print(f"Filtered dataset contains {len(dataset['train'])} instances matching sources")
641
+
642
+ if len(dataset['train']) == 0:
643
+ print("\nNo instances found matching the specified source.")
644
+ print("Use --list-sources to see available sources.")
645
+ return
646
+
647
+ client = _init_client(args)
648
+
649
+ if 'all' in args.strategies:
650
+ strategies = [
651
+ 'baseline', 'cot', 'knowledge_graph',
652
+ 'cot_with_code_validation', 'cot_with_grammar_validation',
653
+ 'cot_with_code_and_grammar_validation',
654
+ 'agents', 'agents_with_code_validation', 'gala',
655
+ ]
656
+ else:
657
+ strategies = args.strategies
658
+
659
+ if args.problem_ids:
660
+ problems_to_process = [
661
+ (idx, dataset['train'][idx])
662
+ for idx in args.problem_ids
663
+ if idx < len(dataset['train'])
664
+ ]
665
+ else:
666
+ problems_to_process = list(enumerate(dataset['train']))
667
+
668
+ print(f"\n{'='*50}")
669
+ print("RUN CONFIGURATION SUMMARY")
670
+ print(f"{'='*50}")
671
+ print(f"Model: {args.model}")
672
+ print(f"Strategies: {', '.join(strategies)}")
673
+ print(f"Source filter: {args.source if args.source else 'None (all sources)'}")
674
+ print(f"Include unverified: {args.include_unverified}")
675
+ print(f"Number of instances to process: {len(problems_to_process)}")
676
+ print(f"Output directory: {args.output_dir}")
677
+ print(f"{'='*50}\n")
678
+
679
+ results = {}
680
+ for strategy in strategies:
681
+ print(f"\nRunning {strategy} strategy with {args.model}...")
682
+
683
+ results[strategy] = {'success': 0, 'failed': 0, 'errors': []}
684
+
685
+ for idx, problem in tqdm(problems_to_process, desc=f"{strategy} progress"):
686
+ try:
687
+ problem_identifier = utils.get_problem_identifier(problem, idx)
688
+
689
+ cardinal_subfolder = utils.get_cardinal_ops_subfolder(problem)
690
+ if cardinal_subfolder:
691
+ output_dir = os.path.join(
692
+ args.output_dir, args.model, f"{strategy}_{cardinal_subfolder}"
693
+ )
694
+ else:
695
+ output_dir = os.path.join(args.output_dir, args.model, strategy)
696
+
697
+ if check_already_processed(output_dir, problem_identifier):
698
+ continue
699
+
700
+ os.makedirs(output_dir, exist_ok=True)
701
+
702
+ success = _STRATEGY_MAP[strategy](client, args.model, problem, problem_identifier, output_dir)
703
+
704
+ if success:
705
+ results[strategy]['success'] += 1
706
+ else:
707
+ results[strategy]['failed'] += 1
708
+ results[strategy]['errors'].append({
709
+ 'idx': idx,
710
+ 'identifier': problem_identifier,
711
+ 'error': 'Strategy returned False'
712
+ })
713
+
714
+ except Exception as e:
715
+ print(f"\nUnexpected error processing problem at index {idx}: {e}")
716
+ results[strategy]['failed'] += 1
717
+ problem_identifier = utils.get_problem_identifier(problem, idx)
718
+ results[strategy]['errors'].append({
719
+ 'idx': idx,
720
+ 'identifier': problem_identifier,
721
+ 'error': str(e)
722
+ })
723
+ continue
724
+
725
+ time.sleep(args.sleep_time)
726
+
727
+ print("\n" + "="*50)
728
+ print("SUMMARY")
729
+ print("="*50)
730
+ for strategy, result in results.items():
731
+ total = result['success'] + result['failed']
732
+ success_rate = (result['success'] / total * 100) if total > 0 else 0
733
+ print(f"{strategy}: {result['success']}/{total} successful ({success_rate:.1f}%)")
734
+ if result['errors']:
735
+ print(" Failed instances:")
736
+ for err in result['errors'][:5]:
737
+ print(f" - {err['identifier']}: {err['error'][:50]}...")
738
+ if len(result['errors']) > 5:
739
+ print(f" ... and {len(result['errors']) - 5} more errors")
740
+
741
+ results_for_json = {
742
+ strategy: {
743
+ 'success': result['success'],
744
+ 'failed': result['failed'],
745
+ 'total': result['success'] + result['failed'],
746
+ 'success_rate': (
747
+ result['success'] / (result['success'] + result['failed']) * 100
748
+ if (result['success'] + result['failed']) > 0 else 0
749
+ ),
750
+ 'failed_identifiers': [err['identifier'] for err in result['errors']]
751
+ }
752
+ for strategy, result in results.items()
753
+ }
754
+
755
+ results_for_json['_metadata'] = {
756
+ 'model': args.model,
757
+ 'source_filter': args.source,
758
+ 'include_unverified': args.include_unverified,
759
+ 'num_instances': len(problems_to_process),
760
+ 'strategies': strategies,
761
+ }
762
+
763
+ summary_path = os.path.join(args.output_dir, args.model, 'summary.json')
764
+ os.makedirs(os.path.dirname(summary_path), exist_ok=True)
765
+ with open(summary_path, 'w') as f:
766
+ json.dump(results_for_json, f, indent=2)
767
+ print(f"\nResults saved to {summary_path}")
768
+
769
+
770
+ if __name__ == "__main__":
771
+ main()