|
25 | 25 | from csv import reader, writer |
26 | 26 | import datetime |
27 | 27 | import dill |
| 28 | +import gc |
28 | 29 | import json |
29 | 30 | from multiprocessing import get_context, Manager |
30 | 31 | import os.path |
@@ -210,7 +211,43 @@ def run_optimization_for_subproblem_stage( |
210 | 211 | Return the objective function (Total_Cost) value; only used in testing mode |
211 | 212 |
|
212 | 213 | """ |
213 | | - # If directed to do so, log optimization run |
| 214 | + # Determine whether to skip this optimization before creating logger to |
| 215 | + # avoid the logging overhead and opening too many files for large |
| 216 | + # simulations |
| 217 | + skip_solve = False |
| 218 | + if parsed_arguments.incomplete_only: |
| 219 | + termination_condition_file = os.path.join( |
| 220 | + scenario_directory, |
| 221 | + weather_iteration_directory, |
| 222 | + hydro_iteration_directory, |
| 223 | + availability_iteration_directory, |
| 224 | + subproblem_directory, |
| 225 | + stage_directory, |
| 226 | + "results", |
| 227 | + "termination_condition.txt", |
| 228 | + ) |
| 229 | + if os.path.isfile(termination_condition_file): |
| 230 | + with open(termination_condition_file, "r") as f: |
| 231 | + termination_condition = f.read() |
| 232 | + if not parsed_arguments.quiet: |
| 233 | + print( |
| 234 | + f"Subproblem stage {subproblem_directory} " |
| 235 | + f"{stage_directory} " |
| 236 | + f"previously solved with termination condition " |
| 237 | + f"**{termination_condition}**. Skipping solve." |
| 238 | + ) |
| 239 | + skip_solve = True |
| 240 | + if not parsed_arguments.quiet: |
| 241 | + print( |
| 242 | + f"Skipping {weather_iteration_directory}/{hydro_iteration_directory}/" |
| 243 | + f"{availability_iteration_directory}/{subproblem_directory}/{stage_directory} " |
| 244 | + f"(already solved)" |
| 245 | + ) |
| 246 | + # Force garbage collection to release file descriptor immediately |
| 247 | + gc.collect() |
| 248 | + return None # Exit early without creating logger |
| 249 | + |
| 250 | + # If directed to do so, log optimization run (only if actually solving) |
214 | 251 | if parsed_arguments.log: |
215 | 252 | logs_directory = create_logs_directory_if_not_exists( |
216 | 253 | scenario_directory, |
@@ -238,31 +275,6 @@ def run_optimization_for_subproblem_stage( |
238 | 275 | sys.stdout = logger |
239 | 276 | sys.stderr = logger |
240 | 277 |
|
241 | | - # Determine whether to skip this optimization |
242 | | - skip_solve = False |
243 | | - if parsed_arguments.incomplete_only: |
244 | | - termination_condition_file = os.path.join( |
245 | | - scenario_directory, |
246 | | - weather_iteration_directory, |
247 | | - hydro_iteration_directory, |
248 | | - availability_iteration_directory, |
249 | | - subproblem_directory, |
250 | | - stage_directory, |
251 | | - "results", |
252 | | - "termination_condition.txt", |
253 | | - ) |
254 | | - if os.path.isfile(termination_condition_file): |
255 | | - with open(termination_condition_file, "r") as f: |
256 | | - termination_condition = f.read() |
257 | | - if not parsed_arguments.quiet: |
258 | | - print( |
259 | | - f"Subproblem stage {subproblem_directory} " |
260 | | - f"{stage_directory} " |
261 | | - f"previously solved with termination condition " |
262 | | - f"**{termination_condition}**. Skipping solve." |
263 | | - ) |
264 | | - skip_solve = True |
265 | | - |
266 | 278 | if not skip_solve: |
267 | 279 | # If directed, set temporary file directory to be the logs directory |
268 | 280 | # In conjunction with --keepfiles, this will write the solver solution |
@@ -395,10 +407,14 @@ def run_optimization_for_subproblem_stage( |
395 | 407 | ) |
396 | 408 |
|
397 | 409 | # If logging, we need to return sys.stdout to original (i.e. stop writing |
398 | | - # to log file) |
| 410 | + # to log file) and close the log file to release file descriptor |
399 | 411 | if parsed_arguments.log: |
| 412 | + logger.close() |
400 | 413 | sys.stdout = stdout_original |
401 | 414 | sys.stderr = stderr_original |
| 415 | + # Explicitly delete logger reference and force garbage collection |
| 416 | + del logger |
| 417 | + gc.collect() |
402 | 418 |
|
403 | 419 | # Return the objective function value (in the testing suite, the value |
404 | 420 | # gets checked against the expected value, but this is the only place |
@@ -446,6 +462,8 @@ def run_optimization_for_subproblem( |
446 | 462 | multi_stage, |
447 | 463 | parsed_arguments, |
448 | 464 | ) |
| 465 | + # Force garbage collection after each stage to release file descriptors |
| 466 | + gc.collect() |
449 | 467 |
|
450 | 468 |
|
451 | 469 | def run_optimization_for_subproblem_pool(pool_datum): |
@@ -551,6 +569,10 @@ def solve_sequentially( |
551 | 569 | parsed_arguments=parsed_arguments, |
552 | 570 | objective_values=objective_values, |
553 | 571 | ) |
| 572 | + # Force garbage collection after each subproblem to release file descriptors |
| 573 | + gc.collect() |
| 574 | + # Force garbage collection after each availability iteration |
| 575 | + gc.collect() |
554 | 576 |
|
555 | 577 | return objective_values |
556 | 578 |
|
@@ -915,6 +937,10 @@ def save_results( |
915 | 937 | dynamic_components=dynamic_components, |
916 | 938 | verbose=parsed_arguments.verbose, |
917 | 939 | ) |
| 940 | + |
| 941 | + # Force garbage collection to release file descriptors immediately |
| 942 | + # This prevents "too many open files" errors when processing many iterations |
| 943 | + gc.collect() |
918 | 944 | # If solver status is not ok, don't export results and print some |
919 | 945 | # messages for the user |
920 | 946 | else: |
|
0 commit comments