From c652f219afa0ba018e400dbee1428ffeb53cf48a Mon Sep 17 00:00:00 2001 From: shudson Date: Thu, 5 Feb 2026 20:42:05 -0600 Subject: [PATCH 1/2] Provide relative workdir --- libensemble/executors/executor.py | 21 +++++++++++++++++++++ libensemble/executors/mpi_executor.py | 5 ++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/libensemble/executors/executor.py b/libensemble/executors/executor.py index e8a23b22c..81e2cd834 100644 --- a/libensemble/executors/executor.py +++ b/libensemble/executors/executor.py @@ -30,6 +30,9 @@ # To change logging level for just this module # logger.setLevel(logging.DEBUG) +# Placeholder for container support - replaced with simulation directory at runtime +LIBE_SIM_DIR_PLACEHOLDER = "%LIBENSEMBLE_SIM_DIR%" + STATES = """ UNKNOWN CREATED @@ -431,6 +434,7 @@ def __init__(self) -> None: self.workerID = None self.comm = None self.last_task = 0 + self.base_dir = os.getcwd() Executor.executor = self def __enter__(self): @@ -522,6 +526,10 @@ def register_app( precedent: str, Optional Any str that should directly precede the application full path. + Supports the placeholder ``%LIBENSEMBLE_SIM_DIR%`` which is replaced + at runtime with the simulation directory as a relative path from + where the executor was created. This is useful for container exec + commands. """ if not app_name: @@ -684,6 +692,16 @@ def _check_app_exists(self, app: Application) -> None: if not os.path.isfile(app.full_path): raise ExecutorException(f"Application does not exist {app.full_path}") + def _set_sim_dir_env(self, task: Task, run_cmd: list[str]) -> list[str]: + """Replace simulation directory placeholder in run command if present. + + Supports container-based execution where the simulation directory needs to be + passed to container exec commands (e.g., podman-hpc exec --workdir). + """ + sim_dir = os.path.relpath(task.workdir, self.base_dir) + task._add_to_env("LIBENSEMBLE_SIM_DIR", sim_dir) + return [arg.replace(LIBE_SIM_DIR_PLACEHOLDER, sim_dir) for arg in run_cmd] + def submit( self, calc_type: str | None = None, @@ -757,6 +775,9 @@ def submit( if task.app_args is not None: runline.extend(task.app_args.split()) + runline = self._set_sim_dir_env(task, runline) + task.runline = " ".join(runline) + if dry_run: logger.info(f"Test (No submit) Runline: {' '.join(runline)}") else: diff --git a/libensemble/executors/mpi_executor.py b/libensemble/executors/mpi_executor.py index 114144291..454775374 100644 --- a/libensemble/executors/mpi_executor.py +++ b/libensemble/executors/mpi_executor.py @@ -71,8 +71,6 @@ class MPIExecutor(Executor): from libensemble.executors.mpi_executor import MPIExecutor exctr = MPIExecutor(custom_info=customizer) - - """ def __init__(self, custom_info: dict = {}) -> None: @@ -363,7 +361,8 @@ def submit( if task.app_args is not None: runline.extend(task.app_args.split()) - task.runline = " ".join(runline) # Allow to be queried + runline = self._set_sim_dir_env(task, runline) + task.runline = " ".join(runline) if env_script is not None: run_cmd = Executor._process_env_script(task, runline, env_script) From 1de8c05e46bd82bfcee866845af94c367b3e9e93 Mon Sep 17 00:00:00 2001 From: shudson Date: Fri, 6 Feb 2026 15:36:44 -0600 Subject: [PATCH 2/2] Allow additional env vars in GPU setting checks --- .../tests/unit_tests/test_executor_gpus.py | 4 +++- libensemble/tools/test_support.py | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/libensemble/tests/unit_tests/test_executor_gpus.py b/libensemble/tests/unit_tests/test_executor_gpus.py index 1eba92110..239344ecd 100644 --- a/libensemble/tests/unit_tests/test_executor_gpus.py +++ b/libensemble/tests/unit_tests/test_executor_gpus.py @@ -118,7 +118,9 @@ def run_check(exp_env, exp_cmd, **kwargs): args_for_sim = "sleep 0" exp_runline = exp_cmd + " simdir/my_simtask.x sleep 0" task = exctr.submit(calc_type="sim", app_args=args_for_sim, dry_run=True, **kwargs) - assert task.env == exp_env, f"Task env does not match expected:\n Received: {task.env}\n Expected: {exp_env}" + for key, value in exp_env.items(): + assert key in task.env, f"Expected env key '{key}' not found in task.env: {task.env}" + assert task.env[key] == value, f"Env key '{key}' has value '{task.env[key]}', expected '{value}'" assert ( task.runline == exp_runline ), f"Run line does not match expected.\n Received: {task.runline}\n Expected: {exp_runline}" diff --git a/libensemble/tools/test_support.py b/libensemble/tools/test_support.py index 9f62b1e12..bad9c2a78 100644 --- a/libensemble/tools/test_support.py +++ b/libensemble/tools/test_support.py @@ -242,6 +242,16 @@ def check_gpu_setting(task, assert_setting=True, print_setting=False, resources= print(f"Worker {task.workerID}: {desc}GPU setting ({stype}): {gpu_setting} {addon}", flush=True) if assert_setting: - assert ( - gpu_setting == expected - ), f"Worker {task.workerID}: Found GPU setting: {gpu_setting}, Expected: {expected}" + if isinstance(expected, dict): + for key, value in expected.items(): + assert key in gpu_setting, ( + f"Worker {task.workerID}: Expected env key '{key}' not found in GPU setting: {gpu_setting}" + ) + assert gpu_setting[key] == value, ( + f"Worker {task.workerID}: GPU setting key '{key}' has value '{gpu_setting[key]}', " + f"expected '{value}'" + ) + else: + assert ( + gpu_setting == expected + ), f"Worker {task.workerID}: Found GPU setting: {gpu_setting}, Expected: {expected}"