omniopt2 8911__tar.gz → 8929__tar.gz

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 (41) hide show
  1. {omniopt2-8911 → omniopt2-8929}/.omniopt.py +564 -649
  2. omniopt2-8929/.pareto.py +134 -0
  3. {omniopt2-8911 → omniopt2-8929}/PKG-INFO +1 -1
  4. {omniopt2-8911 → omniopt2-8929}/omniopt2.egg-info/PKG-INFO +1 -1
  5. {omniopt2-8911 → omniopt2-8929}/omniopt2.egg-info/SOURCES.txt +1 -0
  6. {omniopt2-8911 → omniopt2-8929}/pyproject.toml +1 -1
  7. {omniopt2-8911 → omniopt2-8929}/.colorfunctions.sh +0 -0
  8. {omniopt2-8911 → omniopt2-8929}/.dockerignore +0 -0
  9. {omniopt2-8911 → omniopt2-8929}/.general.sh +0 -0
  10. {omniopt2-8911 → omniopt2-8929}/.gitignore +0 -0
  11. {omniopt2-8911 → omniopt2-8929}/.helpers.py +0 -0
  12. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_cpu_ram_usage.py +0 -0
  13. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_general.py +0 -0
  14. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_gpu_usage.py +0 -0
  15. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_kde.py +0 -0
  16. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_scatter.py +0 -0
  17. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_scatter_generation_method.py +0 -0
  18. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_scatter_hex.py +0 -0
  19. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_time_and_exit_code.py +0 -0
  20. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_trial_index_result.py +0 -0
  21. {omniopt2-8911 → omniopt2-8929}/.omniopt_plot_worker.py +0 -0
  22. {omniopt2-8911 → omniopt2-8929}/.random_generator.py +0 -0
  23. {omniopt2-8911 → omniopt2-8929}/.shellscript_functions +0 -0
  24. {omniopt2-8911 → omniopt2-8929}/.tests/pylint.rc +0 -0
  25. {omniopt2-8911 → omniopt2-8929}/.tpe.py +0 -0
  26. {omniopt2-8911 → omniopt2-8929}/LICENSE +0 -0
  27. {omniopt2-8911 → omniopt2-8929}/MANIFEST.in +0 -0
  28. {omniopt2-8911 → omniopt2-8929}/README.md +0 -0
  29. {omniopt2-8911 → omniopt2-8929}/apt-dependencies.txt +0 -0
  30. {omniopt2-8911 → omniopt2-8929}/omniopt +0 -0
  31. {omniopt2-8911 → omniopt2-8929}/omniopt2.egg-info/dependency_links.txt +0 -0
  32. {omniopt2-8911 → omniopt2-8929}/omniopt2.egg-info/requires.txt +0 -0
  33. {omniopt2-8911 → omniopt2-8929}/omniopt2.egg-info/top_level.txt +0 -0
  34. {omniopt2-8911 → omniopt2-8929}/omniopt_docker +0 -0
  35. {omniopt2-8911 → omniopt2-8929}/omniopt_evaluate +0 -0
  36. {omniopt2-8911 → omniopt2-8929}/omniopt_plot +0 -0
  37. {omniopt2-8911 → omniopt2-8929}/omniopt_share +0 -0
  38. {omniopt2-8911 → omniopt2-8929}/requirements.txt +0 -0
  39. {omniopt2-8911 → omniopt2-8929}/setup.cfg +0 -0
  40. {omniopt2-8911 → omniopt2-8929}/setup.py +0 -0
  41. {omniopt2-8911 → omniopt2-8929}/test_requirements.txt +0 -0
@@ -493,6 +493,24 @@ try:
493
493
  dier: FunctionType = helpers.dier
494
494
  is_equal: FunctionType = helpers.is_equal
495
495
  is_not_equal: FunctionType = helpers.is_not_equal
496
+ with spinner("Importing pareto..."):
497
+ pareto_file: str = f"{script_dir}/.pareto.py"
498
+ spec = importlib.util.spec_from_file_location(
499
+ name="pareto",
500
+ location=pareto_file,
501
+ )
502
+ if spec is not None and spec.loader is not None:
503
+ pareto = importlib.util.module_from_spec(spec)
504
+ spec.loader.exec_module(pareto)
505
+ else:
506
+ raise ImportError(f"Could not load module from {pareto_file}")
507
+
508
+ pareto_front_table_filter_rows: FunctionType = pareto.pareto_front_table_filter_rows
509
+ pareto_front_table_add_headers: FunctionType = pareto.pareto_front_table_add_headers
510
+ pareto_front_table_add_rows: FunctionType = pareto.pareto_front_table_add_rows
511
+ pareto_front_filter_complete_points: FunctionType = pareto.pareto_front_filter_complete_points
512
+ pareto_front_select_pareto_points: FunctionType = pareto.pareto_front_select_pareto_points
513
+
496
514
  except KeyboardInterrupt:
497
515
  print("You pressed CTRL-c while importing the helpers file")
498
516
  sys.exit(0)
@@ -546,7 +564,6 @@ def error_without_print(text: str) -> None:
546
564
  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
565
  sys.exit(99)
548
566
 
549
-
550
567
  def print_red(text: str) -> None:
551
568
  helpers.print_color("red", text)
552
569
 
@@ -3353,190 +3370,512 @@ 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]}")
3528
3507
 
3529
- if len(e.stderr):
3530
- print(f"stderr: {e.stderr}")
3531
- else:
3532
- print("No stderr")
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]}")
3533
3512
 
3534
- return [e.stdout, e.stderr, real_exit_code, signal_code]
3513
+ return x, y
3535
3514
 
3536
- def get_results(input_string: Optional[Union[int, str]]) -> Optional[Union[Dict[str, Optional[float]], List[float]]]:
3537
- if input_string is None:
3538
- if not args.tests:
3539
- print_red("get_results: Input-String is None")
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")
3867
+
3868
+ if len(e.stderr):
3869
+ print(f"stderr: {e.stderr}")
3870
+ else:
3871
+ print("No stderr")
3872
+
3873
+ return [e.stdout, e.stderr, real_exit_code, signal_code]
3874
+
3875
+ def get_results(input_string: Optional[Union[int, str]]) -> Optional[Union[Dict[str, Optional[float]], List[float]]]:
3876
+ if input_string is None:
3877
+ if not args.tests:
3878
+ print_red("get_results: Input-String is None")
3540
3879
  return None
3541
3880
 
3542
3881
  if not isinstance(input_string, str):
@@ -5011,21 +5350,6 @@ def abandon_all_jobs() -> None:
5011
5350
  if not abandoned:
5012
5351
  print_debug(f"Job {job} could not be abandoned.")
5013
5352
 
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
5353
  def end_program(_force: Optional[bool] = False, exit_code: Optional[int] = None) -> None:
5030
5354
  global END_PROGRAM_RAN
5031
5355
 
@@ -9576,379 +9900,67 @@ def parse_orchestrator_file(_f: str, _test: bool = False) -> Union[dict, None]:
9576
9900
  print_red(f"{key}-entry is not {expected_type.__name__} but {type(x[key])}")
9577
9901
  die_orchestrator_exit_code_206(_test)
9578
9902
 
9579
- for y in x["match_strings"]:
9580
- if not isinstance(y, str):
9581
- print_red("x['match_strings'] is not a string but {type(x['match_strings'])}")
9582
- die_orchestrator_exit_code_206(_test)
9583
-
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)
9903
+ for y in x["match_strings"]:
9904
+ if not isinstance(y, str):
9905
+ print_red("x['match_strings'] is not a string but {type(x['match_strings'])}")
9906
+ die_orchestrator_exit_code_206(_test)
9889
9907
 
9890
- param_dicts = []
9891
- idxs = []
9892
- means_dict = defaultdict(list)
9908
+ return data
9909
+ except Exception as e:
9910
+ print(f"Error while parse_experiment_parameters({_f}): {e}")
9911
+ else:
9912
+ print_red(f"{_f} could not be found")
9893
9913
 
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
9914
+ return None
9899
9915
 
9900
- idxs.append(int(row["trial_index"]))
9916
+ def set_orchestrator() -> None:
9917
+ with spinner("Setting orchestrator..."):
9918
+ global orchestrator
9901
9919
 
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
9920
+ if args.orchestrator_file:
9921
+ if SYSTEM_HAS_SBATCH:
9922
+ orchestrator = parse_orchestrator_file(args.orchestrator_file, False)
9923
+ else:
9924
+ print_yellow("--orchestrator_file will be ignored on non-sbatch-systems.")
9912
9925
 
9913
- param_dicts.append(param_dict)
9926
+ def check_if_has_random_steps() -> None:
9927
+ 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"]:
9928
+ _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
9929
 
9915
- for metric in absolute_metrics:
9916
- means_dict[metric].append(records[(trial_index, arm_name)]['means'].get(metric, float("nan")))
9930
+ def add_exclude_to_defective_nodes() -> None:
9931
+ with spinner("Adding excluded nodes..."):
9932
+ if args.exclude:
9933
+ entries = [entry.strip() for entry in args.exclude.split(',')]
9917
9934
 
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
- }
9935
+ for entry in entries:
9936
+ count_defective_nodes(None, entry)
9929
9937
 
9930
- return ret
9938
+ def check_max_eval(_max_eval: int) -> None:
9939
+ with spinner("Checking max_eval..."):
9940
+ if not _max_eval:
9941
+ _fatal_error("--max_eval needs to be set!", 19)
9931
9942
 
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)
9943
+ def parse_parameters() -> Any:
9944
+ cli_params_experiment_parameters = None
9945
+ if args.parameter:
9946
+ parse_experiment_parameters()
9947
+ cli_params_experiment_parameters = experiment_parameters
9942
9948
 
9943
- if records is None:
9944
- return None
9949
+ return cli_params_experiment_parameters
9945
9950
 
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)
9951
+ def supports_sixel() -> bool:
9952
+ term = os.environ.get("TERM", "").lower()
9953
+ if "xterm" in term or "mlterm" in term:
9954
+ return True
9950
9955
 
9951
- return result
9956
+ try:
9957
+ output = subprocess.run(["tput", "setab", "256"], capture_output=True, text=True, check=True)
9958
+ if output.returncode == 0 and "sixel" in output.stdout.lower():
9959
+ return True
9960
+ except (subprocess.CalledProcessError, FileNotFoundError):
9961
+ pass
9962
+
9963
+ return False
9952
9964
 
9953
9965
  def save_experiment_state() -> None:
9954
9966
  try:
@@ -10182,33 +10194,42 @@ def get_result_minimize_flag(path_to_calculate: str, resname: str) -> bool:
10182
10194
 
10183
10195
  return minmax[index] == "min"
10184
10196
 
10185
- def get_pareto_front_data(path_to_calculate: str, res_names: list) -> dict:
10186
- pareto_front_data: dict = {}
10197
+ def post_job_calculate_pareto_front() -> None:
10198
+ if not args.calculate_pareto_front_of_job:
10199
+ return
10187
10200
 
10188
- all_combinations = list(combinations(range(len(arg_result_names)), 2))
10201
+ failure = False
10189
10202
 
10190
- skip = False
10203
+ _paths_to_calculate = []
10191
10204
 
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]
10205
+ for _path_to_calculate in list(set(args.calculate_pareto_front_of_job)):
10206
+ try:
10207
+ found_paths = find_results_paths(_path_to_calculate)
10196
10208
 
10197
- x_minimize = get_result_minimize_flag(path_to_calculate, metric_x)
10198
- y_minimize = get_result_minimize_flag(path_to_calculate, metric_y)
10209
+ for _fp in found_paths:
10210
+ if _fp not in _paths_to_calculate:
10211
+ _paths_to_calculate.append(_fp)
10212
+ except (FileNotFoundError, NotADirectoryError) as e:
10213
+ print_red(f"post_job_calculate_pareto_front: find_results_paths('{_path_to_calculate}') failed with {e}")
10199
10214
 
10200
- try:
10201
- if metric_x not in pareto_front_data:
10202
- pareto_front_data[metric_x] = {}
10215
+ failure = True
10203
10216
 
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
10217
+ for _path_to_calculate in _paths_to_calculate:
10218
+ for path_to_calculate in found_paths:
10219
+ if not job_calculate_pareto_front(path_to_calculate):
10220
+ failure = True
10210
10221
 
10211
- return pareto_front_data
10222
+ if failure:
10223
+ my_exit(24)
10224
+
10225
+ my_exit(0)
10226
+
10227
+ def pareto_front_as_rich_table(idxs: list, metric_x: str, metric_y: str) -> Optional[Table]:
10228
+ if not os.path.exists(RESULT_CSV_FILE):
10229
+ print_debug(f"pareto_front_as_rich_table: File '{RESULT_CSV_FILE}' not found")
10230
+ return None
10231
+
10232
+ return create_pareto_front_table(idxs, metric_x, metric_y)
10212
10233
 
10213
10234
  def show_pareto_frontier_data(path_to_calculate: str, res_names: list, disable_sixel_and_table: bool = False) -> None:
10214
10235
  if len(res_names) <= 1:
@@ -10552,112 +10573,6 @@ def find_results_paths(base_path: str) -> list:
10552
10573
 
10553
10574
  return list(set(found_paths))
10554
10575
 
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
10576
  def set_arg_states_from_continue() -> None:
10662
10577
  if args.continue_previous_job and not args.num_random_steps:
10663
10578
  num_random_steps_file = f"{args.continue_previous_job}/state_files/num_random_steps"