diff --git a/src/ansys/fluent/core/__init__.py b/src/ansys/fluent/core/__init__.py index b0e853d019c..797be98e49e 100644 --- a/src/ansys/fluent/core/__init__.py +++ b/src/ansys/fluent/core/__init__.py @@ -20,7 +20,7 @@ connect_to_fluent, launch_fluent, ) -from ansys.fluent.core.launcher.launcher_utils import ( # noqa: F401 +from ansys.fluent.core.launcher.pyfluent_enums import ( # noqa: F401 FluentLinuxGraphicsDriver, FluentMode, FluentUI, diff --git a/src/ansys/fluent/core/launcher/container_launcher.py b/src/ansys/fluent/core/launcher/container_launcher.py index 9c34dcd8c9d..18b6a599401 100644 --- a/src/ansys/fluent/core/launcher/container_launcher.py +++ b/src/ansys/fluent/core/launcher/container_launcher.py @@ -4,7 +4,7 @@ -------- >>> from ansys.fluent.core.launcher.launcher import create_launcher ->>> from ansys.fluent.core.launcher.launcher_utils import LaunchMode +>>> from ansys.fluent.core.launcher.pyfluent_enums import LaunchMode >>> container_meshing_launcher = create_launcher(LaunchMode.CONTAINER, mode="meshing") >>> container_meshing_session = container_meshing_launcher() @@ -18,17 +18,19 @@ from typing import Any, Dict, Optional, Union from ansys.fluent.core.fluent_connection import FluentConnection +from ansys.fluent.core.launcher.error_handler import _process_invalid_args from ansys.fluent.core.launcher.fluent_container import ( configure_container_dict, start_fluent_container, ) -from ansys.fluent.core.launcher.launcher_utils import ( +from ansys.fluent.core.launcher.process_launch_string import ( + _build_fluent_launch_args_string, +) +from ansys.fluent.core.launcher.pyfluent_enums import ( FluentLinuxGraphicsDriver, FluentMode, FluentUI, FluentWindowsGraphicsDriver, - _build_fluent_launch_args_string, - _process_invalid_args, ) import ansys.fluent.core.launcher.watchdog as watchdog from ansys.fluent.core.utils.file_transfer_service import PimFileTransferService diff --git a/src/ansys/fluent/core/launcher/error_handler.py b/src/ansys/fluent/core/launcher/error_handler.py new file mode 100644 index 00000000000..220f4471fb9 --- /dev/null +++ b/src/ansys/fluent/core/launcher/error_handler.py @@ -0,0 +1,126 @@ +from ansys.fluent.core.exceptions import InvalidArgument +from ansys.fluent.core.launcher import launcher_utils +from ansys.fluent.core.launcher.pyfluent_enums import FluentUI, LaunchMode +from ansys.fluent.core.utils.fluent_version import FluentVersion + + +class InvalidPassword(ValueError): + """Raised when password is invalid.""" + + def __init__(self): + super().__init__("Provide correct 'password'.") + + +class GPUSolverSupportError(ValueError): + """Raised when an unsupported Fluent version is specified.""" + + def __init__(self): + super().__init__("Fluent GPU Solver is only supported for 3D.") + + +class IpPortNotProvided(ValueError): + """Raised when IP address and port are not specified.""" + + def __init__(self): + super().__init__("Provide either 'ip' and 'port' or 'server_info_file_name'.") + + +class UnexpectedKeywordArgument(TypeError): + """Raised when a valid keyword argument is not specified.""" + + pass + + +class DockerContainerLaunchNotSupported(SystemError): + """Raised when Docker container launch is not supported.""" + + def __init__(self): + super().__init__("Python Docker SDK is unsupported on this system.") + + +# pylint: disable=missing-raises-doc +class LaunchFluentError(Exception): + """Exception class representing launch errors.""" + + def __init__(self, launch_string): + """__init__ method of LaunchFluentError class.""" + details = "\n" + "Fluent Launch string: " + launch_string + super().__init__(details) + + +def _raise_non_gui_exception_in_windows( + ui: FluentUI, product_version: FluentVersion +) -> None: + """Fluent user interface mode lower than ``FluentUI.HIDDEN_GUI`` is not supported in + Windows in Fluent versions earlier than 2024 R1.""" + if ( + launcher_utils.is_windows() + and ui < FluentUI.HIDDEN_GUI + and product_version < FluentVersion.v241 + ): + raise InvalidArgument( + f"'{ui}' supported in Windows only for Fluent version 24.1 or later." + ) + + +def _process_kwargs(kwargs): + """Verify whether keyword arguments are valid or not. + + Parameters + ---------- + kwargs: Any + Keyword arguments. + + Raises + ------ + UnexpectedKeywordArgument + If an unexpected keyword argument is provided. + """ + if kwargs: + if "meshing_mode" in kwargs: + raise UnexpectedKeywordArgument( + "Use 'launch_fluent(mode='meshing')' to launch Fluent in meshing mode." + ) + else: + raise UnexpectedKeywordArgument( + f"launch_fluent() got an unexpected keyword argument {next(iter(kwargs))}" + ) + + +def _process_invalid_args(dry_run, fluent_launch_mode, argvals): + """Get invalid arguments. + + Parameters + ---------- + dry_run: bool + Whether to dry run a container start. + If ``True``, the ``launch_fluent()`` will return the configured ``container_dict``. + fluent_launch_mode: LaunchMode + Fluent launch mode. + argvals: dict + Local arguments. + """ + if dry_run and fluent_launch_mode != LaunchMode.CONTAINER: + launcher_utils.logger.warning( + "'dry_run' argument for 'launch_fluent' currently is only " + "supported when starting containers." + ) + if fluent_launch_mode != LaunchMode.STANDALONE: + arg_names = [ + "env", + "cwd", + "topy", + "case_file_name", + "lightweight_mode", + "journal_file_names", + "case_data_file_name", + ] + invalid_arg_names = list( + filter(lambda arg_name: argvals[arg_name] is not None, arg_names) + ) + if len(invalid_arg_names) != 0: + invalid_str_names = ", ".join(invalid_arg_names) + launcher_utils.logger.warning( + f"These specified arguments are only supported when starting " + f"local standalone Fluent clients: {invalid_str_names}." + ) diff --git a/src/ansys/fluent/core/launcher/launcher.py b/src/ansys/fluent/core/launcher/launcher.py index 96dfb9b8471..103d6553696 100644 --- a/src/ansys/fluent/core/launcher/launcher.py +++ b/src/ansys/fluent/core/launcher/launcher.py @@ -12,23 +12,27 @@ from ansys.fluent.core.exceptions import DisallowedValuesError from ansys.fluent.core.fluent_connection import FluentConnection from ansys.fluent.core.launcher.container_launcher import DockerLauncher +from ansys.fluent.core.launcher.error_handler import ( + GPUSolverSupportError, + _process_invalid_args, + _process_kwargs, +) from ansys.fluent.core.launcher.launcher_utils import ( + _confirm_watchdog_start, + is_windows, +) +from ansys.fluent.core.launcher.pim_launcher import PIMLauncher +from ansys.fluent.core.launcher.pyfluent_enums import ( FluentLinuxGraphicsDriver, FluentMode, FluentUI, FluentWindowsGraphicsDriver, - GPUSolverSupportError, LaunchMode, - _confirm_watchdog_start, _get_fluent_launch_mode, _get_mode, _get_running_session_mode, - _get_server_info, - _is_windows, - _process_invalid_args, - _process_kwargs, ) -from ansys.fluent.core.launcher.pim_launcher import PIMLauncher +from ansys.fluent.core.launcher.server_info import _get_server_info from ansys.fluent.core.launcher.slurm_launcher import SlurmFuture, SlurmLauncher from ansys.fluent.core.launcher.standalone_launcher import StandaloneLauncher import ansys.fluent.core.launcher.watchdog as watchdog @@ -51,12 +55,10 @@ def create_launcher(fluent_launch_mode: LaunchMode = None, **kwargs): fluent_launch_mode: LaunchMode Supported Fluent launch modes. Options are ``"LaunchMode.CONTAINER"``, ``"LaunchMode.PIM"``, ``"LaunchMode.SLURM"``, and ``"LaunchMode.STANDALONE"``. - Returns ------- launcher: Union[DockerLauncher, PimLauncher, StandaloneLauncher] Session launcher. - Raises ------ DisallowedValuesError @@ -271,7 +273,7 @@ def launch_fluent( if ui is None: # Not using NO_GUI in windows as it opens a new cmd or # shows Fluent output in the current cmd if start is not used - ui = FluentUI.HIDDEN_GUI if _is_windows() else FluentUI.NO_GUI + ui = FluentUI.HIDDEN_GUI if is_windows() else FluentUI.NO_GUI if isinstance(ui, str): ui = FluentUI(ui) if graphics_driver is None: @@ -279,7 +281,7 @@ def launch_fluent( graphics_driver = str(graphics_driver) graphics_driver = ( FluentWindowsGraphicsDriver(graphics_driver) - if _is_windows() + if is_windows() else FluentLinuxGraphicsDriver(graphics_driver) ) fluent_launch_mode = _get_fluent_launch_mode( diff --git a/src/ansys/fluent/core/launcher/launcher_utils.py b/src/ansys/fluent/core/launcher/launcher_utils.py index 676cd404605..64158988700 100644 --- a/src/ansys/fluent/core/launcher/launcher_utils.py +++ b/src/ansys/fluent/core/launcher/launcher_utils.py @@ -1,88 +1,29 @@ """Provides a module for launching utilities.""" -from enum import Enum -from functools import total_ordering -import json import logging import os from pathlib import Path import platform import socket import subprocess -import tempfile import time -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, Union from beartype import BeartypeConf, beartype -from ansys.fluent.core.exceptions import DisallowedValuesError, InvalidArgument -from ansys.fluent.core.fluent_connection import FluentConnection, PortNotProvided -from ansys.fluent.core.scheduler import build_parallel_options, load_machines -from ansys.fluent.core.session import _parse_server_info_file -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 -from ansys.fluent.core.session_solver_icing import SolverIcing -from ansys.fluent.core.utils.file_transfer_service import PimFileTransferService -from ansys.fluent.core.utils.fluent_version import FluentVersion +from ansys.fluent.core.exceptions import InvalidArgument from ansys.fluent.core.utils.networking import find_remoting_ip -import ansys.platform.instancemanagement as pypim -_THIS_DIR = os.path.dirname(__file__) -_OPTIONS_FILE = os.path.join(_THIS_DIR, "fluent_launcher_options.json") logger = logging.getLogger("pyfluent.launcher") -class InvalidPassword(ValueError): - """Raised when password is invalid.""" - - def __init__(self): - super().__init__("Provide correct 'password'.") - - -class GPUSolverSupportError(ValueError): - """Raised when an unsupported Fluent version is specified.""" - - def __init__(self): - super().__init__("Fluent GPU Solver is only supported for 3D.") - - -class IpPortNotProvided(ValueError): - """Raised when ip and port are not specified.""" - - def __init__(self): - super().__init__("Provide either 'ip' and 'port' or 'server_info_file_name'.") - - -class UnexpectedKeywordArgument(TypeError): - """Raised when a valid keyword argument is not specified.""" - - pass - - -class DockerContainerLaunchNotSupported(SystemError): - """Raised when docker container launch is not supported.""" - - def __init__(self): - super().__init__("Python Docker SDK is unsupported on this system.") - - -def _is_windows(): - """Check if the current operating system is windows.""" +def is_windows(): + """Check if the current operating system is Windows.""" return platform.system() == "Windows" -class LaunchMode(Enum): - """An enumeration over supported Fluent launch modes.""" - - STANDALONE = 1 - PIM = 2 - CONTAINER = 3 - SLURM = 4 - - def check_docker_support(): - """Checks whether Python Docker SDK is supported by the current system.""" + """Check whether Python Docker SDK is supported by the current system.""" import docker try: @@ -92,211 +33,13 @@ def check_docker_support(): return True -def _get_standalone_launch_fluent_version( - product_version: Union[FluentVersion, str, None] -) -> Optional[FluentVersion]: - """Determine the Fluent version during the execution of the ``launch_fluent()`` - method in standalone mode. - - The search for the version is performed in this order. - - 1. The ``product_version`` parameter passed with the ``launch_fluent()`` method. - 2. The latest Ansys version from ``AWP_ROOTnnn``` environment variables. - - Returns - ------- - FluentVersion, optional - Fluent version or ``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"): - return None - - # Look for Fluent version in the following order: - # 1. product_version parameter passed with launch_fluent - if product_version: - return FluentVersion(product_version) - - # 2. the latest ANSYS version from AWP_ROOT environment variables - return FluentVersion.get_latest_installed() - - -def get_fluent_exe_path(**launch_argvals) -> Path: - """Get the path for the Fluent executable file. - - The search for the path is performed in this order: - - 1. ``product_version`` parameter passed with ``launch_fluent``. - 2. The latest ANSYS version from ``AWP_ROOTnnn``` environment variables. - - Returns - ------- - Path - Fluent executable path - """ - - def get_fluent_root(version: FluentVersion) -> Path: - awp_root = os.environ[version.awp_var] - return Path(awp_root) / "fluent" - - def get_exe_path(fluent_root: Path) -> Path: - if _is_windows(): - return fluent_root / "ntbin" / "win64" / "fluent.exe" - else: - return fluent_root / "bin" / "fluent" - - # (DEV) "PYFLUENT_FLUENT_ROOT" environment variable - fluent_root = os.getenv("PYFLUENT_FLUENT_ROOT") - if fluent_root: - return get_exe_path(Path(fluent_root)) - - # Look for Fluent exe path in the following order: - # 1. 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))) - - # 2. the latest ANSYS version from AWP_ROOT environment variables - return get_exe_path(get_fluent_root(FluentVersion.get_latest_installed())) - - -class FluentMode(Enum): - """An enumeration over supported Fluent modes.""" - - MESHING_MODE = (Meshing, "meshing") - PURE_MESHING_MODE = (PureMeshing, "pure-meshing") - SOLVER = (Solver, "solver") - SOLVER_ICING = (SolverIcing, "solver-icing") - - @staticmethod - def get_mode(mode: str) -> "FluentMode": - """Returns the FluentMode based on the provided mode string. - - Parameters - ---------- - mode : str - mode - - Returns - ------- - FluentMode - Fluent mode - - Raises - ------ - DisallowedValuesError - If an unknown mode is passed. - """ - allowed_modes = [] - for m in FluentMode: - allowed_modes.append(m.value[1]) - if mode == m.value[1]: - return m - raise DisallowedValuesError("mode", mode, allowed_modes) - - @staticmethod - def is_meshing(mode: "FluentMode") -> bool: - """Returns whether the current mode is meshing. - - Parameters - ---------- - mode : FluentMode - mode - - Returns - ------- - True if mode is FluentMode.MESHING_MODE or FluentMode.PURE_MESHING_MODE else False - bool - """ - return mode in [FluentMode.MESHING_MODE, FluentMode.PURE_MESHING_MODE] - - -@total_ordering -class FluentEnum(Enum): - """Provides the base class for Fluent-related enums. - - Accepts lowercase member names as values and supports comparison operators. - """ - - @classmethod - def _missing_(cls, value: str): - for member in cls: - if str(member) == value: - return member - raise ValueError( - f"The specified value '{value}' is a supported value of {cls.__name__}." - f""" The supported values are: '{", '".join(str(member) for member in cls)}'.""" - ) - - def __str__(self): - return self.name.lower() - - def __lt__(self, other): - if not isinstance(other, type(self)): - raise TypeError( - f"Cannot compare between {type(self).__name__} and {type(other).__name__}" - ) - if self == other: - return False - for member in type(self): - if self == member: - return True - if other == member: - return False - - -class FluentUI(FluentEnum): - """Provides supported user interface mode of Fluent.""" - - NO_GUI_OR_GRAPHICS = ("g",) - NO_GRAPHICS = ("gr",) - NO_GUI = ("gu",) - HIDDEN_GUI = ("hidden",) - GUI = ("",) - - -class FluentWindowsGraphicsDriver(FluentEnum): - """Provides supported graphics driver of Fluent in Windows.""" - - NULL = ("null",) - MSW = ("msw",) - DX11 = ("dx11",) - OPENGL2 = ("opengl2",) - OPENGL = ("opengl",) - AUTO = ("",) - - -class FluentLinuxGraphicsDriver(FluentEnum): - """Provides supported graphics driver of Fluent in Linux.""" - - NULL = ("null",) - X11 = ("x11",) - OPENGL2 = ("opengl2",) - OPENGL = ("opengl",) - AUTO = ("",) - - -def _get_server_info_file_name(use_tmpdir=True): - server_info_dir = os.getenv("SERVER_INFO_DIR") - dir_ = ( - Path(server_info_dir) - if server_info_dir - else tempfile.gettempdir() if use_tmpdir else Path.cwd() - ) - fd, file_name = tempfile.mkstemp(suffix=".txt", prefix="serverinfo-", dir=str(dir_)) - os.close(fd) - return file_name - - def _get_subprocess_kwargs_for_fluent(env: Dict[str, Any], argvals) -> Dict[str, Any]: scheduler_options = argvals.get("scheduler_options") is_slurm = scheduler_options and scheduler_options["scheduler"] == "slurm" kwargs: Dict[str, Any] = {} if is_slurm: kwargs.update(stdout=subprocess.PIPE) - if _is_windows(): + if is_windows(): kwargs.update(shell=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP) else: kwargs.update(shell=True, start_new_session=True) @@ -316,92 +59,6 @@ def _get_subprocess_kwargs_for_fluent(env: Dict[str, Any], argvals) -> Dict[str, return kwargs -def _build_fluent_launch_args_string(**kwargs) -> str: - """Build Fluent's launch arguments string from keyword arguments. - - Returns - ------- - str - Fluent's launch arguments string. - """ - all_options = None - with open(_OPTIONS_FILE, encoding="utf-8") as fp: - all_options = json.load(fp) - launch_args_string = "" - for k, v in all_options.items(): - argval = kwargs.get(k) - default = v.get("default") - if argval is None and v.get("fluent_required") is True: - argval = default - if argval is not None: - allowed_values = v.get("allowed_values") - if allowed_values and argval not in allowed_values: - if default is not None: - old_argval = argval - argval = default - logger.warning( - f"Specified value '{old_argval}' for argument '{k}' is not an allowed value ({allowed_values}), default value '{argval}' is going to be used instead." - ) - else: - logger.warning( - f"{k} = {argval} is discarded as it is not an allowed value. Allowed values: {allowed_values}" - ) - continue - fluent_map = v.get("fluent_map") - if fluent_map: - if isinstance(argval, str): - json_key = argval - else: - json_key = json.dumps(argval) - argval = fluent_map[json_key] - launch_args_string += v["fluent_format"].replace("{}", str(argval)) - addArgs = kwargs.get("additional_arguments") - if addArgs and "-t" not in addArgs and "-cnf=" not in addArgs: - parallel_options = build_parallel_options( - load_machines(ncores=kwargs["processor_count"]) - ) - if parallel_options: - launch_args_string += " " + parallel_options - gpu = kwargs.get("gpu") - if gpu is True: - launch_args_string += " -gpu" - elif isinstance(gpu, list): - launch_args_string += f" -gpu={','.join(map(str, gpu))}" - ui = kwargs.get("ui") - if ui and ui.value[0]: - launch_args_string += f" -{ui.value[0]}" - graphics_driver = kwargs.get("graphics_driver") - if graphics_driver and graphics_driver.value[0]: - launch_args_string += f" -driver {graphics_driver.value[0]}" - return launch_args_string - - -def _get_mode(mode: Optional[Union[FluentMode, str, None]] = None): - """Updates the session information.""" - if mode is None: - mode = FluentMode.SOLVER - - if isinstance(mode, str): - mode = FluentMode.get_mode(mode) - - return mode - - -def _raise_non_gui_exception_in_windows( - ui: FluentUI, product_version: FluentVersion -) -> None: - """Fluent user interface mode lower than ``FluentUI.HIDDEN_GUI`` is not supported in - Windows in Fluent versions lower than 2024 R1.""" - if ( - _is_windows() - and ui < FluentUI.HIDDEN_GUI - and product_version < FluentVersion.v241 - ): - raise InvalidArgument( - f"'{ui}' supported in Windows only for Fluent version 24.1 or later." - ) - - def _await_fluent_launch( server_info_file_name: str, start_timeout: int, sifile_last_mtime: float ): @@ -415,83 +72,11 @@ def _await_fluent_launch( raise TimeoutError("The launch process has timed out.") time.sleep(1) start_timeout -= 1 - logger.info(f"Waiting for Fluent to launch...") + logger.info("Waiting for Fluent to launch...") if start_timeout >= 0: logger.info(f"...{start_timeout} seconds remaining") -def _get_server_info( - server_info_file_name: str, - ip: Optional[str] = None, - port: Optional[int] = None, - password: Optional[str] = None, -): - """Get server connection information of an already running session.""" - if not (ip and port) and not server_info_file_name: - raise IpPortNotProvided() - if (ip or port) and server_info_file_name: - logger.warning( - "The ip and port will be extracted from the server-info file and their explicitly specified values will be ignored." - ) - else: - if server_info_file_name: - ip, port, password = _parse_server_info_file(server_info_file_name) - ip = ip or os.getenv("PYFLUENT_FLUENT_IP", "127.0.0.1") - port = port or os.getenv("PYFLUENT_FLUENT_PORT") - - if not port: - raise PortNotProvided() - - return ip, port, password - - -def _get_running_session_mode( - fluent_connection: FluentConnection, mode: Optional[FluentMode] = None -): - """Get the mode of the running session if the mode has not been mentioned - explicitly.""" - if mode: - session_mode = mode - else: - try: - session_mode = FluentMode.get_mode( - "solver" - if fluent_connection.scheme_eval.scheme_eval("(cx-solver-mode?)") - else "meshing" - ) - except Exception as ex: - raise InvalidPassword() from ex - return session_mode.value[0] - - -def _generate_launch_string( - argvals, - mode: FluentMode, - additional_arguments: str, - server_info_file_name: str, -): - """Generates the launch string to launch fluent.""" - if _is_windows(): - exe_path = str(get_fluent_exe_path(**argvals)) - if " " in exe_path: - exe_path = '"' + exe_path + '"' - else: - exe_path = str(get_fluent_exe_path(**argvals)) - launch_string = exe_path - if mode == FluentMode.SOLVER_ICING: - argvals["fluent_icing"] = True - launch_string += _build_fluent_launch_args_string(**argvals) - if FluentMode.is_meshing(mode): - launch_string += " -meshing" - if additional_arguments: - launch_string += f" {additional_arguments}" - if " " in server_info_file_name: - server_info_file_name = '"' + server_info_file_name + '"' - launch_string += f" -sifile={server_info_file_name}" - launch_string += " -nm" - return launch_string - - def _confirm_watchdog_start(start_watchdog, cleanup_on_exit, fluent_connection): """Confirm whether Fluent is running locally, and whether the Watchdog should be started.""" @@ -527,190 +112,3 @@ def _build_journal_argument( else: fluent_jou_arg += " -topy" return fluent_jou_arg - - -# pylint: disable=missing-raises-doc -class LaunchFluentError(Exception): - """Exception class representing launch errors.""" - - def __init__(self, launch_string): - """__init__ method of LaunchFluentError class.""" - details = "\n" + "Fluent Launch string: " + launch_string - super().__init__(details) - - -def _process_kwargs(kwargs): - """Verify whether keyword arguments are valid or not. - - Parameters - ---------- - kwargs: Any - Provided keyword arguments. - - Raises - ------ - UnexpectedKeywordArgument - If an unexpected keyword argument is provided. - """ - if kwargs: - if "meshing_mode" in kwargs: - raise UnexpectedKeywordArgument( - "Use 'launch_fluent(mode='meshing')' to launch Fluent in meshing mode." - ) - else: - raise UnexpectedKeywordArgument( - f"launch_fluent() got an unexpected keyword argument {next(iter(kwargs))}" - ) - - -def _get_fluent_launch_mode(start_container, container_dict, scheduler_options): - """Get Fluent launch mode. - - Parameters - ---------- - start_container: bool - Specifies whether to launch a Fluent Docker container image. - container_dict: dict - Dictionary for Fluent Docker container configuration. - - Returns - ------- - fluent_launch_mode: LaunchMode - Fluent launch mode. - """ - if pypim.is_configured(): - fluent_launch_mode = LaunchMode.PIM - elif start_container is True or ( - start_container is None - and (container_dict or os.getenv("PYFLUENT_LAUNCH_CONTAINER") == "1") - ): - if check_docker_support(): - fluent_launch_mode = LaunchMode.CONTAINER - else: - raise DockerContainerLaunchNotSupported() - elif scheduler_options and scheduler_options["scheduler"] == "slurm": - fluent_launch_mode = LaunchMode.SLURM - else: - fluent_launch_mode = LaunchMode.STANDALONE - return fluent_launch_mode - - -def _process_invalid_args(dry_run, fluent_launch_mode, argvals): - """Get invalid arguments. - - Parameters - ---------- - dry_run: bool - If dry running a container start, - ``launch_fluent()`` will return the configured ``container_dict``. - fluent_launch_mode: LaunchMode - Fluent launch mode. - argvals: dict - Local arguments. - """ - if dry_run and fluent_launch_mode != LaunchMode.CONTAINER: - logger.warning( - "'dry_run' argument for 'launch_fluent' currently is only " - "supported when starting containers." - ) - if fluent_launch_mode != LaunchMode.STANDALONE: - arg_names = [ - "env", - "cwd", - "topy", - "case_file_name", - "lightweight_mode", - "journal_file_names", - "case_data_file_name", - ] - invalid_arg_names = list( - filter(lambda arg_name: argvals[arg_name] is not None, arg_names) - ) - if len(invalid_arg_names) != 0: - invalid_str_names = ", ".join(invalid_arg_names) - logger.warning( - f"These specified arguments are only supported when starting " - f"local standalone Fluent clients: {invalid_str_names}." - ) - - -def launch_remote_fluent( - session_cls, - start_transcript: bool, - product_version: Optional[str] = None, - cleanup_on_exit: bool = True, - mode: FluentMode = FluentMode.SOLVER, - dimensionality: Optional[str] = None, - launcher_args: Optional[Dict[str, Any]] = None, - file_transfer_service: Optional[Any] = None, -) -> Union[Meshing, PureMeshing, Solver, SolverIcing]: - """Launch Fluent remotely using `PyPIM `. - - When calling this method, you must ensure that you are in an - environment where PyPIM is configured. You can use the :func: - `pypim.is_configured ` - method to verify that PyPIM is configured. - - Parameters - ---------- - session_cls: Union[type(Meshing), type(PureMeshing), type(Solver), type(SolverIcing)] - Session type. - start_transcript: bool - Whether to start streaming the Fluent transcript in the client. The - default is ``True``. You can stop and start the streaming of the - Fluent transcript subsequently via method calls on the session object. - product_version : str, optional - Select an installed version of ANSYS. The string must be in a format like - ``"23.2.0"`` (for 2023 R2) matching the documented version format in the - FluentVersion class. The default is ``None``, in which case the newest installed - version is used. - cleanup_on_exit : bool, optional - Whether to clean up and exit Fluent when Python exits or when garbage - is collected for the Fluent Python instance. The default is ``True``. - mode : FluentMode, optional - Whether to launch Fluent remotely in meshing mode. The default is - ``FluentMode.SOLVER``. - dimensionality : str, optional - Geometric dimensionality of the Fluent simulation. The default is ``None``, - in which case ``"3d"`` is used. Options are ``"3d"`` and ``"2d"``. - file_transfer_service : optional - File transfer service. Uploads/downloads files to/from the server. - - Returns - ------- - :obj:`~typing.Union` [:class:`Meshing`, \ - :class:`~ansys.fluent.core.session_pure_meshing.PureMeshing`, \ - :class:`~ansys.fluent.core.session_solver.Solver`, \ - :class:`~ansys.fluent.core.session_solver_icing.SolverIcing`] - Session object. - """ - pim = pypim.connect() - instance = pim.create_instance( - product_name=( - "fluent-meshing" - if FluentMode.is_meshing(mode) - else "fluent-2ddp" if dimensionality == "2d" else "fluent-3ddp" - ), - product_version=product_version, - ) - instance.wait_for_ready() - # nb pymapdl sets max msg len here: - channel = instance.build_grpc_channel() - - fluent_connection = FluentConnection( - channel=channel, - cleanup_on_exit=cleanup_on_exit, - remote_instance=instance, - start_transcript=start_transcript, - launcher_args=launcher_args, - ) - - file_transfer_service = ( - file_transfer_service - if file_transfer_service - else PimFileTransferService(pim_instance=fluent_connection._remote_instance) - ) - - return session_cls( - fluent_connection=fluent_connection, file_transfer_service=file_transfer_service - ) diff --git a/src/ansys/fluent/core/launcher/pim_launcher.py b/src/ansys/fluent/core/launcher/pim_launcher.py index 2fbddf2acc7..2016a9d48e8 100644 --- a/src/ansys/fluent/core/launcher/pim_launcher.py +++ b/src/ansys/fluent/core/launcher/pim_launcher.py @@ -4,7 +4,7 @@ -------- >>> from ansys.fluent.core.launcher.launcher import create_launcher ->>> from ansys.fluent.core.launcher.launcher_utils import LaunchMode +>>> from ansys.fluent.core.launcher.pyfluent_enums import LaunchMode >>> pim_meshing_launcher = create_launcher(LaunchMode.PIM, mode="meshing") >>> pim_meshing_session = pim_meshing_launcher() @@ -17,15 +17,20 @@ import os from typing import Any, Dict, Optional, Union -from ansys.fluent.core.launcher.launcher_utils import ( +from ansys.fluent.core.fluent_connection import FluentConnection +from ansys.fluent.core.launcher.error_handler import _process_invalid_args +from ansys.fluent.core.launcher.pyfluent_enums import ( FluentLinuxGraphicsDriver, FluentMode, FluentUI, FluentWindowsGraphicsDriver, - _process_invalid_args, - launch_remote_fluent, ) +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 +from ansys.fluent.core.session_solver_icing import SolverIcing from ansys.fluent.core.utils.file_transfer_service import PimFileTransferService +import ansys.platform.instancemanagement as pypim _THIS_DIR = os.path.dirname(__file__) _OPTIONS_FILE = os.path.join(_THIS_DIR, "fluent_launcher_options.json") @@ -218,3 +223,85 @@ def __call__(self): launcher_args=self.argvals, file_transfer_service=self.file_transfer_service, ) + + +def launch_remote_fluent( + session_cls, + start_transcript: bool, + product_version: Optional[str] = None, + cleanup_on_exit: bool = True, + mode: FluentMode = FluentMode.SOLVER, + dimensionality: Optional[str] = None, + launcher_args: Optional[Dict[str, Any]] = None, + file_transfer_service: Optional[Any] = None, +) -> Union[Meshing, PureMeshing, Solver, SolverIcing]: + """Launch Fluent remotely using `PyPIM `. + + When calling this method, you must ensure that you are in an + environment where PyPIM is configured. You can use the :func: + `pypim.is_configured ` + method to verify that PyPIM is configured. + + Parameters + ---------- + session_cls: Union[type(Meshing), type(PureMeshing), type(Solver), type(SolverIcing)] + Session type. + start_transcript: bool + Whether to start streaming the Fluent transcript in the client. The + default is ``True``. You can stop and start the streaming of the + Fluent transcript subsequently via method calls on the session object. + product_version : str, optional + Ansys version to use. The string must be in a format like + ``"23.2.0"`` (for 2023 R2), matching the documented version format in the + FluentVersion class. The default is ``None``, in which case the newest installed + version is used. + cleanup_on_exit : bool, optional + Whether to clean up and exit Fluent when Python exits or when garbage + is collected for the Fluent Python instance. The default is ``True``. + mode : FluentMode, optional + Whether to launch Fluent remotely in meshing mode. The default is + ``FluentMode.SOLVER``. + dimensionality : str, optional + Geometric dimensionality of the Fluent simulation. The default is ``None``, + in which case ``"3d"`` is used. Options are ``"3d"`` and ``"2d"``. + file_transfer_service : optional + File transfer service for uploading or downloading files to or from the server. + + Returns + ------- + :obj:`~typing.Union` [:class:`Meshing`, \ + :class:`~ansys.fluent.core.session_pure_meshing.PureMeshing`, \ + :class:`~ansys.fluent.core.session_solver.Solver`, \ + :class:`~ansys.fluent.core.session_solver_icing.SolverIcing`] + Session object. + """ + pim = pypim.connect() + instance = pim.create_instance( + product_name=( + "fluent-meshing" + if FluentMode.is_meshing(mode) + else "fluent-2ddp" if dimensionality == "2d" else "fluent-3ddp" + ), + product_version=product_version, + ) + instance.wait_for_ready() + # nb pymapdl sets max msg len here: + channel = instance.build_grpc_channel() + + fluent_connection = FluentConnection( + channel=channel, + cleanup_on_exit=cleanup_on_exit, + remote_instance=instance, + start_transcript=start_transcript, + launcher_args=launcher_args, + ) + + file_transfer_service = ( + file_transfer_service + if file_transfer_service + else PimFileTransferService(pim_instance=fluent_connection._remote_instance) + ) + + return session_cls( + fluent_connection=fluent_connection, file_transfer_service=file_transfer_service + ) diff --git a/src/ansys/fluent/core/launcher/process_launch_string.py b/src/ansys/fluent/core/launcher/process_launch_string.py new file mode 100644 index 00000000000..e70761face2 --- /dev/null +++ b/src/ansys/fluent/core/launcher/process_launch_string.py @@ -0,0 +1,138 @@ +import json +import os +from pathlib import Path + +from ansys.fluent.core.launcher import launcher_utils +from ansys.fluent.core.launcher.pyfluent_enums import FluentMode +from ansys.fluent.core.scheduler import build_parallel_options, load_machines +from ansys.fluent.core.utils.fluent_version import FluentVersion + +_THIS_DIR = os.path.dirname(__file__) +_OPTIONS_FILE = os.path.join(_THIS_DIR, "fluent_launcher_options.json") + + +def _build_fluent_launch_args_string(**kwargs) -> str: + """Build Fluent's launch arguments string from keyword arguments. + + Returns + ------- + str + Fluent's launch arguments string. + """ + all_options = None + with open(_OPTIONS_FILE, encoding="utf-8") as fp: + all_options = json.load(fp) + launch_args_string = "" + for k, v in all_options.items(): + argval = kwargs.get(k) + default = v.get("default") + if argval is None and v.get("fluent_required") is True: + argval = default + if argval is not None: + allowed_values = v.get("allowed_values") + if allowed_values and argval not in allowed_values: + if default is not None: + old_argval = argval + argval = default + launcher_utils.logger.warning( + f"Specified value '{old_argval}' for argument '{k}' is not an allowed value ({allowed_values})." + f" Default value '{argval}' is going to be used instead." + ) + else: + launcher_utils.logger.warning( + f"{k} = {argval} is discarded as it is not an allowed value. Allowed values: {allowed_values}" + ) + continue + fluent_map = v.get("fluent_map") + if fluent_map: + if isinstance(argval, str): + json_key = argval + else: + json_key = json.dumps(argval) + argval = fluent_map[json_key] + launch_args_string += v["fluent_format"].replace("{}", str(argval)) + addArgs = kwargs.get("additional_arguments") + if addArgs and "-t" not in addArgs and "-cnf=" not in addArgs: + parallel_options = build_parallel_options( + load_machines(ncores=kwargs["processor_count"]) + ) + if parallel_options: + launch_args_string += " " + parallel_options + gpu = kwargs.get("gpu") + if gpu is True: + launch_args_string += " -gpu" + elif isinstance(gpu, list): + launch_args_string += f" -gpu={','.join(map(str, gpu))}" + ui = kwargs.get("ui") + if ui and ui.value[0]: + launch_args_string += f" -{ui.value[0]}" + graphics_driver = kwargs.get("graphics_driver") + if graphics_driver and graphics_driver.value[0]: + launch_args_string += f" -driver {graphics_driver.value[0]}" + return launch_args_string + + +def _generate_launch_string( + argvals, + mode: FluentMode, + additional_arguments: str, + server_info_file_name: str, +): + """Generates the launch string to launch fluent.""" + if launcher_utils.is_windows(): + exe_path = str(get_fluent_exe_path(**argvals)) + if " " in exe_path: + exe_path = '"' + exe_path + '"' + else: + exe_path = str(get_fluent_exe_path(**argvals)) + launch_string = exe_path + if mode == FluentMode.SOLVER_ICING: + argvals["fluent_icing"] = True + launch_string += _build_fluent_launch_args_string(**argvals) + if FluentMode.is_meshing(mode): + launch_string += " -meshing" + if additional_arguments: + launch_string += f" {additional_arguments}" + if " " in server_info_file_name: + server_info_file_name = '"' + server_info_file_name + '"' + launch_string += f" -sifile={server_info_file_name}" + launch_string += " -nm" + return launch_string + + +def get_fluent_exe_path(**launch_argvals) -> Path: + """Get the path for the Fluent executable file. + The search for the path is performed in this order: + + 1. ``product_version`` parameter passed with the ``launch_fluent`` method. + 2. The latest Ansys version from ``AWP_ROOTnnn``` environment variables. + + Returns + ------- + Path + Fluent executable path + """ + + def get_fluent_root(version: FluentVersion) -> Path: + awp_root = os.environ[version.awp_var] + return Path(awp_root) / "fluent" + + def get_exe_path(fluent_root: Path) -> Path: + if launcher_utils.is_windows(): + return fluent_root / "ntbin" / "win64" / "fluent.exe" + else: + return fluent_root / "bin" / "fluent" + + # (DEV) "PYFLUENT_FLUENT_ROOT" environment variable + fluent_root = os.getenv("PYFLUENT_FLUENT_ROOT") + if fluent_root: + return get_exe_path(Path(fluent_root)) + + # Look for Fluent exe path in the following order: + # 1. 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))) + + # 2. 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 new file mode 100644 index 00000000000..15272a994de --- /dev/null +++ b/src/ansys/fluent/core/launcher/pyfluent_enums.py @@ -0,0 +1,233 @@ +from enum import Enum +from functools import total_ordering +import os +from typing import Optional, Union + +from ansys.fluent.core.exceptions import DisallowedValuesError +from ansys.fluent.core.fluent_connection import FluentConnection +import ansys.fluent.core.launcher.error_handler as exceptions +from ansys.fluent.core.launcher.launcher_utils import check_docker_support +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 +from ansys.fluent.core.session_solver_icing import SolverIcing +from ansys.fluent.core.utils.fluent_version import FluentVersion +import ansys.platform.instancemanagement as pypim + + +class LaunchMode(Enum): + """Enumerates over supported Fluent launch modes.""" + + STANDALONE = 1 + PIM = 2 + CONTAINER = 3 + SLURM = 4 + + +class FluentMode(Enum): + """Enumerates over supported Fluent modes.""" + + MESHING_MODE = (Meshing, "meshing") + PURE_MESHING_MODE = (PureMeshing, "pure-meshing") + SOLVER = (Solver, "solver") + SOLVER_ICING = (SolverIcing, "solver-icing") + + @staticmethod + def get_mode(mode: str) -> "FluentMode": + """Get the FluentMode based on the provided mode string. + + Parameters + ---------- + mode : str + Mode + + Returns + ------- + FluentMode + Fluent mode. + + Raises + ------ + DisallowedValuesError + If an unknown mode is passed. + """ + allowed_modes = [] + for m in FluentMode: + allowed_modes.append(m.value[1]) + if mode == m.value[1]: + return m + raise DisallowedValuesError("mode", mode, allowed_modes) + + @staticmethod + def is_meshing(mode: "FluentMode") -> bool: + """Check if the current mode is meshing. + + Parameters + ---------- + mode : FluentMode + mode + + Returns + ------- + bool + ``True`` if the mode is ``FluentMode.MESHING_MODE`` or ``FluentMode.PURE_MESHING_MODE``, + ``False`` otherwise. + """ + return mode in [FluentMode.MESHING_MODE, FluentMode.PURE_MESHING_MODE] + + +@total_ordering +class FluentEnum(Enum): + """Provides the base class for Fluent-related enums. + + Accepts lowercase member names as values and supports comparison operators. + """ + + @classmethod + def _missing_(cls, value: str): + for member in cls: + if str(member) == value: + return member + raise ValueError( + f"The specified value '{value}' is a supported value of {cls.__name__}." + f""" The supported values are: '{", '".join(str(member) for member in cls)}'.""" + ) + + def __str__(self): + return self.name.lower() + + def __lt__(self, other): + if not isinstance(other, type(self)): + raise TypeError( + f"Cannot compare between {type(self).__name__} and {type(other).__name__}" + ) + if self == other: + return False + for member in type(self): + if self == member: + return True + if other == member: + return False + + +class FluentUI(FluentEnum): + """Provides supported user interface mode of Fluent.""" + + NO_GUI_OR_GRAPHICS = ("g",) + NO_GRAPHICS = ("gr",) + NO_GUI = ("gu",) + HIDDEN_GUI = ("hidden",) + GUI = ("",) + + +class FluentWindowsGraphicsDriver(FluentEnum): + """Provides supported graphics driver of Fluent in Windows.""" + + NULL = ("null",) + MSW = ("msw",) + DX11 = ("dx11",) + OPENGL2 = ("opengl2",) + OPENGL = ("opengl",) + AUTO = ("",) + + +class FluentLinuxGraphicsDriver(FluentEnum): + """Provides supported graphics driver of Fluent in Linux.""" + + NULL = ("null",) + X11 = ("x11",) + OPENGL2 = ("opengl2",) + OPENGL = ("opengl",) + AUTO = ("",) + + +def _get_mode(mode: Optional[Union[FluentMode, str, None]] = None): + """Update the session information.""" + if mode is None: + mode = FluentMode.SOLVER + + if isinstance(mode, str): + mode = FluentMode.get_mode(mode) + + return mode + + +def _get_running_session_mode( + fluent_connection: FluentConnection, mode: Optional[FluentMode] = None +): + """Get the mode of the running session if the mode has not been explicitly given.""" + if mode: + session_mode = mode + else: + try: + session_mode = FluentMode.get_mode( + "solver" + if fluent_connection.scheme_eval.scheme_eval("(cx-solver-mode?)") + else "meshing" + ) + except Exception as ex: + raise exceptions.InvalidPassword() from ex + return session_mode.value[0] + + +def _get_fluent_launch_mode(start_container, container_dict, scheduler_options): + """Get the Fluent launch mode. + + Parameters + ---------- + start_container: bool + Whether to launch a Fluent Docker container image. + container_dict: dict + Dictionary for Fluent Docker container configuration. + + Returns + ------- + fluent_launch_mode: LaunchMode + Fluent launch mode. + """ + if pypim.is_configured(): + fluent_launch_mode = LaunchMode.PIM + elif start_container is True or ( + start_container is None + and (container_dict or os.getenv("PYFLUENT_LAUNCH_CONTAINER") == "1") + ): + if check_docker_support(): + fluent_launch_mode = LaunchMode.CONTAINER + else: + raise exceptions.DockerContainerLaunchNotSupported() + elif scheduler_options and scheduler_options["scheduler"] == "slurm": + fluent_launch_mode = LaunchMode.SLURM + else: + fluent_launch_mode = LaunchMode.STANDALONE + return fluent_launch_mode + + +def _get_standalone_launch_fluent_version( + product_version: Union[FluentVersion, str, None] +) -> Optional[FluentVersion]: + """Determine the Fluent version during the execution of the ``launch_fluent()`` + method in standalone mode. + + The search for the version is performed in this order: + + 1. The ``product_version`` parameter passed with the ``launch_fluent`` method. + 2. The latest Ansys version from ``AWP_ROOTnnn``` environment variables. + + Returns + ------- + FluentVersion, optional + Fluent version or ``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"): + return None + + # Look for Fluent version in the following order: + # 1. product_version parameter passed with launch_fluent + if product_version: + return FluentVersion(product_version) + + # 2. the latest ANSYS version from AWP_ROOT environment variables + return FluentVersion.get_latest_installed() diff --git a/src/ansys/fluent/core/launcher/server_info.py b/src/ansys/fluent/core/launcher/server_info.py new file mode 100644 index 00000000000..3e51f5450ca --- /dev/null +++ b/src/ansys/fluent/core/launcher/server_info.py @@ -0,0 +1,47 @@ +import os +from pathlib import Path +import tempfile +from typing import Optional + +from ansys.fluent.core.fluent_connection import PortNotProvided +from ansys.fluent.core.launcher import launcher_utils +from ansys.fluent.core.launcher.error_handler import IpPortNotProvided +from ansys.fluent.core.session import _parse_server_info_file + + +def _get_server_info_file_name(use_tmpdir=True): + server_info_dir = os.getenv("SERVER_INFO_DIR") + dir_ = ( + Path(server_info_dir) + if server_info_dir + else tempfile.gettempdir() if use_tmpdir else Path.cwd() + ) + fd, file_name = tempfile.mkstemp(suffix=".txt", prefix="serverinfo-", dir=str(dir_)) + os.close(fd) + return file_name + + +def _get_server_info( + server_info_file_name: str, + ip: Optional[str] = None, + port: Optional[int] = None, + password: Optional[str] = None, +): + """Get server connection information of an already running session.""" + if not (ip and port) and not server_info_file_name: + raise IpPortNotProvided() + if (ip or port) and server_info_file_name: + launcher_utils.logger.warning( + "The IP address and port are extracted from the server-info file " + "and their explicitly specified values are ignored." + ) + else: + if server_info_file_name: + ip, port, password = _parse_server_info_file(server_info_file_name) + ip = ip or os.getenv("PYFLUENT_FLUENT_IP", "127.0.0.1") + port = port or os.getenv("PYFLUENT_FLUENT_PORT") + + if not port: + raise PortNotProvided() + + return ip, port, password diff --git a/src/ansys/fluent/core/launcher/slurm_launcher.py b/src/ansys/fluent/core/launcher/slurm_launcher.py index 574e869be72..fcc88d3761d 100644 --- a/src/ansys/fluent/core/launcher/slurm_launcher.py +++ b/src/ansys/fluent/core/launcher/slurm_launcher.py @@ -36,15 +36,15 @@ from typing import Any, Callable, Union from ansys.fluent.core.exceptions import InvalidArgument +from ansys.fluent.core.launcher.error_handler import _process_invalid_args from ansys.fluent.core.launcher.launcher_utils import ( _await_fluent_launch, _build_journal_argument, - _generate_launch_string, - _get_mode, - _get_server_info_file_name, _get_subprocess_kwargs_for_fluent, - _process_invalid_args, ) +from ansys.fluent.core.launcher.process_launch_string import _generate_launch_string +from ansys.fluent.core.launcher.pyfluent_enums import _get_mode +from ansys.fluent.core.launcher.server_info import _get_server_info_file_name 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 diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 2077b3bcf4f..4195b1e144b 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -4,7 +4,7 @@ -------- >>> from ansys.fluent.core.launcher.launcher import create_launcher ->>> from ansys.fluent.core.launcher.launcher_utils import LaunchMode +>>> from ansys.fluent.core.launcher.pyfluent_enums import LaunchMode >>> standalone_meshing_launcher = create_launcher(LaunchMode.STANDALONE, mode="meshing") >>> standalone_meshing_session = standalone_meshing_launcher() @@ -19,23 +19,29 @@ import subprocess from typing import Any, Dict, Optional, Union +from ansys.fluent.core.launcher.error_handler import ( + LaunchFluentError, + _process_invalid_args, + _raise_non_gui_exception_in_windows, +) from ansys.fluent.core.launcher.launcher_utils import ( + _await_fluent_launch, + _build_journal_argument, + _confirm_watchdog_start, + _get_subprocess_kwargs_for_fluent, + is_windows, +) +from ansys.fluent.core.launcher.process_launch_string import _generate_launch_string +from ansys.fluent.core.launcher.pyfluent_enums import ( FluentLinuxGraphicsDriver, FluentMode, FluentUI, FluentWindowsGraphicsDriver, - LaunchFluentError, - _await_fluent_launch, - _build_journal_argument, - _confirm_watchdog_start, - _generate_launch_string, + _get_standalone_launch_fluent_version, +) +from ansys.fluent.core.launcher.server_info import ( _get_server_info, _get_server_info_file_name, - _get_standalone_launch_fluent_version, - _get_subprocess_kwargs_for_fluent, - _is_windows, - _process_invalid_args, - _raise_non_gui_exception_in_windows, ) import ansys.fluent.core.launcher.watchdog as watchdog @@ -223,7 +229,7 @@ def __call__(self): kwargs.update(cwd=self.cwd) launch_string += _build_journal_argument(self.topy, self.journal_file_names) - if _is_windows(): + if is_windows(): # Using 'start.exe' is better, otherwise Fluent is more susceptible to bad termination attempts launch_cmd = 'start "" ' + launch_string else: @@ -243,7 +249,7 @@ def __call__(self): server_info_file_name, self.start_timeout, sifile_last_mtime ) except TimeoutError as ex: - if _is_windows(): + if is_windows(): logger.warning(f"Exception caught - {type(ex).__name__}: {ex}") launch_cmd = launch_string.replace('"', "", 2) kwargs.update(shell=False) diff --git a/tests/test_fluent_session.py b/tests/test_fluent_session.py index fa5b65ce6ab..6143817aca2 100644 --- a/tests/test_fluent_session.py +++ b/tests/test_fluent_session.py @@ -15,7 +15,7 @@ import ansys.fluent.core as pyfluent from ansys.fluent.core.examples import download_file from ansys.fluent.core.fluent_connection import WaitTypeError, get_container -from ansys.fluent.core.launcher.launcher_utils import IpPortNotProvided +from ansys.fluent.core.launcher.error_handler import IpPortNotProvided from ansys.fluent.core.utils.execution import asynchronous, timeout_loop diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 7e6336e62a1..53d5efd1571 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -8,24 +8,30 @@ from ansys.fluent.core import PyFluentDeprecationWarning # noqa: F401 from ansys.fluent.core.exceptions import DisallowedValuesError, InvalidArgument from ansys.fluent.core.launcher import launcher_utils -from ansys.fluent.core.launcher.launcher import create_launcher -from ansys.fluent.core.launcher.launcher_utils import ( +from ansys.fluent.core.launcher.error_handler import ( DockerContainerLaunchNotSupported, - FluentLinuxGraphicsDriver, - FluentMode, - FluentUI, - FluentWindowsGraphicsDriver, GPUSolverSupportError, LaunchFluentError, - LaunchMode, UnexpectedKeywordArgument, - _build_fluent_launch_args_string, - _build_journal_argument, - _is_windows, _raise_non_gui_exception_in_windows, +) +from ansys.fluent.core.launcher.launcher import create_launcher +from ansys.fluent.core.launcher.launcher_utils import ( + _build_journal_argument, check_docker_support, + is_windows, +) +from ansys.fluent.core.launcher.process_launch_string import ( + _build_fluent_launch_args_string, get_fluent_exe_path, ) +from ansys.fluent.core.launcher.pyfluent_enums import ( + FluentLinuxGraphicsDriver, + FluentMode, + FluentUI, + FluentWindowsGraphicsDriver, + LaunchMode, +) from ansys.fluent.core.utils.fluent_version import AnsysVersionNotFound, FluentVersion import ansys.platform.instancemanagement as pypim @@ -67,8 +73,8 @@ def test_unsuccessful_fluent_connection(): @pytest.mark.fluent_version("<24.1") def test_non_gui_in_windows_throws_exception(): - default_windows_flag = launcher_utils._is_windows() - launcher_utils._is_windows = lambda: True + default_windows_flag = launcher_utils.is_windows() + launcher_utils.is_windows = lambda: True try: with pytest.raises(InvalidArgument): _raise_non_gui_exception_in_windows(FluentUI.NO_GUI, FluentVersion.v232) @@ -89,13 +95,13 @@ def test_non_gui_in_windows_throws_exception(): FluentUI.NO_GUI_OR_GRAPHICS, FluentVersion.v222 ) finally: - launcher_utils._is_windows = lambda: default_windows_flag + launcher_utils.is_windows = lambda: default_windows_flag @pytest.mark.fluent_version(">=24.1") def test_non_gui_in_windows_does_not_throw_exception(): - default_windows_flag = launcher_utils._is_windows() - launcher_utils._is_windows = lambda: True + default_windows_flag = launcher_utils.is_windows() + launcher_utils.is_windows = lambda: True try: _raise_non_gui_exception_in_windows(FluentUI.NO_GUI, FluentVersion.v241) _raise_non_gui_exception_in_windows( @@ -106,7 +112,7 @@ def test_non_gui_in_windows_does_not_throw_exception(): FluentUI.NO_GUI_OR_GRAPHICS, FluentVersion.v242 ) finally: - launcher_utils._is_windows = lambda: default_windows_flag + launcher_utils.is_windows = lambda: default_windows_flag def test_container_launcher(): @@ -304,7 +310,7 @@ def test_fluent_launchers(): ui=FluentUI.NO_GUI, graphics_driver=( FluentWindowsGraphicsDriver.AUTO - if _is_windows() + if is_windows() else FluentLinuxGraphicsDriver.AUTO ), ) @@ -391,7 +397,7 @@ def test_exposure_and_graphics_driver_arguments(): with pytest.raises(ValueError): pyfluent.launch_fluent(ui="gu") with pytest.raises(ValueError): - pyfluent.launch_fluent(graphics_driver="x11" if _is_windows() else "dx11") + pyfluent.launch_fluent(graphics_driver="x11" if is_windows() else "dx11") for m in FluentUI: assert ( _build_fluent_launch_args_string(ui=m).strip() == f"3ddp -{m.value[0]}" diff --git a/tests/test_session.py b/tests/test_session.py index c7cea9ea999..c9e399a9334 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -20,7 +20,7 @@ import ansys.fluent.core as pyfluent from ansys.fluent.core import connect_to_fluent, examples, session from ansys.fluent.core.fluent_connection import FluentConnection, PortNotProvided -from ansys.fluent.core.launcher.launcher_utils import LaunchFluentError +from ansys.fluent.core.launcher.error_handler import LaunchFluentError from ansys.fluent.core.session import BaseSession from ansys.fluent.core.utils.execution import timeout_loop from ansys.fluent.core.utils.networking import get_free_port