diff --git a/src/ansys/fluent/core/launcher/launcher.py b/src/ansys/fluent/core/launcher/launcher.py index 7e1be2ab755..2b5f830635b 100644 --- a/src/ansys/fluent/core/launcher/launcher.py +++ b/src/ansys/fluent/core/launcher/launcher.py @@ -168,8 +168,9 @@ def launch_fluent( See also :mod:`~ansys.fluent.core.launcher.fluent_container`. dry_run : bool, optional Defaults to False. If True, will not launch Fluent, and will instead print configuration information - that would be used as if Fluent was being launched. If dry running a container start, - ``launch_fluent()`` will return the configured ``container_dict``. + that would be used as if Fluent was being launched. If dry running a standalone start + ``launch_fluent()`` will return a tuple containing Fluent launch string and the server info file name. + If dry running a container start, ``launch_fluent()`` will return the configured ``container_dict``. cleanup_on_exit : bool, optional Whether to shut down the connected Fluent session when PyFluent is exited, or the ``exit()`` method is called on the session instance, diff --git a/src/ansys/fluent/core/launcher/process_launch_string.py b/src/ansys/fluent/core/launcher/process_launch_string.py index 91dcc1744aa..2b1ed4cfad8 100644 --- a/src/ansys/fluent/core/launcher/process_launch_string.py +++ b/src/ansys/fluent/core/launcher/process_launch_string.py @@ -137,7 +137,14 @@ def get_exe_path(fluent_root: Path) -> Path: return fluent_root / "bin" / "fluent" # Look for Fluent exe path in the following order: - # 1. product_version parameter passed with launch_fluent + # 1. Custom Path provided by the user in launch_fluent + fluent_path = launch_argvals.get("fluent_path") + if fluent_path: + # Return the fluent_path string verbatim. The path may not even exist + # in the current machine if user wants to launch fluent externally (dry_run use case). + return fluent_path + + # 2. product_version parameter passed with launch_fluent product_version = launch_argvals.get("product_version") if product_version: return get_exe_path(get_fluent_root(FluentVersion(product_version))) @@ -147,10 +154,5 @@ def get_exe_path(fluent_root: Path) -> Path: if fluent_root: return get_exe_path(Path(fluent_root)) - # 2. Custom Path provided by the user in launch_fluent - fluent_path = launch_argvals.get("fluent_path") - if fluent_path: - return Path(fluent_path) - # 3. the latest ANSYS version from AWP_ROOT environment variables return get_exe_path(get_fluent_root(FluentVersion.get_latest_installed())) diff --git a/src/ansys/fluent/core/launcher/pyfluent_enums.py b/src/ansys/fluent/core/launcher/pyfluent_enums.py index cfff8b63c2b..278b055061e 100644 --- a/src/ansys/fluent/core/launcher/pyfluent_enums.py +++ b/src/ansys/fluent/core/launcher/pyfluent_enums.py @@ -276,9 +276,7 @@ def _get_running_session_mode( return session_mode.get_fluent_value() -def _get_standalone_launch_fluent_version( - product_version: FluentVersion | str | float | int | None, -) -> FluentVersion | None: +def _get_standalone_launch_fluent_version(argvals) -> FluentVersion | None: """Determine the Fluent version during the execution of the ``launch_fluent()`` method in standalone mode. @@ -295,9 +293,14 @@ def _get_standalone_launch_fluent_version( # Look for Fluent version in the following order: # 1. product_version parameter passed with launch_fluent + product_version = argvals.get("product_version") if product_version: return FluentVersion(product_version) + # If fluent_path is provided, we cannot determine the Fluent version, so returning None. + if argvals.get("fluent_path"): + return None + # (DEV) if "PYFLUENT_FLUENT_ROOT" environment variable is defined, we cannot # determine the Fluent version, so returning None. if os.getenv("PYFLUENT_FLUENT_ROOT"): diff --git a/src/ansys/fluent/core/launcher/server_info.py b/src/ansys/fluent/core/launcher/server_info.py index 662d76e8d8c..44c3f5c3081 100644 --- a/src/ansys/fluent/core/launcher/server_info.py +++ b/src/ansys/fluent/core/launcher/server_info.py @@ -10,7 +10,27 @@ from ansys.fluent.core.session import _parse_server_info_file -def _get_server_info_file_name(use_tmpdir=True): +def _get_server_info_file_names(use_tmpdir=True) -> tuple[str, str]: + """Returns a tuple containing server and client-side file names with the server connection information. + When server and client are in a different machine, the environment variable SERVER_INFO_DIR + can be set to a shared directory between the two machines and the server-info file will be + created in that directory. The value of the environment variable SERVER_INFO_DIR can be + different for the server and client machines. The relative path of the server-side server-info + file is passed to Fluent launcher and PyFluent connects to the server using the absolute path + of the client-side server-info file. A typical use case of the environment variable + SERVER_INFO_DIR is as follows: + - Server machine environment variable: SERVER_INFO_DIR=/mnt/shared + - Client machine environment variable: SERVER_INFO_DIR=\\\\server\\shared + - Server-side server-info file: /mnt/shared/serverinfo-xyz.txt + - Client-side server-info file: \\\\server\\shared\\serverinfo-xyz.txt + - Fluent launcher command: fluent ... -sifile=serverinfo-xyz.txt ... + - From PyFluent: connect_to_fluent(server_info_file_name="\\\\server\\shared\\serverinfo-xyz.txt") + + When the environment variable SERVER_INFO_DIR is not set, the server-side and client-side + file paths for the server-info file are identical. The server-info file is created in the + temporary directory if ``use_tmpdir`` is True, otherwise it is created in the current working + directory. + """ server_info_dir = os.getenv("SERVER_INFO_DIR") dir_ = ( Path(server_info_dir) @@ -19,7 +39,10 @@ def _get_server_info_file_name(use_tmpdir=True): ) fd, file_name = tempfile.mkstemp(suffix=".txt", prefix="serverinfo-", dir=str(dir_)) os.close(fd) - return file_name + if server_info_dir: + return Path(file_name).name, file_name + else: + return file_name, file_name def _get_server_info( diff --git a/src/ansys/fluent/core/launcher/slurm_launcher.py b/src/ansys/fluent/core/launcher/slurm_launcher.py index 9323fc51040..eb0397ba0f4 100644 --- a/src/ansys/fluent/core/launcher/slurm_launcher.py +++ b/src/ansys/fluent/core/launcher/slurm_launcher.py @@ -64,7 +64,7 @@ UIMode, _get_argvals_and_session, ) -from ansys.fluent.core.launcher.server_info import _get_server_info_file_name +from ansys.fluent.core.launcher.server_info import _get_server_info_file_names from ansys.fluent.core.session_meshing import Meshing from ansys.fluent.core.session_pure_meshing import PureMeshing from ansys.fluent.core.session_solver import Solver @@ -416,11 +416,14 @@ def __init__( ) def _prepare(self): - self._server_info_file_name = _get_server_info_file_name(use_tmpdir=False) + server_info_file_name_for_server, server_info_file_name_for_client = ( + _get_server_info_file_names(use_tmpdir=False) + ) + self._server_info_file_name = server_info_file_name_for_client self._argvals.update(self._argvals["scheduler_options"]) launch_cmd = _generate_launch_string( self._argvals, - self._server_info_file_name, + server_info_file_name_for_server, ) self._sifile_last_mtime = Path(self._server_info_file_name).stat().st_mtime diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 4455b35d71d..edf5bb062e6 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -43,7 +43,7 @@ ) from ansys.fluent.core.launcher.server_info import ( _get_server_info, - _get_server_info_file_name, + _get_server_info_file_names, ) import ansys.fluent.core.launcher.watchdog as watchdog from ansys.fluent.core.utils.fluent_version import FluentVersion @@ -128,8 +128,9 @@ def __init__( exited, or the ``exit()`` method is called on the session instance, or if the session instance becomes unreferenced. The default is ``True``. dry_run : bool, optional - Defaults to False. If True, will not launch Fluent, and will instead print configuration information - that would be used as if Fluent was being launched. + Defaults to False. If True, will not launch Fluent, and will print configuration information + that would be used as if Fluent was being launched. If True, the ``call()`` method will return + a tuple containing Fluent launch string and the server info file name. start_transcript : bool, optional Whether to start streaming the Fluent transcript in the client. The default is ``True``. You can stop and start the streaming of the @@ -188,9 +189,7 @@ def __init__( self.argvals["start_timeout"] = 60 if self.argvals["lightweight_mode"] is None: self.argvals["lightweight_mode"] = False - fluent_version = _get_standalone_launch_fluent_version( - self.argvals["product_version"] - ) + fluent_version = _get_standalone_launch_fluent_version(self.argvals) if fluent_version: _raise_non_gui_exception_in_windows(self.argvals["ui_mode"], fluent_version) @@ -200,10 +199,13 @@ def __init__( if os.getenv("PYFLUENT_FLUENT_DEBUG") == "1": self.argvals["fluent_debug"] = True - self._server_info_file_name = _get_server_info_file_name() + server_info_file_name_for_server, server_info_file_name_for_client = ( + _get_server_info_file_names() + ) + self._server_info_file_name = server_info_file_name_for_client self._launch_string = _generate_launch_string( self.argvals, - self._server_info_file_name, + server_info_file_name_for_server, ) self._sifile_last_mtime = Path(self._server_info_file_name).stat().st_mtime @@ -232,7 +234,7 @@ def __init__( def __call__(self): if self.argvals["dry_run"]: print(f"Fluent launch string: {self._launch_string}") - return + return self._launch_string, self._server_info_file_name try: logger.debug(f"Launching Fluent with command: {self._launch_cmd}") diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 90be7b30019..31e34c774cb 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -1,6 +1,7 @@ import os from pathlib import Path import platform +import tempfile from tempfile import TemporaryDirectory import pytest @@ -498,3 +499,31 @@ def test_fluent_automatic_transcript(monkeypatch): with TemporaryDirectory(dir=pyfluent.EXAMPLES_PATH) as tmp_dir: with pyfluent.launch_fluent(container_dict=dict(working_dir=tmp_dir)): assert not list(Path(tmp_dir).glob("*.trn")) + + +def test_standalone_launcher_dry_run(monkeypatch): + monkeypatch.setenv("PYFLUENT_LAUNCH_CONTAINER", "0") + fluent_path = r"\x\y\z\fluent.exe" + fluent_launch_string, server_info_file_name = pyfluent.launch_fluent( + fluent_path=fluent_path, dry_run=True, ui_mode="no_gui" + ) + assert str(Path(server_info_file_name).parent) == tempfile.gettempdir() + assert ( + fluent_launch_string + == f"{fluent_path} 3ddp -gu -sifile={server_info_file_name} -nm" + ) + + +def test_standalone_launcher_dry_run_with_server_info_dir(monkeypatch): + monkeypatch.setenv("PYFLUENT_LAUNCH_CONTAINER", "0") + with tempfile.TemporaryDirectory() as tmp_dir: + monkeypatch.setenv("SERVER_INFO_DIR", tmp_dir) + fluent_path = r"\x\y\z\fluent.exe" + fluent_launch_string, server_info_file_name = pyfluent.launch_fluent( + fluent_path=fluent_path, dry_run=True, ui_mode="no_gui" + ) + assert str(Path(server_info_file_name).parent) == tmp_dir + assert ( + fluent_launch_string + == f"{fluent_path} 3ddp -gu -sifile={Path(server_info_file_name).name} -nm" + )