omniopt2 8911__py3-none-any.whl → 9061__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.

Potentially problematic release.


This version of omniopt2 might be problematic. Click here for more details.

Files changed (40) hide show
  1. .omniopt.py +629 -712
  2. .pareto.py +134 -0
  3. omniopt +1 -1
  4. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt.py +629 -712
  5. omniopt2-9061.data/data/bin/.pareto.py +134 -0
  6. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/omniopt +1 -1
  7. {omniopt2-8911.dist-info → omniopt2-9061.dist-info}/METADATA +1 -1
  8. {omniopt2-8911.dist-info → omniopt2-9061.dist-info}/RECORD +40 -38
  9. omniopt2.egg-info/PKG-INFO +1 -1
  10. omniopt2.egg-info/SOURCES.txt +1 -0
  11. pyproject.toml +1 -1
  12. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.colorfunctions.sh +0 -0
  13. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.general.sh +0 -0
  14. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.helpers.py +0 -0
  15. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_cpu_ram_usage.py +0 -0
  16. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_general.py +0 -0
  17. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_gpu_usage.py +0 -0
  18. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_kde.py +0 -0
  19. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_scatter.py +0 -0
  20. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_scatter_generation_method.py +0 -0
  21. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_scatter_hex.py +0 -0
  22. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_time_and_exit_code.py +0 -0
  23. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_trial_index_result.py +0 -0
  24. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.omniopt_plot_worker.py +0 -0
  25. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.random_generator.py +0 -0
  26. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.shellscript_functions +0 -0
  27. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/.tpe.py +0 -0
  28. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/LICENSE +0 -0
  29. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/apt-dependencies.txt +0 -0
  30. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/omniopt_docker +0 -0
  31. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/omniopt_evaluate +0 -0
  32. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/omniopt_plot +0 -0
  33. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/omniopt_share +0 -0
  34. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/pylint.rc +0 -0
  35. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/requirements.txt +0 -0
  36. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/setup.py +0 -0
  37. {omniopt2-8911.data → omniopt2-9061.data}/data/bin/test_requirements.txt +0 -0
  38. {omniopt2-8911.dist-info → omniopt2-9061.dist-info}/WHEEL +0 -0
  39. {omniopt2-8911.dist-info → omniopt2-9061.dist-info}/licenses/LICENSE +0 -0
  40. {omniopt2-8911.dist-info → omniopt2-9061.dist-info}/top_level.txt +0 -0
.omniopt.py CHANGED
@@ -31,9 +31,10 @@ import psutil
31
31
 
32
32
  FORCE_EXIT: bool = False
33
33
 
34
+ LAST_LOG_TIME: int = 0
34
35
  last_msg_progressbar = ""
35
36
  last_msg_raw = None
36
- last_lock = threading.Lock()
37
+ last_lock_print_debug = threading.Lock()
37
38
 
38
39
  def force_exit(signal_number: Any, frame: Any) -> Any:
39
40
  global FORCE_EXIT
@@ -493,6 +494,24 @@ try:
493
494
  dier: FunctionType = helpers.dier
494
495
  is_equal: FunctionType = helpers.is_equal
495
496
  is_not_equal: FunctionType = helpers.is_not_equal
497
+ with spinner("Importing pareto..."):
498
+ pareto_file: str = f"{script_dir}/.pareto.py"
499
+ spec = importlib.util.spec_from_file_location(
500
+ name="pareto",
501
+ location=pareto_file,
502
+ )
503
+ if spec is not None and spec.loader is not None:
504
+ pareto = importlib.util.module_from_spec(spec)
505
+ spec.loader.exec_module(pareto)
506
+ else:
507
+ raise ImportError(f"Could not load module from {pareto_file}")
508
+
509
+ pareto_front_table_filter_rows: FunctionType = pareto.pareto_front_table_filter_rows
510
+ pareto_front_table_add_headers: FunctionType = pareto.pareto_front_table_add_headers
511
+ pareto_front_table_add_rows: FunctionType = pareto.pareto_front_table_add_rows
512
+ pareto_front_filter_complete_points: FunctionType = pareto.pareto_front_filter_complete_points
513
+ pareto_front_select_pareto_points: FunctionType = pareto.pareto_front_select_pareto_points
514
+
496
515
  except KeyboardInterrupt:
497
516
  print("You pressed CTRL-c while importing the helpers file")
498
517
  sys.exit(0)
@@ -546,7 +565,6 @@ def error_without_print(text: str) -> None:
546
565
  helpers.print_color("red", f"Error: {e}. This may mean that the {get_current_run_folder()} was deleted during the run. Could not write '{text} to {get_current_run_folder()}/oo_errors.txt'")
547
566
  sys.exit(99)
548
567
 
549
-
550
568
  def print_red(text: str) -> None:
551
569
  helpers.print_color("red", text)
552
570
 
@@ -2212,12 +2230,7 @@ def fetch_and_prepare_trials() -> Optional[pd.DataFrame]:
2212
2230
 
2213
2231
  ax_client.experiment.fetch_data()
2214
2232
  df = ax_client_get_trials_data_frame()
2215
- #print("========================")
2216
- #print("BEFORE merge_with_job_infos:")
2217
- #print(df["generation_node"])
2218
2233
  df = merge_with_job_infos(df)
2219
- #print("AFTER merge_with_job_infos:")
2220
- #print(df["generation_node"])
2221
2234
 
2222
2235
  return df
2223
2236
 
@@ -2458,32 +2471,36 @@ def write_worker_usage() -> None:
2458
2471
  print_debug(f"WORKER_PERCENTAGE_USAGE seems to be empty. Not writing {worker_usage_file}")
2459
2472
 
2460
2473
  def log_system_usage() -> None:
2474
+ global LAST_LOG_TIME
2475
+
2476
+ now = time.time()
2477
+ if now - LAST_LOG_TIME < 30:
2478
+ return
2479
+
2480
+ LAST_LOG_TIME = int(now)
2481
+
2461
2482
  if not get_current_run_folder():
2462
2483
  return
2463
2484
 
2464
2485
  ram_cpu_csv_file_path = os.path.join(get_current_run_folder(), "cpu_ram_usage.csv")
2465
-
2466
2486
  makedirs(os.path.dirname(ram_cpu_csv_file_path))
2467
2487
 
2468
2488
  file_exists = os.path.isfile(ram_cpu_csv_file_path)
2469
2489
 
2470
- with open(ram_cpu_csv_file_path, mode='a', newline='', encoding="utf-8") as file:
2471
- writer = csv.writer(file)
2472
-
2473
- current_time = int(time.time())
2474
-
2475
- if process is not None:
2476
- mem_proc = process.memory_info()
2477
-
2478
- if mem_proc is not None:
2479
- ram_usage_mb = mem_proc.rss / (1024 * 1024)
2480
- cpu_usage_percent = psutil.cpu_percent(percpu=False)
2490
+ mem_proc = process.memory_info() if process else None
2491
+ if not mem_proc:
2492
+ return
2481
2493
 
2482
- if ram_usage_mb > 0 and cpu_usage_percent > 0:
2483
- if not file_exists:
2484
- writer.writerow(["timestamp", "ram_usage_mb", "cpu_usage_percent"])
2494
+ ram_usage_mb = mem_proc.rss / (1024 * 1024)
2495
+ cpu_usage_percent = psutil.cpu_percent(percpu=False)
2496
+ if ram_usage_mb <= 0 or cpu_usage_percent <= 0:
2497
+ return
2485
2498
 
2486
- writer.writerow([current_time, ram_usage_mb, cpu_usage_percent])
2499
+ with open(ram_cpu_csv_file_path, mode='a', newline='', encoding="utf-8") as file:
2500
+ writer = csv.writer(file)
2501
+ if not file_exists:
2502
+ writer.writerow(["timestamp", "ram_usage_mb", "cpu_usage_percent"])
2503
+ writer.writerow([int(now), ram_usage_mb, cpu_usage_percent])
2487
2504
 
2488
2505
  def write_process_info() -> None:
2489
2506
  try:
@@ -2836,7 +2853,7 @@ def print_debug_progressbar(msg: str) -> None:
2836
2853
  global last_msg_progressbar, last_msg_raw
2837
2854
 
2838
2855
  try:
2839
- with last_lock:
2856
+ with last_lock_print_debug:
2840
2857
  if msg != last_msg_raw:
2841
2858
  time_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
2842
2859
  full_msg = f"{time_str} ({worker_generator_uuid}): {msg}"
@@ -3353,178 +3370,500 @@ def parse_experiment_parameters() -> None:
3353
3370
 
3354
3371
  experiment_parameters = params # type: ignore[assignment]
3355
3372
 
3356
- def check_factorial_range() -> None:
3357
- if args.model and args.model == "FACTORIAL":
3358
- _fatal_error("\n⚠ --model FACTORIAL cannot be used with range parameter", 181)
3373
+ def job_calculate_pareto_front(path_to_calculate: str, disable_sixel_and_table: bool = False) -> bool:
3374
+ pf_start_time = time.time()
3359
3375
 
3360
- def check_if_range_types_are_invalid(value_type: str, valid_value_types: list) -> None:
3361
- if value_type not in valid_value_types:
3362
- valid_value_types_string = ", ".join(valid_value_types)
3363
- _fatal_error(f"⚠ {value_type} is not a valid value type. Valid types for range are: {valid_value_types_string}", 181)
3376
+ if not path_to_calculate:
3377
+ return False
3364
3378
 
3365
- def check_range_params_length(this_args: Union[str, list]) -> None:
3366
- if len(this_args) != 5 and len(this_args) != 4 and len(this_args) != 6:
3367
- _fatal_error("\n⚠ --parameter for type range must have 4 (or 5, the last one being optional and float by default, or 6, while the last one is true or false) parameters: <NAME> range <START> <END> (<TYPE (int or float)>, <log_scale: bool>)", 181)
3379
+ global CURRENT_RUN_FOLDER
3380
+ global RESULT_CSV_FILE
3381
+ global arg_result_names
3368
3382
 
3369
- def die_if_lower_and_upper_bound_equal_zero(lower_bound: Union[int, float], upper_bound: Union[int, float]) -> None:
3370
- if upper_bound is None or lower_bound is None:
3371
- _fatal_error("die_if_lower_and_upper_bound_equal_zero: upper_bound or lower_bound is None. Cannot continue.", 91)
3372
- if upper_bound == lower_bound:
3373
- if lower_bound == 0:
3374
- _fatal_error(f"⚠ Lower bound and upper bound are equal: {lower_bound}, cannot automatically fix this, because they -0 = +0 (usually a quickfix would be to set lower_bound = -upper_bound)", 181)
3375
- print_red(f"⚠ Lower bound and upper bound are equal: {lower_bound}, setting lower_bound = -upper_bound")
3376
- if upper_bound is not None:
3377
- lower_bound = -upper_bound
3383
+ if not path_to_calculate:
3384
+ print_red("Can only calculate pareto front of previous job when --calculate_pareto_front_of_job is set")
3385
+ return False
3378
3386
 
3379
- def format_value(value: Any, float_format: str = '.80f') -> str:
3380
- try:
3381
- if isinstance(value, float):
3382
- s = format(value, float_format)
3383
- s = s.rstrip('0').rstrip('.') if '.' in s else s
3384
- return s
3385
- return str(value)
3386
- except Exception as e:
3387
- print_red(f"⚠ Error formatting the number {value}: {e}")
3388
- return str(value)
3387
+ if not os.path.exists(path_to_calculate):
3388
+ print_red(f"Path '{path_to_calculate}' does not exist")
3389
+ return False
3389
3390
 
3390
- def replace_parameters_in_string(
3391
- parameters: dict,
3392
- input_string: str,
3393
- float_format: str = '.20f',
3394
- additional_prefixes: list[str] = [],
3395
- additional_patterns: list[str] = [],
3396
- ) -> str:
3397
- try:
3398
- prefixes = ['$', '%'] + additional_prefixes
3399
- patterns = ['{key}', '({key})'] + additional_patterns
3391
+ ax_client_json = f"{path_to_calculate}/state_files/ax_client.experiment.json"
3400
3392
 
3401
- for key, value in parameters.items():
3402
- replacement = format_value(value, float_format=float_format)
3403
- for prefix in prefixes:
3404
- for pattern in patterns:
3405
- token = prefix + pattern.format(key=key)
3406
- input_string = input_string.replace(token, replacement)
3393
+ if not os.path.exists(ax_client_json):
3394
+ print_red(f"Path '{ax_client_json}' not found")
3395
+ return False
3407
3396
 
3408
- input_string = input_string.replace('\r', ' ').replace('\n', ' ')
3409
- return input_string
3397
+ checkpoint_file: str = f"{path_to_calculate}/state_files/checkpoint.json"
3398
+ if not os.path.exists(checkpoint_file):
3399
+ print_red(f"The checkpoint file '{checkpoint_file}' does not exist")
3400
+ return False
3410
3401
 
3411
- except Exception as e:
3412
- print_red(f"\n⚠ Error: {e}")
3413
- return ""
3402
+ RESULT_CSV_FILE = f"{path_to_calculate}/{RESULTS_CSV_FILENAME}"
3403
+ if not os.path.exists(RESULT_CSV_FILE):
3404
+ print_red(f"{RESULT_CSV_FILE} not found")
3405
+ return False
3414
3406
 
3415
- def get_memory_usage() -> float:
3416
- user_uid = os.getuid()
3407
+ res_names = []
3417
3408
 
3418
- memory_usage = float(sum(
3419
- p.memory_info().rss for p in psutil.process_iter(attrs=['memory_info', 'uids'])
3420
- if p.info['uids'].real == user_uid
3421
- ) / (1024 * 1024))
3409
+ res_names_file = f"{path_to_calculate}/result_names.txt"
3410
+ if not os.path.exists(res_names_file):
3411
+ print_red(f"File '{res_names_file}' does not exist")
3412
+ return False
3422
3413
 
3423
- return memory_usage
3414
+ try:
3415
+ with open(res_names_file, "r", encoding="utf-8") as file:
3416
+ lines = file.readlines()
3417
+ except Exception as e:
3418
+ print_red(f"Error reading file '{res_names_file}': {e}")
3419
+ return False
3424
3420
 
3425
- class MonitorProcess:
3426
- def __init__(self: Any, pid: int, interval: float = 1.0) -> None:
3427
- self.pid = pid
3428
- self.interval = interval
3429
- self.running = True
3430
- self.thread = threading.Thread(target=self._monitor)
3431
- self.thread.daemon = True
3421
+ for line in lines:
3422
+ entry = line.strip()
3423
+ if entry != "":
3424
+ res_names.append(entry)
3432
3425
 
3433
- fool_linter(f"self.thread.daemon was set to {self.thread.daemon}")
3426
+ if len(res_names) < 2:
3427
+ print_red(f"Error: There are less than 2 result names (is: {len(res_names)}, {', '.join(res_names)}) in {path_to_calculate}. Cannot continue calculating the pareto front.")
3428
+ return False
3434
3429
 
3435
- def _monitor(self: Any) -> None:
3436
- try:
3437
- _internal_process = psutil.Process(self.pid)
3438
- while self.running and _internal_process.is_running():
3439
- crf = get_current_run_folder()
3430
+ load_username_to_args(path_to_calculate)
3440
3431
 
3441
- if crf and crf != "":
3442
- log_file_path = os.path.join(crf, "eval_nodes_cpu_ram_logs.txt")
3432
+ CURRENT_RUN_FOLDER = path_to_calculate
3443
3433
 
3444
- os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
3434
+ arg_result_names = res_names
3445
3435
 
3446
- with open(log_file_path, mode="a", encoding="utf-8") as log_file:
3447
- hostname = socket.gethostname()
3436
+ load_experiment_parameters_from_checkpoint_file(checkpoint_file, False)
3448
3437
 
3449
- slurm_job_id = os.getenv("SLURM_JOB_ID")
3438
+ if experiment_parameters is None:
3439
+ return False
3450
3440
 
3451
- if slurm_job_id:
3452
- hostname += f"-SLURM-ID-{slurm_job_id}"
3441
+ show_pareto_or_error_msg(path_to_calculate, res_names, disable_sixel_and_table)
3453
3442
 
3454
- total_memory = psutil.virtual_memory().total / (1024 * 1024)
3455
- cpu_usage = psutil.cpu_percent(interval=5)
3443
+ pf_end_time = time.time()
3456
3444
 
3457
- memory_usage = get_memory_usage()
3445
+ print_debug(f"Calculating the Pareto-front took {pf_end_time - pf_start_time} seconds")
3458
3446
 
3459
- unix_timestamp = int(time.time())
3447
+ return True
3460
3448
 
3461
- log_file.write(f"\nUnix-Timestamp: {unix_timestamp}, Hostname: {hostname}, CPU: {cpu_usage:.2f}%, RAM: {memory_usage:.2f} MB / {total_memory:.2f} MB\n")
3462
- time.sleep(self.interval)
3463
- except psutil.NoSuchProcess:
3464
- pass
3449
+ def show_pareto_or_error_msg(path_to_calculate: str, res_names: list = arg_result_names, disable_sixel_and_table: bool = False) -> None:
3450
+ if args.dryrun:
3451
+ print_debug("Not showing Pareto-frontier data with --dryrun")
3452
+ return None
3465
3453
 
3466
- def __enter__(self: Any) -> None:
3467
- self.thread.start()
3468
- return self
3454
+ if len(res_names) > 1:
3455
+ try:
3456
+ show_pareto_frontier_data(path_to_calculate, res_names, disable_sixel_and_table)
3457
+ except Exception as e:
3458
+ inner_tb = ''.join(traceback.format_exception(type(e), e, e.__traceback__))
3459
+ print_red(f"show_pareto_frontier_data() failed with exception '{e}':\n{inner_tb}")
3460
+ else:
3461
+ print_debug(f"show_pareto_frontier_data will NOT be executed because len(arg_result_names) is {len(arg_result_names)}")
3462
+ return None
3469
3463
 
3470
- def __exit__(self: Any, exc_type: Any, exc_value: Any, _traceback: Any) -> None:
3471
- self.running = False
3472
- self.thread.join()
3464
+ def get_pareto_front_data(path_to_calculate: str, res_names: list) -> dict:
3465
+ pareto_front_data: dict = {}
3473
3466
 
3474
- def execute_bash_code_log_time(code: str) -> list:
3475
- process_item = subprocess.Popen(code, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
3467
+ all_combinations = list(combinations(range(len(arg_result_names)), 2))
3476
3468
 
3477
- with MonitorProcess(process_item.pid):
3478
- try:
3479
- stdout, stderr = process_item.communicate()
3480
- result = subprocess.CompletedProcess(
3481
- args=code, returncode=process_item.returncode, stdout=stdout, stderr=stderr
3482
- )
3483
- return [result.stdout, result.stderr, result.returncode, None]
3484
- except subprocess.CalledProcessError as e:
3485
- real_exit_code = e.returncode
3486
- signal_code = None
3487
- if real_exit_code < 0:
3488
- signal_code = abs(e.returncode)
3489
- real_exit_code = 1
3490
- return [e.stdout, e.stderr, real_exit_code, signal_code]
3469
+ skip = False
3491
3470
 
3492
- def execute_bash_code(code: str) -> list:
3493
- try:
3494
- result = subprocess.run(
3495
- code,
3496
- shell=True,
3497
- check=True,
3498
- text=True,
3499
- capture_output=True
3500
- )
3471
+ for i, j in all_combinations:
3472
+ if not skip:
3473
+ metric_x = arg_result_names[i]
3474
+ metric_y = arg_result_names[j]
3501
3475
 
3502
- if result.returncode != 0:
3503
- print(f"Exit-Code: {result.returncode}")
3476
+ x_minimize = get_result_minimize_flag(path_to_calculate, metric_x)
3477
+ y_minimize = get_result_minimize_flag(path_to_calculate, metric_y)
3504
3478
 
3505
- real_exit_code = result.returncode
3479
+ try:
3480
+ if metric_x not in pareto_front_data:
3481
+ pareto_front_data[metric_x] = {}
3506
3482
 
3507
- signal_code = None
3508
- if real_exit_code < 0:
3509
- signal_code = abs(result.returncode)
3510
- real_exit_code = 1
3483
+ pareto_front_data[metric_x][metric_y] = get_calculated_frontier(path_to_calculate, metric_x, metric_y, x_minimize, y_minimize, res_names)
3484
+ except ax.exceptions.core.DataRequiredError as e:
3485
+ print_red(f"Error computing Pareto frontier for {metric_x} and {metric_y}: {e}")
3486
+ except SignalINT:
3487
+ print_red("Calculating Pareto-fronts was cancelled by pressing CTRL-c")
3488
+ skip = True
3511
3489
 
3512
- return [result.stdout, result.stderr, real_exit_code, signal_code]
3490
+ return pareto_front_data
3513
3491
 
3514
- except subprocess.CalledProcessError as e:
3515
- real_exit_code = e.returncode
3492
+ def pareto_front_transform_objectives(
3493
+ points: List[Tuple[Any, float, float]],
3494
+ primary_name: str,
3495
+ secondary_name: str
3496
+ ) -> Tuple[np.ndarray, np.ndarray]:
3497
+ primary_idx = arg_result_names.index(primary_name)
3498
+ secondary_idx = arg_result_names.index(secondary_name)
3516
3499
 
3517
- signal_code = None
3518
- if real_exit_code < 0:
3519
- signal_code = abs(e.returncode)
3520
- real_exit_code = 1
3500
+ x = np.array([p[1] for p in points])
3501
+ y = np.array([p[2] for p in points])
3521
3502
 
3522
- if not args.tests:
3523
- print(f"Error at execution of your program: {code}. Exit-Code: {real_exit_code}, Signal-Code: {signal_code}")
3524
- if len(e.stdout):
3525
- print(f"stdout: {e.stdout}")
3526
- else:
3527
- print("No stdout")
3503
+ if arg_result_min_or_max[primary_idx] == "max":
3504
+ x = -x
3505
+ elif arg_result_min_or_max[primary_idx] != "min":
3506
+ raise ValueError(f"Unknown mode for {primary_name}: {arg_result_min_or_max[primary_idx]}")
3507
+
3508
+ if arg_result_min_or_max[secondary_idx] == "max":
3509
+ y = -y
3510
+ elif arg_result_min_or_max[secondary_idx] != "min":
3511
+ raise ValueError(f"Unknown mode for {secondary_name}: {arg_result_min_or_max[secondary_idx]}")
3512
+
3513
+ return x, y
3514
+
3515
+ def get_pareto_frontier_points(
3516
+ path_to_calculate: str,
3517
+ primary_objective: str,
3518
+ secondary_objective: str,
3519
+ x_minimize: bool,
3520
+ y_minimize: bool,
3521
+ absolute_metrics: List[str],
3522
+ num_points: int
3523
+ ) -> Optional[dict]:
3524
+ records = pareto_front_aggregate_data(path_to_calculate)
3525
+
3526
+ if records is None:
3527
+ return None
3528
+
3529
+ points = pareto_front_filter_complete_points(path_to_calculate, records, primary_objective, secondary_objective)
3530
+ x, y = pareto_front_transform_objectives(points, primary_objective, secondary_objective)
3531
+ selected_points = pareto_front_select_pareto_points(x, y, x_minimize, y_minimize, points, num_points)
3532
+ result = pareto_front_build_return_structure(path_to_calculate, selected_points, records, absolute_metrics, primary_objective, secondary_objective)
3533
+
3534
+ return result
3535
+
3536
+ def pareto_front_table_read_csv() -> List[Dict[str, str]]:
3537
+ with open(RESULT_CSV_FILE, mode="r", encoding="utf-8", newline="") as f:
3538
+ return list(csv.DictReader(f))
3539
+
3540
+ def create_pareto_front_table(idxs: List[int], metric_x: str, metric_y: str) -> Table:
3541
+ table = Table(title=f"Pareto-Front for {metric_y}/{metric_x}:", show_lines=True)
3542
+
3543
+ rows = pareto_front_table_read_csv()
3544
+ if not rows:
3545
+ table.add_column("No data found")
3546
+ return table
3547
+
3548
+ filtered_rows = pareto_front_table_filter_rows(rows, idxs)
3549
+ if not filtered_rows:
3550
+ table.add_column("No matching entries")
3551
+ return table
3552
+
3553
+ param_cols, result_cols = pareto_front_table_get_columns(filtered_rows[0])
3554
+
3555
+ pareto_front_table_add_headers(table, param_cols, result_cols)
3556
+ pareto_front_table_add_rows(table, filtered_rows, param_cols, result_cols)
3557
+
3558
+ return table
3559
+
3560
+ def pareto_front_build_return_structure(
3561
+ path_to_calculate: str,
3562
+ selected_points: List[Tuple[Any, float, float]],
3563
+ records: Dict[Tuple[int, str], Dict[str, Dict[str, float]]],
3564
+ absolute_metrics: List[str],
3565
+ primary_name: str,
3566
+ secondary_name: str
3567
+ ) -> dict:
3568
+ results_csv_file = f"{path_to_calculate}/{RESULTS_CSV_FILENAME}"
3569
+ result_names_file = f"{path_to_calculate}/result_names.txt"
3570
+
3571
+ with open(result_names_file, mode="r", encoding="utf-8") as f:
3572
+ result_names = [line.strip() for line in f if line.strip()]
3573
+
3574
+ csv_rows = {}
3575
+ with open(results_csv_file, mode="r", encoding="utf-8", newline='') as csvfile:
3576
+ reader = csv.DictReader(csvfile)
3577
+ for row in reader:
3578
+ trial_index = int(row['trial_index'])
3579
+ csv_rows[trial_index] = row
3580
+
3581
+ ignored_columns = {'trial_index', 'arm_name', 'trial_status', 'generation_node'}
3582
+ ignored_columns.update(result_names)
3583
+
3584
+ param_dicts = []
3585
+ idxs = []
3586
+ means_dict = defaultdict(list)
3587
+
3588
+ for (trial_index, arm_name), _, _ in selected_points:
3589
+ row = csv_rows.get(trial_index, {})
3590
+ if row == {} or row is None or row['arm_name'] != arm_name:
3591
+ continue
3592
+
3593
+ idxs.append(int(row["trial_index"]))
3594
+
3595
+ param_dict: dict[str, int | float | str] = {}
3596
+ for key, value in row.items():
3597
+ if key not in ignored_columns:
3598
+ try:
3599
+ param_dict[key] = int(value)
3600
+ except ValueError:
3601
+ try:
3602
+ param_dict[key] = float(value)
3603
+ except ValueError:
3604
+ param_dict[key] = value
3605
+
3606
+ param_dicts.append(param_dict)
3607
+
3608
+ for metric in absolute_metrics:
3609
+ means_dict[metric].append(records[(trial_index, arm_name)]['means'].get(metric, float("nan")))
3610
+
3611
+ ret = {
3612
+ primary_name: {
3613
+ secondary_name: {
3614
+ "absolute_metrics": absolute_metrics,
3615
+ "param_dicts": param_dicts,
3616
+ "means": dict(means_dict),
3617
+ "idxs": idxs
3618
+ },
3619
+ "absolute_metrics": absolute_metrics
3620
+ }
3621
+ }
3622
+
3623
+ return ret
3624
+
3625
+ def pareto_front_aggregate_data(path_to_calculate: str) -> Optional[Dict[Tuple[int, str], Dict[str, Dict[str, float]]]]:
3626
+ results_csv_file = f"{path_to_calculate}/{RESULTS_CSV_FILENAME}"
3627
+ result_names_file = f"{path_to_calculate}/result_names.txt"
3628
+
3629
+ if not os.path.exists(results_csv_file) or not os.path.exists(result_names_file):
3630
+ return None
3631
+
3632
+ with open(result_names_file, mode="r", encoding="utf-8") as f:
3633
+ result_names = [line.strip() for line in f if line.strip()]
3634
+
3635
+ records: dict = defaultdict(lambda: {'means': {}})
3636
+
3637
+ with open(results_csv_file, encoding="utf-8", mode="r", newline='') as csvfile:
3638
+ reader = csv.DictReader(csvfile)
3639
+ for row in reader:
3640
+ trial_index = int(row['trial_index'])
3641
+ arm_name = row['arm_name']
3642
+ key = (trial_index, arm_name)
3643
+
3644
+ for metric in result_names:
3645
+ if metric in row:
3646
+ try:
3647
+ records[key]['means'][metric] = float(row[metric])
3648
+ except ValueError:
3649
+ continue
3650
+
3651
+ return records
3652
+
3653
+ def plot_pareto_frontier_sixel(data: Any, x_metric: str, y_metric: str) -> None:
3654
+ if data is None:
3655
+ print("[italic yellow]The data seems to be empty. Cannot plot pareto frontier.[/]")
3656
+ return
3657
+
3658
+ if not supports_sixel():
3659
+ print(f"[italic yellow]Your console does not support sixel-images. Will not print Pareto-frontier as a matplotlib-sixel-plot for {x_metric}/{y_metric}.[/]")
3660
+ return
3661
+
3662
+ import matplotlib.pyplot as plt
3663
+
3664
+ means = data[x_metric][y_metric]["means"]
3665
+
3666
+ x_values = means[x_metric]
3667
+ y_values = means[y_metric]
3668
+
3669
+ fig, _ax = plt.subplots()
3670
+
3671
+ _ax.scatter(x_values, y_values, s=50, marker='x', c='blue', label='Data Points')
3672
+
3673
+ _ax.set_xlabel(x_metric)
3674
+ _ax.set_ylabel(y_metric)
3675
+
3676
+ _ax.set_title(f'Pareto-Front {x_metric}/{y_metric}')
3677
+
3678
+ _ax.ticklabel_format(style='plain', axis='both', useOffset=False)
3679
+
3680
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=True) as tmp_file:
3681
+ plt.savefig(tmp_file.name, dpi=300)
3682
+
3683
+ print_image_to_cli(tmp_file.name, 1000)
3684
+
3685
+ plt.close(fig)
3686
+
3687
+ def pareto_front_table_get_columns(first_row: Dict[str, str]) -> Tuple[List[str], List[str]]:
3688
+ all_columns = list(first_row.keys())
3689
+ ignored_cols = set(special_col_names) - {"trial_index"}
3690
+
3691
+ param_cols = [col for col in all_columns if col not in ignored_cols and col not in arg_result_names and not col.startswith("OO_Info_")]
3692
+ result_cols = [col for col in arg_result_names if col in all_columns]
3693
+ return param_cols, result_cols
3694
+
3695
+ def check_factorial_range() -> None:
3696
+ if args.model and args.model == "FACTORIAL":
3697
+ _fatal_error("\n⚠ --model FACTORIAL cannot be used with range parameter", 181)
3698
+
3699
+ def check_if_range_types_are_invalid(value_type: str, valid_value_types: list) -> None:
3700
+ if value_type not in valid_value_types:
3701
+ valid_value_types_string = ", ".join(valid_value_types)
3702
+ _fatal_error(f"⚠ {value_type} is not a valid value type. Valid types for range are: {valid_value_types_string}", 181)
3703
+
3704
+ def check_range_params_length(this_args: Union[str, list]) -> None:
3705
+ if len(this_args) != 5 and len(this_args) != 4 and len(this_args) != 6:
3706
+ _fatal_error("\n⚠ --parameter for type range must have 4 (or 5, the last one being optional and float by default, or 6, while the last one is true or false) parameters: <NAME> range <START> <END> (<TYPE (int or float)>, <log_scale: bool>)", 181)
3707
+
3708
+ def die_if_lower_and_upper_bound_equal_zero(lower_bound: Union[int, float], upper_bound: Union[int, float]) -> None:
3709
+ if upper_bound is None or lower_bound is None:
3710
+ _fatal_error("die_if_lower_and_upper_bound_equal_zero: upper_bound or lower_bound is None. Cannot continue.", 91)
3711
+ if upper_bound == lower_bound:
3712
+ if lower_bound == 0:
3713
+ _fatal_error(f"⚠ Lower bound and upper bound are equal: {lower_bound}, cannot automatically fix this, because they -0 = +0 (usually a quickfix would be to set lower_bound = -upper_bound)", 181)
3714
+ print_red(f"⚠ Lower bound and upper bound are equal: {lower_bound}, setting lower_bound = -upper_bound")
3715
+ if upper_bound is not None:
3716
+ lower_bound = -upper_bound
3717
+
3718
+ def format_value(value: Any, float_format: str = '.80f') -> str:
3719
+ try:
3720
+ if isinstance(value, float):
3721
+ s = format(value, float_format)
3722
+ s = s.rstrip('0').rstrip('.') if '.' in s else s
3723
+ return s
3724
+ return str(value)
3725
+ except Exception as e:
3726
+ print_red(f"⚠ Error formatting the number {value}: {e}")
3727
+ return str(value)
3728
+
3729
+ def replace_parameters_in_string(
3730
+ parameters: dict,
3731
+ input_string: str,
3732
+ float_format: str = '.20f',
3733
+ additional_prefixes: list[str] = [],
3734
+ additional_patterns: list[str] = [],
3735
+ ) -> str:
3736
+ try:
3737
+ prefixes = ['$', '%'] + additional_prefixes
3738
+ patterns = ['{' + 'key' + '}', '(' + '{' + 'key' + '}' + ')'] + additional_patterns
3739
+
3740
+ for key, value in parameters.items():
3741
+ replacement = format_value(value, float_format=float_format)
3742
+ for prefix in prefixes:
3743
+ for pattern in patterns:
3744
+ token = prefix + pattern.format(key=key)
3745
+ input_string = input_string.replace(token, replacement)
3746
+
3747
+ input_string = input_string.replace('\r', ' ').replace('\n', ' ')
3748
+ return input_string
3749
+
3750
+ except Exception as e:
3751
+ print_red(f"\n⚠ Error: {e}")
3752
+ return ""
3753
+
3754
+ def get_memory_usage() -> float:
3755
+ user_uid = os.getuid()
3756
+
3757
+ memory_usage = float(sum(
3758
+ p.memory_info().rss for p in psutil.process_iter(attrs=['memory_info', 'uids'])
3759
+ if p.info['uids'].real == user_uid
3760
+ ) / (1024 * 1024))
3761
+
3762
+ return memory_usage
3763
+
3764
+ class MonitorProcess:
3765
+ def __init__(self: Any, pid: int, interval: float = 1.0) -> None:
3766
+ self.pid = pid
3767
+ self.interval = interval
3768
+ self.running = True
3769
+ self.thread = threading.Thread(target=self._monitor)
3770
+ self.thread.daemon = True
3771
+
3772
+ fool_linter(f"self.thread.daemon was set to {self.thread.daemon}")
3773
+
3774
+ def _monitor(self: Any) -> None:
3775
+ try:
3776
+ _internal_process = psutil.Process(self.pid)
3777
+ while self.running and _internal_process.is_running():
3778
+ crf = get_current_run_folder()
3779
+
3780
+ if crf and crf != "":
3781
+ log_file_path = os.path.join(crf, "eval_nodes_cpu_ram_logs.txt")
3782
+
3783
+ os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
3784
+
3785
+ with open(log_file_path, mode="a", encoding="utf-8") as log_file:
3786
+ hostname = socket.gethostname()
3787
+
3788
+ slurm_job_id = os.getenv("SLURM_JOB_ID")
3789
+
3790
+ if slurm_job_id:
3791
+ hostname += f"-SLURM-ID-{slurm_job_id}"
3792
+
3793
+ total_memory = psutil.virtual_memory().total / (1024 * 1024)
3794
+ cpu_usage = psutil.cpu_percent(interval=5)
3795
+
3796
+ memory_usage = get_memory_usage()
3797
+
3798
+ unix_timestamp = int(time.time())
3799
+
3800
+ log_file.write(f"\nUnix-Timestamp: {unix_timestamp}, Hostname: {hostname}, CPU: {cpu_usage:.2f}%, RAM: {memory_usage:.2f} MB / {total_memory:.2f} MB\n")
3801
+ time.sleep(self.interval)
3802
+ except psutil.NoSuchProcess:
3803
+ pass
3804
+
3805
+ def __enter__(self: Any) -> None:
3806
+ self.thread.start()
3807
+ return self
3808
+
3809
+ def __exit__(self: Any, exc_type: Any, exc_value: Any, _traceback: Any) -> None:
3810
+ self.running = False
3811
+ self.thread.join()
3812
+
3813
+ def execute_bash_code_log_time(code: str) -> list:
3814
+ process_item = subprocess.Popen(code, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
3815
+
3816
+ with MonitorProcess(process_item.pid):
3817
+ try:
3818
+ stdout, stderr = process_item.communicate()
3819
+ result = subprocess.CompletedProcess(
3820
+ args=code, returncode=process_item.returncode, stdout=stdout, stderr=stderr
3821
+ )
3822
+ return [result.stdout, result.stderr, result.returncode, None]
3823
+ except subprocess.CalledProcessError as e:
3824
+ real_exit_code = e.returncode
3825
+ signal_code = None
3826
+ if real_exit_code < 0:
3827
+ signal_code = abs(e.returncode)
3828
+ real_exit_code = 1
3829
+ return [e.stdout, e.stderr, real_exit_code, signal_code]
3830
+
3831
+ def execute_bash_code(code: str) -> list:
3832
+ try:
3833
+ result = subprocess.run(
3834
+ code,
3835
+ shell=True,
3836
+ check=True,
3837
+ text=True,
3838
+ capture_output=True
3839
+ )
3840
+
3841
+ if result.returncode != 0:
3842
+ print(f"Exit-Code: {result.returncode}")
3843
+
3844
+ real_exit_code = result.returncode
3845
+
3846
+ signal_code = None
3847
+ if real_exit_code < 0:
3848
+ signal_code = abs(result.returncode)
3849
+ real_exit_code = 1
3850
+
3851
+ return [result.stdout, result.stderr, real_exit_code, signal_code]
3852
+
3853
+ except subprocess.CalledProcessError as e:
3854
+ real_exit_code = e.returncode
3855
+
3856
+ signal_code = None
3857
+ if real_exit_code < 0:
3858
+ signal_code = abs(e.returncode)
3859
+ real_exit_code = 1
3860
+
3861
+ if not args.tests:
3862
+ print(f"Error at execution of your program: {code}. Exit-Code: {real_exit_code}, Signal-Code: {signal_code}")
3863
+ if len(e.stdout):
3864
+ print(f"stdout: {e.stdout}")
3865
+ else:
3866
+ print("No stdout")
3528
3867
 
3529
3868
  if len(e.stderr):
3530
3869
  print(f"stderr: {e.stderr}")
@@ -3604,7 +3943,7 @@ def _add_to_csv_acquire_lock(lockfile: str, dir_path: str) -> bool:
3604
3943
  time.sleep(wait_time)
3605
3944
  max_wait -= wait_time
3606
3945
  except Exception as e:
3607
- print("Lock error:", e)
3946
+ print_red(f"Lock error: {e}")
3608
3947
  return False
3609
3948
  return False
3610
3949
 
@@ -3677,12 +4016,12 @@ def find_file_paths(_text: str) -> List[str]:
3677
4016
  def check_file_info(file_path: str) -> str:
3678
4017
  if not os.path.exists(file_path):
3679
4018
  if not args.tests:
3680
- print(f"check_file_info: The file {file_path} does not exist.")
4019
+ print_red(f"check_file_info: The file {file_path} does not exist.")
3681
4020
  return ""
3682
4021
 
3683
4022
  if not os.access(file_path, os.R_OK):
3684
4023
  if not args.tests:
3685
- print(f"check_file_info: The file {file_path} is not readable.")
4024
+ print_red(f"check_file_info: The file {file_path} is not readable.")
3686
4025
  return ""
3687
4026
 
3688
4027
  file_stat = os.stat(file_path)
@@ -3796,7 +4135,7 @@ def count_defective_nodes(file_path: Union[str, None] = None, entry: Any = None)
3796
4135
  return sorted(set(entries))
3797
4136
 
3798
4137
  except Exception as e:
3799
- print(f"An error has occurred: {e}")
4138
+ print_red(f"An error has occurred: {e}")
3800
4139
  return []
3801
4140
 
3802
4141
  def test_gpu_before_evaluate(return_in_case_of_error: dict) -> Union[None, dict]:
@@ -3807,7 +4146,7 @@ def test_gpu_before_evaluate(return_in_case_of_error: dict) -> Union[None, dict]
3807
4146
 
3808
4147
  fool_linter(tmp)
3809
4148
  except RuntimeError:
3810
- print(f"Node {socket.gethostname()} was detected as faulty. It should have had a GPU, but there is an error initializing the CUDA driver. Adding this node to the --exclude list.")
4149
+ print_red(f"Node {socket.gethostname()} was detected as faulty. It should have had a GPU, but there is an error initializing the CUDA driver. Adding this node to the --exclude list.")
3811
4150
  count_defective_nodes(None, socket.gethostname())
3812
4151
  return return_in_case_of_error
3813
4152
  except Exception:
@@ -4517,7 +4856,7 @@ def replace_string_with_params(input_string: str, params: list) -> str:
4517
4856
  return replaced_string
4518
4857
  except AssertionError as e:
4519
4858
  error_text = f"Error in replace_string_with_params: {e}"
4520
- print(error_text)
4859
+ print_red(error_text)
4521
4860
  raise
4522
4861
 
4523
4862
  return ""
@@ -4794,9 +5133,7 @@ def get_sixel_graphics_data(_pd_csv: str, _force: bool = False) -> list:
4794
5133
  _params = [_command, plot, _tmp, plot_type, tmp_file, _width]
4795
5134
  data.append(_params)
4796
5135
  except Exception as e:
4797
- tb = traceback.format_exc()
4798
- print_red(f"Error trying to print {plot_type} to CLI: {e}, {tb}")
4799
- print_debug(f"Error trying to print {plot_type} to CLI: {e}")
5136
+ print_red(f"Error trying to print {plot_type} to CLI: {e}")
4800
5137
 
4801
5138
  return data
4802
5139
 
@@ -4997,8 +5334,7 @@ def abandon_job(job: Job, trial_index: int, reason: str) -> bool:
4997
5334
  else:
4998
5335
  _fatal_error("ax_client could not be found", 101)
4999
5336
  except Exception as e:
5000
- print(f"ERROR in line {get_line_info()}: {e}")
5001
- print_debug(f"ERROR in line {get_line_info()}: {e}")
5337
+ print_red(f"ERROR in line {get_line_info()}: {e}")
5002
5338
  return False
5003
5339
  job.cancel()
5004
5340
  return True
@@ -5011,21 +5347,6 @@ def abandon_all_jobs() -> None:
5011
5347
  if not abandoned:
5012
5348
  print_debug(f"Job {job} could not be abandoned.")
5013
5349
 
5014
- def show_pareto_or_error_msg(path_to_calculate: str, res_names: list = arg_result_names, disable_sixel_and_table: bool = False) -> None:
5015
- if args.dryrun:
5016
- print_debug("Not showing Pareto-frontier data with --dryrun")
5017
- return None
5018
-
5019
- if len(res_names) > 1:
5020
- try:
5021
- show_pareto_frontier_data(path_to_calculate, res_names, disable_sixel_and_table)
5022
- except Exception as e:
5023
- inner_tb = ''.join(traceback.format_exception(type(e), e, e.__traceback__))
5024
- print_red(f"show_pareto_frontier_data() failed with exception '{e}':\n{inner_tb}")
5025
- else:
5026
- print_debug(f"show_pareto_frontier_data will NOT be executed because len(arg_result_names) is {len(arg_result_names)}")
5027
- return None
5028
-
5029
5350
  def end_program(_force: Optional[bool] = False, exit_code: Optional[int] = None) -> None:
5030
5351
  global END_PROGRAM_RAN
5031
5352
 
@@ -5062,7 +5383,7 @@ def end_program(_force: Optional[bool] = False, exit_code: Optional[int] = None)
5062
5383
  _exit = new_exit
5063
5384
  except (SignalUSR, SignalINT, SignalCONT, KeyboardInterrupt):
5064
5385
  print_red("\n⚠ You pressed CTRL+C or a signal was sent. Program execution halted while ending program.")
5065
- print("\n⚠ KeyboardInterrupt signal was sent. Ending program will still run.")
5386
+ print_red("\n⚠ KeyboardInterrupt signal was sent. Ending program will still run.")
5066
5387
  new_exit = show_end_table_and_save_end_files()
5067
5388
  if new_exit > 0:
5068
5389
  _exit = new_exit
@@ -5096,9 +5417,9 @@ def save_ax_client_to_json_file(checkpoint_filepath: str) -> None:
5096
5417
  def save_checkpoint(trial_nr: int = 0, eee: Union[None, str, Exception] = None) -> None:
5097
5418
  if trial_nr > 3:
5098
5419
  if eee:
5099
- print(f"Error during saving checkpoint: {eee}")
5420
+ print_red(f"Error during saving checkpoint: {eee}")
5100
5421
  else:
5101
- print("Error during saving checkpoint")
5422
+ print_red("Error during saving checkpoint")
5102
5423
  return
5103
5424
 
5104
5425
  try:
@@ -5268,7 +5589,7 @@ def parse_equation_item(comparer_found: bool, item: str, parsed: list, parsed_or
5268
5589
  })
5269
5590
  elif item in [">=", "<="]:
5270
5591
  if comparer_found:
5271
- print("There is already one comparison operator! Cannot have more than one in an equation!")
5592
+ print_red("There is already one comparison operator! Cannot have more than one in an equation!")
5272
5593
  return_totally = True
5273
5594
  comparer_found = True
5274
5595
 
@@ -5956,7 +6277,6 @@ def parse_single_experiment_parameter_table(classic_params: Optional[Union[list,
5956
6277
  _upper = param["bounds"][1]
5957
6278
 
5958
6279
  _possible_int_lower = str(helpers.to_int_when_possible(_lower))
5959
- #print(f"name: {_name}, _possible_int_lower: {_possible_int_lower}, lower: {_lower}")
5960
6280
  _possible_int_upper = str(helpers.to_int_when_possible(_upper))
5961
6281
 
5962
6282
  rows.append([_name, _short_type, _possible_int_lower, _possible_int_upper, "", value_type, log_scale])
@@ -6189,7 +6509,7 @@ def update_progress_bar(nr: int) -> None:
6189
6509
  try:
6190
6510
  progress_bar.update(nr)
6191
6511
  except Exception as e:
6192
- print(f"Error updating progress bar: {e}")
6512
+ print_red(f"Error updating progress bar: {e}")
6193
6513
  else:
6194
6514
  print_red("update_progress_bar: progress_bar was None")
6195
6515
 
@@ -6197,19 +6517,17 @@ def get_current_model_name() -> str:
6197
6517
  if overwritten_to_random:
6198
6518
  return "Random*"
6199
6519
 
6520
+ gs_model = "unknown model"
6521
+
6200
6522
  if ax_client:
6201
6523
  try:
6202
6524
  if args.generation_strategy:
6203
- idx = getattr(ax_client.generation_strategy, "current_step_index", None)
6525
+ idx = getattr(global_gs, "current_step_index", None)
6204
6526
  if isinstance(idx, int):
6205
6527
  if 0 <= idx < len(generation_strategy_names):
6206
6528
  gs_model = generation_strategy_names[int(idx)]
6207
- else:
6208
- gs_model = "unknown model"
6209
- else:
6210
- gs_model = "unknown model"
6211
6529
  else:
6212
- gs_model = getattr(ax_client.generation_strategy, "current_node_name", "unknown model")
6530
+ gs_model = getattr(global_gs, "current_node_name", "unknown model")
6213
6531
 
6214
6532
  if gs_model:
6215
6533
  return str(gs_model)
@@ -6629,7 +6947,7 @@ def get_generation_node_for_index(
6629
6947
 
6630
6948
  return generation_node
6631
6949
  except Exception as e:
6632
- print(f"Error while get_generation_node_for_index: {e}")
6950
+ print_red(f"Error while get_generation_node_for_index: {e}")
6633
6951
  return "MANUAL"
6634
6952
 
6635
6953
  def _get_generation_node_for_index_index_valid(
@@ -7244,7 +7562,7 @@ def get_parameters_from_outfile(stdout_path: str) -> Union[None, dict, str]:
7244
7562
  if not args.tests:
7245
7563
  original_print(f"get_parameters_from_outfile: The file '{stdout_path}' was not found.")
7246
7564
  except Exception as e:
7247
- print(f"get_parameters_from_outfile: There was an error: {e}")
7565
+ print_red(f"get_parameters_from_outfile: There was an error: {e}")
7248
7566
 
7249
7567
  return None
7250
7568
 
@@ -7262,7 +7580,7 @@ def get_hostname_from_outfile(stdout_path: Optional[str]) -> Optional[str]:
7262
7580
  original_print(f"The file '{stdout_path}' was not found.")
7263
7581
  return None
7264
7582
  except Exception as e:
7265
- print(f"There was an error: {e}")
7583
+ print_red(f"There was an error: {e}")
7266
7584
  return None
7267
7585
 
7268
7586
  def add_to_global_error_list(msg: str) -> None:
@@ -7325,7 +7643,7 @@ def check_valid_result(result: Union[None, dict]) -> bool:
7325
7643
  else:
7326
7644
  values.append(obj)
7327
7645
  except Exception as e:
7328
- print(f"Error while flattening values: {e}")
7646
+ print_red(f"Error while flattening values: {e}")
7329
7647
  return values
7330
7648
 
7331
7649
  if result is None:
@@ -7338,7 +7656,7 @@ def check_valid_result(result: Union[None, dict]) -> bool:
7338
7656
  return False
7339
7657
  return True
7340
7658
  except Exception as e:
7341
- print(f"Error while checking result validity: {e}")
7659
+ print_red(f"Error while checking result validity: {e}")
7342
7660
  return False
7343
7661
 
7344
7662
  def update_ax_client_trial(trial_idx: int, result: Union[list, dict]) -> None:
@@ -7474,7 +7792,7 @@ def finish_job_core(job: Any, trial_index: int, this_jobs_finished: int) -> int:
7474
7792
  if len(arg_result_names) > 1 and count_done_jobs() > 1 and not job_calculate_pareto_front(get_current_run_folder(), True):
7475
7793
  print_red("job_calculate_pareto_front post job failed")
7476
7794
  except Exception as e:
7477
- print(f"ERROR in line {get_line_info()}: {e}")
7795
+ print_red(f"ERROR in line {get_line_info()}: {e}")
7478
7796
  else:
7479
7797
  _finish_job_core_helper_mark_failure(job, trial_index, _trial)
7480
7798
  else:
@@ -7813,7 +8131,7 @@ def handle_restart(stdout_path: str, trial_index: int) -> None:
7813
8131
  if parameters:
7814
8132
  orchestrator_start_trial(parameters, trial_index)
7815
8133
  else:
7816
- print(f"Could not determine parameters from outfile {stdout_path} for restarting job")
8134
+ print_red(f"Could not determine parameters from outfile {stdout_path} for restarting job")
7817
8135
 
7818
8136
  def check_alternate_path(path: str) -> str:
7819
8137
  if os.path.exists(path):
@@ -7981,7 +8299,7 @@ def handle_failed_job(error: Union[None, Exception, str], trial_index: int, new_
7981
8299
  my_exit(144)
7982
8300
 
7983
8301
  if new_job is None:
7984
- print_red("handle_failed_job: job is None")
8302
+ print_debug("handle_failed_job: job is None")
7985
8303
 
7986
8304
  return None
7987
8305
 
@@ -8009,7 +8327,7 @@ def cancel_failed_job(trial_index: int, new_job: Job) -> None:
8009
8327
  else:
8010
8328
  _fatal_error("ax_client not defined", 101)
8011
8329
  except Exception as e:
8012
- print(f"ERROR in line {get_line_info()}: {e}")
8330
+ print_red(f"ERROR in line {get_line_info()}: {e}")
8013
8331
  new_job.cancel()
8014
8332
 
8015
8333
  print_debug(f"cancel_failed_job: removing job {new_job}, trial_index: {trial_index}")
@@ -8309,7 +8627,7 @@ def mark_abandoned(trial: Any, reason: str, trial_index: int) -> None:
8309
8627
  print_debug(f"[INFO] Marking trial {trial.index} ({trial.arm.name}) as abandoned, trial-index: {trial_index}. Reason: {reason}")
8310
8628
  trial.mark_abandoned(reason)
8311
8629
  except Exception as e:
8312
- print(f"[ERROR] Could not mark trial as abandoned: {e}")
8630
+ print_red(f"[ERROR] Could not mark trial as abandoned: {e}")
8313
8631
 
8314
8632
  def create_and_handle_trial(arm: Any) -> Optional[Tuple[int, float, bool]]:
8315
8633
  if ax_client is None:
@@ -8385,7 +8703,7 @@ def handle_generation_failure(
8385
8703
 
8386
8704
  if not recursion and args.revert_to_random_when_seemingly_exhausted:
8387
8705
  print_debug("Switching to random search strategy.")
8388
- set_global_gs_to_random()
8706
+ set_global_gs_to_sobol()
8389
8707
  return fetch_next_trials(requested, True)
8390
8708
 
8391
8709
  print_red(f"handle_generation_failure: General Exception: {e}")
@@ -8437,15 +8755,18 @@ def get_model_gen_kwargs() -> dict:
8437
8755
  "fit_out_of_design": args.fit_out_of_design
8438
8756
  }
8439
8757
 
8440
- def set_global_gs_to_random() -> None:
8758
+ def set_global_gs_to_sobol() -> None:
8441
8759
  global global_gs
8442
8760
  global overwritten_to_random
8443
8761
 
8762
+ print("Reverting to SOBOL")
8763
+
8444
8764
  global_gs = GenerationStrategy(
8445
8765
  name="Random*",
8446
8766
  nodes=[
8447
8767
  GenerationNode(
8448
8768
  node_name="Sobol",
8769
+ should_deduplicate=True,
8449
8770
  generator_specs=[ # type: ignore[arg-type]
8450
8771
  GeneratorSpec( # type: ignore[arg-type]
8451
8772
  Models.SOBOL, # type: ignore[arg-type]
@@ -8836,10 +9157,10 @@ def parse_generation_strategy_string(gen_strat_str: str) -> tuple[list[dict[str,
8836
9157
 
8837
9158
  for s in splitted_by_comma:
8838
9159
  if "=" not in s:
8839
- print(f"'{s}' does not contain '='")
9160
+ print_red(f"'{s}' does not contain '='")
8840
9161
  my_exit(123)
8841
9162
  if s.count("=") != 1:
8842
- print(f"There can only be one '=' in the gen_strat_str's element '{s}'")
9163
+ print_red(f"There can only be one '=' in the gen_strat_str's element '{s}'")
8843
9164
  my_exit(123)
8844
9165
 
8845
9166
  model_name, nr_str = s.split("=")
@@ -8849,13 +9170,13 @@ def parse_generation_strategy_string(gen_strat_str: str) -> tuple[list[dict[str,
8849
9170
  _fatal_error(f"Model {matching_model} is not valid for custom generation strategy.", 56)
8850
9171
 
8851
9172
  if not matching_model:
8852
- print(f"'{model_name}' not found in SUPPORTED_MODELS")
9173
+ print_red(f"'{model_name}' not found in SUPPORTED_MODELS")
8853
9174
  my_exit(123)
8854
9175
 
8855
9176
  try:
8856
9177
  nr = int(nr_str)
8857
9178
  except ValueError:
8858
- print(f"Invalid number of generations '{nr_str}' for model '{model_name}'")
9179
+ print_red(f"Invalid number of generations '{nr_str}' for model '{model_name}'")
8859
9180
  my_exit(123)
8860
9181
 
8861
9182
  gen_strat_list.append({matching_model: nr})
@@ -9017,6 +9338,7 @@ def create_node(model_name: str, threshold: int, next_model_name: Optional[str])
9017
9338
  res = GenerationNode(
9018
9339
  node_name=model_name,
9019
9340
  generator_specs=model_spec,
9341
+ should_deduplicate=True,
9020
9342
  transition_criteria=trans_crit
9021
9343
  )
9022
9344
 
@@ -9412,7 +9734,7 @@ def execute_nvidia_smi() -> None:
9412
9734
  if not host:
9413
9735
  print_debug("host not defined")
9414
9736
  except Exception as e:
9415
- print(f"execute_nvidia_smi: An error occurred: {e}")
9737
+ print_red(f"execute_nvidia_smi: An error occurred: {e}")
9416
9738
  if is_slurm_job() and not args.force_local_execution:
9417
9739
  _sleep(30)
9418
9740
 
@@ -9581,374 +9903,62 @@ def parse_orchestrator_file(_f: str, _test: bool = False) -> Union[dict, None]:
9581
9903
  print_red("x['match_strings'] is not a string but {type(x['match_strings'])}")
9582
9904
  die_orchestrator_exit_code_206(_test)
9583
9905
 
9584
- return data
9585
- except Exception as e:
9586
- print(f"Error while parse_experiment_parameters({_f}): {e}")
9587
- else:
9588
- print_red(f"{_f} could not be found")
9589
-
9590
- return None
9591
-
9592
- def set_orchestrator() -> None:
9593
- with spinner("Setting orchestrator..."):
9594
- global orchestrator
9595
-
9596
- if args.orchestrator_file:
9597
- if SYSTEM_HAS_SBATCH:
9598
- orchestrator = parse_orchestrator_file(args.orchestrator_file, False)
9599
- else:
9600
- print_yellow("--orchestrator_file will be ignored on non-sbatch-systems.")
9601
-
9602
- def check_if_has_random_steps() -> None:
9603
- if (not args.continue_previous_job and "--continue" not in sys.argv) and (args.num_random_steps == 0 or not args.num_random_steps) and args.model not in ["EXTERNAL_GENERATOR", "SOBOL", "PSEUDORANDOM"]:
9604
- _fatal_error("You have no random steps set. This is only allowed in continued jobs. To start, you need either some random steps, or a continued run.", 233)
9605
-
9606
- def add_exclude_to_defective_nodes() -> None:
9607
- with spinner("Adding excluded nodes..."):
9608
- if args.exclude:
9609
- entries = [entry.strip() for entry in args.exclude.split(',')]
9610
-
9611
- for entry in entries:
9612
- count_defective_nodes(None, entry)
9613
-
9614
- def check_max_eval(_max_eval: int) -> None:
9615
- with spinner("Checking max_eval..."):
9616
- if not _max_eval:
9617
- _fatal_error("--max_eval needs to be set!", 19)
9618
-
9619
- def parse_parameters() -> Any:
9620
- cli_params_experiment_parameters = None
9621
- if args.parameter:
9622
- parse_experiment_parameters()
9623
- cli_params_experiment_parameters = experiment_parameters
9624
-
9625
- return cli_params_experiment_parameters
9626
-
9627
- def create_pareto_front_table(idxs: List[int], metric_x: str, metric_y: str) -> Table:
9628
- table = Table(title=f"Pareto-Front for {metric_y}/{metric_x}:", show_lines=True)
9629
-
9630
- rows = pareto_front_table_read_csv()
9631
- if not rows:
9632
- table.add_column("No data found")
9633
- return table
9634
-
9635
- filtered_rows = pareto_front_table_filter_rows(rows, idxs)
9636
- if not filtered_rows:
9637
- table.add_column("No matching entries")
9638
- return table
9639
-
9640
- param_cols, result_cols = pareto_front_table_get_columns(filtered_rows[0])
9641
-
9642
- pareto_front_table_add_headers(table, param_cols, result_cols)
9643
- pareto_front_table_add_rows(table, filtered_rows, param_cols, result_cols)
9644
-
9645
- return table
9646
-
9647
- def pareto_front_table_read_csv() -> List[Dict[str, str]]:
9648
- with open(RESULT_CSV_FILE, mode="r", encoding="utf-8", newline="") as f:
9649
- return list(csv.DictReader(f))
9650
-
9651
- def pareto_front_table_filter_rows(rows: List[Dict[str, str]], idxs: List[int]) -> List[Dict[str, str]]:
9652
- result = []
9653
- for row in rows:
9654
- try:
9655
- trial_index = int(row["trial_index"])
9656
- except (KeyError, ValueError):
9657
- continue
9658
-
9659
- if row.get("trial_status", "").strip().upper() == "COMPLETED" and trial_index in idxs:
9660
- result.append(row)
9661
- return result
9662
-
9663
- def pareto_front_table_get_columns(first_row: Dict[str, str]) -> Tuple[List[str], List[str]]:
9664
- all_columns = list(first_row.keys())
9665
- ignored_cols = set(special_col_names) - {"trial_index"}
9666
-
9667
- param_cols = [col for col in all_columns if col not in ignored_cols and col not in arg_result_names and not col.startswith("OO_Info_")]
9668
- result_cols = [col for col in arg_result_names if col in all_columns]
9669
- return param_cols, result_cols
9670
-
9671
- def pareto_front_table_add_headers(table: Table, param_cols: List[str], result_cols: List[str]) -> None:
9672
- for col in param_cols:
9673
- table.add_column(col, justify="center")
9674
- for col in result_cols:
9675
- table.add_column(Text(f"{col}", style="cyan"), justify="center")
9676
-
9677
- def pareto_front_table_add_rows(table: Table, rows: List[Dict[str, str]], param_cols: List[str], result_cols: List[str]) -> None:
9678
- for row in rows:
9679
- values = [str(helpers.to_int_when_possible(row[col])) for col in param_cols]
9680
- result_values = [Text(str(helpers.to_int_when_possible(row[col])), style="cyan") for col in result_cols]
9681
- table.add_row(*values, *result_values, style="bold green")
9682
-
9683
- def pareto_front_as_rich_table(idxs: list, metric_x: str, metric_y: str) -> Optional[Table]:
9684
- if not os.path.exists(RESULT_CSV_FILE):
9685
- print_debug(f"pareto_front_as_rich_table: File '{RESULT_CSV_FILE}' not found")
9686
- return None
9687
-
9688
- return create_pareto_front_table(idxs, metric_x, metric_y)
9689
-
9690
- def supports_sixel() -> bool:
9691
- term = os.environ.get("TERM", "").lower()
9692
- if "xterm" in term or "mlterm" in term:
9693
- return True
9694
-
9695
- try:
9696
- output = subprocess.run(["tput", "setab", "256"], capture_output=True, text=True, check=True)
9697
- if output.returncode == 0 and "sixel" in output.stdout.lower():
9698
- return True
9699
- except (subprocess.CalledProcessError, FileNotFoundError):
9700
- pass
9701
-
9702
- return False
9703
-
9704
- def plot_pareto_frontier_sixel(data: Any, x_metric: str, y_metric: str) -> None:
9705
- if data is None:
9706
- print("[italic yellow]The data seems to be empty. Cannot plot pareto frontier.[/]")
9707
- return
9708
-
9709
- if not supports_sixel():
9710
- print(f"[italic yellow]Your console does not support sixel-images. Will not print Pareto-frontier as a matplotlib-sixel-plot for {x_metric}/{y_metric}.[/]")
9711
- return
9712
-
9713
- import matplotlib.pyplot as plt
9714
-
9715
- means = data[x_metric][y_metric]["means"]
9716
-
9717
- x_values = means[x_metric]
9718
- y_values = means[y_metric]
9719
-
9720
- fig, _ax = plt.subplots()
9721
-
9722
- _ax.scatter(x_values, y_values, s=50, marker='x', c='blue', label='Data Points')
9723
-
9724
- _ax.set_xlabel(x_metric)
9725
- _ax.set_ylabel(y_metric)
9726
-
9727
- _ax.set_title(f'Pareto-Front {x_metric}/{y_metric}')
9728
-
9729
- _ax.ticklabel_format(style='plain', axis='both', useOffset=False)
9730
-
9731
- with tempfile.NamedTemporaryFile(suffix=".png", delete=True) as tmp_file:
9732
- plt.savefig(tmp_file.name, dpi=300)
9733
-
9734
- print_image_to_cli(tmp_file.name, 1000)
9735
-
9736
- plt.close(fig)
9737
-
9738
- def pareto_front_general_validate_shapes(x: np.ndarray, y: np.ndarray) -> None:
9739
- if x.shape != y.shape:
9740
- raise ValueError("Input arrays x and y must have the same shape.")
9741
-
9742
- def pareto_front_general_compare(
9743
- xi: float, yi: float, xj: float, yj: float,
9744
- x_minimize: bool, y_minimize: bool
9745
- ) -> bool:
9746
- x_better_eq = xj <= xi if x_minimize else xj >= xi
9747
- y_better_eq = yj <= yi if y_minimize else yj >= yi
9748
- x_strictly_better = xj < xi if x_minimize else xj > xi
9749
- y_strictly_better = yj < yi if y_minimize else yj > yi
9750
-
9751
- return bool(x_better_eq and y_better_eq and (x_strictly_better or y_strictly_better))
9752
-
9753
- def pareto_front_general_find_dominated(
9754
- x: np.ndarray, y: np.ndarray, x_minimize: bool, y_minimize: bool
9755
- ) -> np.ndarray:
9756
- num_points = len(x)
9757
- is_dominated = np.zeros(num_points, dtype=bool)
9758
-
9759
- for i in range(num_points):
9760
- for j in range(num_points):
9761
- if i == j:
9762
- continue
9763
-
9764
- if pareto_front_general_compare(x[i], y[i], x[j], y[j], x_minimize, y_minimize):
9765
- is_dominated[i] = True
9766
- break
9767
-
9768
- return is_dominated
9769
-
9770
- def pareto_front_general(
9771
- x: np.ndarray,
9772
- y: np.ndarray,
9773
- x_minimize: bool = True,
9774
- y_minimize: bool = True
9775
- ) -> np.ndarray:
9776
- try:
9777
- pareto_front_general_validate_shapes(x, y)
9778
- is_dominated = pareto_front_general_find_dominated(x, y, x_minimize, y_minimize)
9779
- return np.where(~is_dominated)[0]
9780
- except Exception as e:
9781
- print("Error in pareto_front_general:", str(e))
9782
- return np.array([], dtype=int)
9783
-
9784
- def pareto_front_aggregate_data(path_to_calculate: str) -> Optional[Dict[Tuple[int, str], Dict[str, Dict[str, float]]]]:
9785
- results_csv_file = f"{path_to_calculate}/{RESULTS_CSV_FILENAME}"
9786
- result_names_file = f"{path_to_calculate}/result_names.txt"
9787
-
9788
- if not os.path.exists(results_csv_file) or not os.path.exists(result_names_file):
9789
- return None
9790
-
9791
- with open(result_names_file, mode="r", encoding="utf-8") as f:
9792
- result_names = [line.strip() for line in f if line.strip()]
9793
-
9794
- records: dict = defaultdict(lambda: {'means': {}})
9795
-
9796
- with open(results_csv_file, encoding="utf-8", mode="r", newline='') as csvfile:
9797
- reader = csv.DictReader(csvfile)
9798
- for row in reader:
9799
- trial_index = int(row['trial_index'])
9800
- arm_name = row['arm_name']
9801
- key = (trial_index, arm_name)
9802
-
9803
- for metric in result_names:
9804
- if metric in row:
9805
- try:
9806
- records[key]['means'][metric] = float(row[metric])
9807
- except ValueError:
9808
- continue
9809
-
9810
- return records
9811
-
9812
- def pareto_front_filter_complete_points(
9813
- path_to_calculate: str,
9814
- records: Dict[Tuple[int, str], Dict[str, Dict[str, float]]],
9815
- primary_name: str,
9816
- secondary_name: str
9817
- ) -> List[Tuple[Tuple[int, str], float, float]]:
9818
- points = []
9819
- for key, metrics in records.items():
9820
- means = metrics['means']
9821
- if primary_name in means and secondary_name in means:
9822
- x_val = means[primary_name]
9823
- y_val = means[secondary_name]
9824
- points.append((key, x_val, y_val))
9825
- if len(points) == 0:
9826
- raise ValueError(f"No full data points with both objectives found in {path_to_calculate}.")
9827
- return points
9828
-
9829
- def pareto_front_transform_objectives(
9830
- points: List[Tuple[Any, float, float]],
9831
- primary_name: str,
9832
- secondary_name: str
9833
- ) -> Tuple[np.ndarray, np.ndarray]:
9834
- primary_idx = arg_result_names.index(primary_name)
9835
- secondary_idx = arg_result_names.index(secondary_name)
9836
-
9837
- x = np.array([p[1] for p in points])
9838
- y = np.array([p[2] for p in points])
9839
-
9840
- if arg_result_min_or_max[primary_idx] == "max":
9841
- x = -x
9842
- elif arg_result_min_or_max[primary_idx] != "min":
9843
- raise ValueError(f"Unknown mode for {primary_name}: {arg_result_min_or_max[primary_idx]}")
9844
-
9845
- if arg_result_min_or_max[secondary_idx] == "max":
9846
- y = -y
9847
- elif arg_result_min_or_max[secondary_idx] != "min":
9848
- raise ValueError(f"Unknown mode for {secondary_name}: {arg_result_min_or_max[secondary_idx]}")
9849
-
9850
- return x, y
9851
-
9852
- def pareto_front_select_pareto_points(
9853
- x: np.ndarray,
9854
- y: np.ndarray,
9855
- x_minimize: bool,
9856
- y_minimize: bool,
9857
- points: List[Tuple[Any, float, float]],
9858
- num_points: int
9859
- ) -> List[Tuple[Any, float, float]]:
9860
- indices = pareto_front_general(x, y, x_minimize, y_minimize)
9861
- sorted_indices = indices[np.argsort(x[indices])]
9862
- sorted_indices = sorted_indices[:num_points]
9863
- selected_points = [points[i] for i in sorted_indices]
9864
- return selected_points
9865
-
9866
- def pareto_front_build_return_structure(
9867
- path_to_calculate: str,
9868
- selected_points: List[Tuple[Any, float, float]],
9869
- records: Dict[Tuple[int, str], Dict[str, Dict[str, float]]],
9870
- absolute_metrics: List[str],
9871
- primary_name: str,
9872
- secondary_name: str
9873
- ) -> dict:
9874
- results_csv_file = f"{path_to_calculate}/{RESULTS_CSV_FILENAME}"
9875
- result_names_file = f"{path_to_calculate}/result_names.txt"
9876
-
9877
- with open(result_names_file, mode="r", encoding="utf-8") as f:
9878
- result_names = [line.strip() for line in f if line.strip()]
9879
-
9880
- csv_rows = {}
9881
- with open(results_csv_file, mode="r", encoding="utf-8", newline='') as csvfile:
9882
- reader = csv.DictReader(csvfile)
9883
- for row in reader:
9884
- trial_index = int(row['trial_index'])
9885
- csv_rows[trial_index] = row
9886
-
9887
- ignored_columns = {'trial_index', 'arm_name', 'trial_status', 'generation_node'}
9888
- ignored_columns.update(result_names)
9889
-
9890
- param_dicts = []
9891
- idxs = []
9892
- means_dict = defaultdict(list)
9906
+ return data
9907
+ except Exception as e:
9908
+ print_red(f"Error while parse_experiment_parameters({_f}): {e}")
9909
+ else:
9910
+ print_red(f"{_f} could not be found")
9893
9911
 
9894
- for (trial_index, arm_name), _, _ in selected_points:
9895
- row = csv_rows.get(trial_index, {})
9896
- if row == {} or row is None or row['arm_name'] != arm_name:
9897
- print_debug(f"pareto_front_build_return_structure: trial_index '{trial_index}' could not be found and row returned as None")
9898
- continue
9912
+ return None
9899
9913
 
9900
- idxs.append(int(row["trial_index"]))
9914
+ def set_orchestrator() -> None:
9915
+ with spinner("Setting orchestrator..."):
9916
+ global orchestrator
9901
9917
 
9902
- param_dict: dict[str, int | float | str] = {}
9903
- for key, value in row.items():
9904
- if key not in ignored_columns:
9905
- try:
9906
- param_dict[key] = int(value)
9907
- except ValueError:
9908
- try:
9909
- param_dict[key] = float(value)
9910
- except ValueError:
9911
- param_dict[key] = value
9918
+ if args.orchestrator_file:
9919
+ if SYSTEM_HAS_SBATCH:
9920
+ orchestrator = parse_orchestrator_file(args.orchestrator_file, False)
9921
+ else:
9922
+ print_yellow("--orchestrator_file will be ignored on non-sbatch-systems.")
9912
9923
 
9913
- param_dicts.append(param_dict)
9924
+ def check_if_has_random_steps() -> None:
9925
+ if (not args.continue_previous_job and "--continue" not in sys.argv) and (args.num_random_steps == 0 or not args.num_random_steps) and args.model not in ["EXTERNAL_GENERATOR", "SOBOL", "PSEUDORANDOM"]:
9926
+ _fatal_error("You have no random steps set. This is only allowed in continued jobs. To start, you need either some random steps, or a continued run.", 233)
9914
9927
 
9915
- for metric in absolute_metrics:
9916
- means_dict[metric].append(records[(trial_index, arm_name)]['means'].get(metric, float("nan")))
9928
+ def add_exclude_to_defective_nodes() -> None:
9929
+ with spinner("Adding excluded nodes..."):
9930
+ if args.exclude:
9931
+ entries = [entry.strip() for entry in args.exclude.split(',')]
9917
9932
 
9918
- ret = {
9919
- primary_name: {
9920
- secondary_name: {
9921
- "absolute_metrics": absolute_metrics,
9922
- "param_dicts": param_dicts,
9923
- "means": dict(means_dict),
9924
- "idxs": idxs
9925
- },
9926
- "absolute_metrics": absolute_metrics
9927
- }
9928
- }
9933
+ for entry in entries:
9934
+ count_defective_nodes(None, entry)
9929
9935
 
9930
- return ret
9936
+ def check_max_eval(_max_eval: int) -> None:
9937
+ with spinner("Checking max_eval..."):
9938
+ if not _max_eval:
9939
+ _fatal_error("--max_eval needs to be set!", 19)
9931
9940
 
9932
- def get_pareto_frontier_points(
9933
- path_to_calculate: str,
9934
- primary_objective: str,
9935
- secondary_objective: str,
9936
- x_minimize: bool,
9937
- y_minimize: bool,
9938
- absolute_metrics: List[str],
9939
- num_points: int
9940
- ) -> Optional[dict]:
9941
- records = pareto_front_aggregate_data(path_to_calculate)
9941
+ def parse_parameters() -> Any:
9942
+ cli_params_experiment_parameters = None
9943
+ if args.parameter:
9944
+ parse_experiment_parameters()
9945
+ cli_params_experiment_parameters = experiment_parameters
9942
9946
 
9943
- if records is None:
9944
- return None
9947
+ return cli_params_experiment_parameters
9945
9948
 
9946
- points = pareto_front_filter_complete_points(path_to_calculate, records, primary_objective, secondary_objective)
9947
- x, y = pareto_front_transform_objectives(points, primary_objective, secondary_objective)
9948
- selected_points = pareto_front_select_pareto_points(x, y, x_minimize, y_minimize, points, num_points)
9949
- result = pareto_front_build_return_structure(path_to_calculate, selected_points, records, absolute_metrics, primary_objective, secondary_objective)
9949
+ def supports_sixel() -> bool:
9950
+ term = os.environ.get("TERM", "").lower()
9951
+ if "xterm" in term or "mlterm" in term:
9952
+ return True
9950
9953
 
9951
- return result
9954
+ try:
9955
+ output = subprocess.run(["tput", "setab", "256"], capture_output=True, text=True, check=True)
9956
+ if output.returncode == 0 and "sixel" in output.stdout.lower():
9957
+ return True
9958
+ except (subprocess.CalledProcessError, FileNotFoundError):
9959
+ pass
9960
+
9961
+ return False
9952
9962
 
9953
9963
  def save_experiment_state() -> None:
9954
9964
  try:
@@ -9963,7 +9973,7 @@ def save_experiment_state() -> None:
9963
9973
  def wait_for_state_file(state_path: str, min_size: int = 5, max_wait_seconds: int = 60) -> bool:
9964
9974
  try:
9965
9975
  if not os.path.exists(state_path):
9966
- print(f"[ERROR] File '{state_path}' does not exist.")
9976
+ print_debug(f"[ERROR] File '{state_path}' does not exist.")
9967
9977
  return False
9968
9978
 
9969
9979
  i = 0
@@ -10182,33 +10192,42 @@ def get_result_minimize_flag(path_to_calculate: str, resname: str) -> bool:
10182
10192
 
10183
10193
  return minmax[index] == "min"
10184
10194
 
10185
- def get_pareto_front_data(path_to_calculate: str, res_names: list) -> dict:
10186
- pareto_front_data: dict = {}
10195
+ def post_job_calculate_pareto_front() -> None:
10196
+ if not args.calculate_pareto_front_of_job:
10197
+ return
10187
10198
 
10188
- all_combinations = list(combinations(range(len(arg_result_names)), 2))
10199
+ failure = False
10189
10200
 
10190
- skip = False
10201
+ _paths_to_calculate = []
10191
10202
 
10192
- for i, j in all_combinations:
10193
- if not skip:
10194
- metric_x = arg_result_names[i]
10195
- metric_y = arg_result_names[j]
10203
+ for _path_to_calculate in list(set(args.calculate_pareto_front_of_job)):
10204
+ try:
10205
+ found_paths = find_results_paths(_path_to_calculate)
10196
10206
 
10197
- x_minimize = get_result_minimize_flag(path_to_calculate, metric_x)
10198
- y_minimize = get_result_minimize_flag(path_to_calculate, metric_y)
10207
+ for _fp in found_paths:
10208
+ if _fp not in _paths_to_calculate:
10209
+ _paths_to_calculate.append(_fp)
10210
+ except (FileNotFoundError, NotADirectoryError) as e:
10211
+ print_red(f"post_job_calculate_pareto_front: find_results_paths('{_path_to_calculate}') failed with {e}")
10199
10212
 
10200
- try:
10201
- if metric_x not in pareto_front_data:
10202
- pareto_front_data[metric_x] = {}
10213
+ failure = True
10203
10214
 
10204
- pareto_front_data[metric_x][metric_y] = get_calculated_frontier(path_to_calculate, metric_x, metric_y, x_minimize, y_minimize, res_names)
10205
- except ax.exceptions.core.DataRequiredError as e:
10206
- print_red(f"Error computing Pareto frontier for {metric_x} and {metric_y}: {e}")
10207
- except SignalINT:
10208
- print_red("Calculating Pareto-fronts was cancelled by pressing CTRL-c")
10209
- skip = True
10215
+ for _path_to_calculate in _paths_to_calculate:
10216
+ for path_to_calculate in found_paths:
10217
+ if not job_calculate_pareto_front(path_to_calculate):
10218
+ failure = True
10210
10219
 
10211
- return pareto_front_data
10220
+ if failure:
10221
+ my_exit(24)
10222
+
10223
+ my_exit(0)
10224
+
10225
+ def pareto_front_as_rich_table(idxs: list, metric_x: str, metric_y: str) -> Optional[Table]:
10226
+ if not os.path.exists(RESULT_CSV_FILE):
10227
+ print_debug(f"pareto_front_as_rich_table: File '{RESULT_CSV_FILE}' not found")
10228
+ return None
10229
+
10230
+ return create_pareto_front_table(idxs, metric_x, metric_y)
10212
10231
 
10213
10232
  def show_pareto_frontier_data(path_to_calculate: str, res_names: list, disable_sixel_and_table: bool = False) -> None:
10214
10233
  if len(res_names) <= 1:
@@ -10552,112 +10571,6 @@ def find_results_paths(base_path: str) -> list:
10552
10571
 
10553
10572
  return list(set(found_paths))
10554
10573
 
10555
- def post_job_calculate_pareto_front() -> None:
10556
- if not args.calculate_pareto_front_of_job:
10557
- return
10558
-
10559
- failure = False
10560
-
10561
- _paths_to_calculate = []
10562
-
10563
- for _path_to_calculate in list(set(args.calculate_pareto_front_of_job)):
10564
- try:
10565
- found_paths = find_results_paths(_path_to_calculate)
10566
-
10567
- for _fp in found_paths:
10568
- if _fp not in _paths_to_calculate:
10569
- _paths_to_calculate.append(_fp)
10570
- except (FileNotFoundError, NotADirectoryError) as e:
10571
- print_red(f"post_job_calculate_pareto_front: find_results_paths('{_path_to_calculate}') failed with {e}")
10572
-
10573
- failure = True
10574
-
10575
- for _path_to_calculate in _paths_to_calculate:
10576
- for path_to_calculate in found_paths:
10577
- if not job_calculate_pareto_front(path_to_calculate):
10578
- failure = True
10579
-
10580
- if failure:
10581
- my_exit(24)
10582
-
10583
- my_exit(0)
10584
-
10585
- def job_calculate_pareto_front(path_to_calculate: str, disable_sixel_and_table: bool = False) -> bool:
10586
- pf_start_time = time.time()
10587
-
10588
- if not path_to_calculate:
10589
- return False
10590
-
10591
- global CURRENT_RUN_FOLDER
10592
- global RESULT_CSV_FILE
10593
- global arg_result_names
10594
-
10595
- if not path_to_calculate:
10596
- print_red("Can only calculate pareto front of previous job when --calculate_pareto_front_of_job is set")
10597
- return False
10598
-
10599
- if not os.path.exists(path_to_calculate):
10600
- print_red(f"Path '{path_to_calculate}' does not exist")
10601
- return False
10602
-
10603
- ax_client_json = f"{path_to_calculate}/state_files/ax_client.experiment.json"
10604
-
10605
- if not os.path.exists(ax_client_json):
10606
- print_red(f"Path '{ax_client_json}' not found")
10607
- return False
10608
-
10609
- checkpoint_file: str = f"{path_to_calculate}/state_files/checkpoint.json"
10610
- if not os.path.exists(checkpoint_file):
10611
- print_red(f"The checkpoint file '{checkpoint_file}' does not exist")
10612
- return False
10613
-
10614
- RESULT_CSV_FILE = f"{path_to_calculate}/{RESULTS_CSV_FILENAME}"
10615
- if not os.path.exists(RESULT_CSV_FILE):
10616
- print_red(f"{RESULT_CSV_FILE} not found")
10617
- return False
10618
-
10619
- res_names = []
10620
-
10621
- res_names_file = f"{path_to_calculate}/result_names.txt"
10622
- if not os.path.exists(res_names_file):
10623
- print_red(f"File '{res_names_file}' does not exist")
10624
- return False
10625
-
10626
- try:
10627
- with open(res_names_file, "r", encoding="utf-8") as file:
10628
- lines = file.readlines()
10629
- except Exception as e:
10630
- print_red(f"Error reading file '{res_names_file}': {e}")
10631
- return False
10632
-
10633
- for line in lines:
10634
- entry = line.strip()
10635
- if entry != "":
10636
- res_names.append(entry)
10637
-
10638
- if len(res_names) < 2:
10639
- print_red(f"Error: There are less than 2 result names (is: {len(res_names)}, {', '.join(res_names)}) in {path_to_calculate}. Cannot continue calculating the pareto front.")
10640
- return False
10641
-
10642
- load_username_to_args(path_to_calculate)
10643
-
10644
- CURRENT_RUN_FOLDER = path_to_calculate
10645
-
10646
- arg_result_names = res_names
10647
-
10648
- load_experiment_parameters_from_checkpoint_file(checkpoint_file, False)
10649
-
10650
- if experiment_parameters is None:
10651
- return False
10652
-
10653
- show_pareto_or_error_msg(path_to_calculate, res_names, disable_sixel_and_table)
10654
-
10655
- pf_end_time = time.time()
10656
-
10657
- print_debug(f"Calculating the Pareto-front took {pf_end_time - pf_start_time} seconds")
10658
-
10659
- return True
10660
-
10661
10574
  def set_arg_states_from_continue() -> None:
10662
10575
  if args.continue_previous_job and not args.num_random_steps:
10663
10576
  num_random_steps_file = f"{args.continue_previous_job}/state_files/num_random_steps"
@@ -10700,27 +10613,27 @@ def run_program_once(params: Optional[dict] = None) -> None:
10700
10613
  params = {}
10701
10614
 
10702
10615
  if isinstance(args.run_program_once, str):
10703
- command_str = args.run_program_once
10616
+ command_str = decode_if_base64(args.run_program_once)
10704
10617
  for k, v in params.items():
10705
10618
  placeholder = f"%({k})"
10706
10619
  command_str = command_str.replace(placeholder, str(v))
10707
10620
 
10708
- with spinner(f"Executing command: [cyan]{command_str}[/cyan]"):
10709
- result = subprocess.run(command_str, shell=True, check=True)
10710
- if result.returncode == 0:
10711
- console.log("[bold green]Setup script completed successfully ✅[/bold green]")
10712
- else:
10713
- console.log(f"[bold red]Setup script failed with exit code {result.returncode} ❌[/bold red]")
10621
+ print(f"Executing command: [cyan]{command_str}[/cyan]")
10622
+ result = subprocess.run(command_str, shell=True, check=True)
10623
+ if result.returncode == 0:
10624
+ print("[bold green]Setup script completed successfully ✅[/bold green]")
10625
+ else:
10626
+ print(f"[bold red]Setup script failed with exit code {result.returncode} ❌[/bold red]")
10714
10627
 
10715
- my_exit(57)
10628
+ my_exit(57)
10716
10629
 
10717
10630
  elif isinstance(args.run_program_once, (list, tuple)):
10718
10631
  with spinner("run_program_once: Executing command list: [cyan]{args.run_program_once}[/cyan]"):
10719
10632
  result = subprocess.run(args.run_program_once, check=True)
10720
10633
  if result.returncode == 0:
10721
- console.log("[bold green]Setup script completed successfully ✅[/bold green]")
10634
+ print("[bold green]Setup script completed successfully ✅[/bold green]")
10722
10635
  else:
10723
- console.log(f"[bold red]Setup script failed with exit code {result.returncode} ❌[/bold red]")
10636
+ print(f"[bold red]Setup script failed with exit code {result.returncode} ❌[/bold red]")
10724
10637
 
10725
10638
  my_exit(57)
10726
10639
 
@@ -10738,6 +10651,12 @@ def show_omniopt_call() -> None:
10738
10651
 
10739
10652
  original_print(oo_call + " " + cleaned)
10740
10653
 
10654
+ if args.dependency is not None and args.dependency != "":
10655
+ print(f"Dependency: {args.dependency}")
10656
+
10657
+ if args.ui_url is not None and args.ui_url != "":
10658
+ print_yellow("--ui_url is deprecated. Do not use it anymore. It will be ignored and one day be removed.")
10659
+
10741
10660
  def main() -> None:
10742
10661
  global RESULT_CSV_FILE, LOGFILE_DEBUG_GET_NEXT_TRIALS
10743
10662
 
@@ -11063,8 +10982,6 @@ def run_search_with_progress_bar() -> None:
11063
10982
  wait_for_jobs_to_complete()
11064
10983
 
11065
10984
  def complex_tests(_program_name: str, wanted_stderr: str, wanted_exit_code: int, wanted_signal: Union[int, None], res_is_none: bool = False) -> int:
11066
- #print_yellow(f"Test suite: {_program_name}")
11067
-
11068
10985
  nr_errors: int = 0
11069
10986
 
11070
10987
  program_path: str = f"./.tests/test_wronggoing_stuff.bin/bin/{_program_name}"