diff --git a/src/ansys/fluent/core/fluent_connection.py b/src/ansys/fluent/core/fluent_connection.py index 297cbea9fdf..a8483d1c071 100644 --- a/src/ansys/fluent/core/fluent_connection.py +++ b/src/ansys/fluent/core/fluent_connection.py @@ -9,7 +9,7 @@ import socket import subprocess import threading -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Callable, List, Optional, Tuple, Union import warnings import weakref @@ -201,6 +201,84 @@ def list_values(self) -> dict: return vars(self) +def _get_ip_and_port( + ip: Optional[str] = None, port: Optional[int] = None +) -> (str, int): + if not ip: + ip = os.getenv("PYFLUENT_FLUENT_IP", "127.0.0.1") + if not port: + port = os.getenv("PYFLUENT_FLUENT_PORT") + if not port: + raise PortNotProvided() + return ip, port + + +def _get_channel(ip: str, port: int): + # Same maximum message length is used in the server + max_message_length = _get_max_c_int_limit() + return grpc.insecure_channel( + f"{ip}:{port}", + options=[ + ("grpc.max_send_message_length", max_message_length), + ("grpc.max_receive_message_length", max_message_length), + ], + ) + + +class _ConnectionInterface: + def __init__(self, create_grpc_service, error_state): + self._scheme_eval_service = create_grpc_service(SchemeEvalService, error_state) + self.scheme_eval = service_creator("scheme_eval").create( + self._scheme_eval_service + ) + + @property + def product_build_info(self) -> str: + """Get Fluent build information.""" + build_time = self.scheme_eval.scheme_eval("(inquire-build-time)") + build_id = self.scheme_eval.scheme_eval("(inquire-build-id)") + rev = self.scheme_eval.scheme_eval("(inquire-src-vcs-id)") + branch = self.scheme_eval.scheme_eval("(inquire-src-vcs-branch)") + return f"Build Time: {build_time} Build Id: {build_id} Revision: {rev} Branch: {branch}" + + def get_cortex_connection_properties(self): + """Get connection properties of Fluent.""" + from grpc._channel import _InactiveRpcError + + try: + logger.info(self.product_build_info) + logger.debug("Obtaining Cortex connection properties...") + fluent_host_pid = self.scheme_eval.scheme_eval("(cx-client-id)") + cortex_host = self.scheme_eval.scheme_eval("(cx-cortex-host)") + cortex_pid = self.scheme_eval.scheme_eval("(cx-cortex-id)") + cortex_pwd = self.scheme_eval.scheme_eval("(cortex-pwd)") + logger.debug("Cortex connection properties successfully obtained.") + except _InactiveRpcError: + logger.warning( + "Fluent Cortex properties unobtainable. 'force exit()' and other" + "methods are not going to work properly. Proceeding..." + ) + fluent_host_pid = None + cortex_host = None + cortex_pid = None + cortex_pwd = None + + return fluent_host_pid, cortex_host, cortex_pid, cortex_pwd + + def is_solver_mode(self): + """Checks if the Fluent session is in solver mode. + + Returns + -------- + ``True`` if the Fluent session is in solver mode, ``False`` otherwise. + """ + return self.scheme_eval.scheme_eval("(cx-solver-mode?)") + + def exit_server(self): + """Exits the server.""" + self.scheme_eval.exec(("(exit-server)",)) + + class FluentConnection: """Encapsulates a Fluent connection. @@ -224,9 +302,8 @@ def __init__( password: Optional[str] = None, channel: Optional[grpc.Channel] = None, cleanup_on_exit: bool = True, - start_transcript: bool = True, remote_instance: Optional[Instance] = None, - launcher_args: Optional[Dict[str, Any]] = None, + slurm_job_id: Optional[str] = None, inside_container: Optional[bool] = None, ): """Initialize a Session. @@ -252,17 +329,15 @@ def __init__( When True, the connected Fluent session will be shut down when PyFluent is exited or exit() is called on the session instance, by default True. - start_transcript : bool, optional - The Fluent transcript is started in the client only when - start_transcript is True. It can be started and stopped - subsequently via method calls on the Session object. remote_instance : ansys.platform.instancemanagement.Instance The corresponding remote instance when Fluent is launched through PyPIM. This instance will be deleted when calling ``Session.exit()``. + slurm_job_id: bool, optional + Job ID of a Fluent session running within a Slurm environment. inside_container: bool, optional Whether the Fluent session that is being connected to - is running inside a docker container. + is running inside a Docker container. Raises ------ @@ -277,22 +352,9 @@ def __init__( if channel is not None: self._channel = channel else: - if not ip: - ip = os.getenv("PYFLUENT_FLUENT_IP", "127.0.0.1") - if not port: - port = os.getenv("PYFLUENT_FLUENT_PORT") + ip, port = _get_ip_and_port(ip, port) + self._channel = _get_channel(ip, port) self._channel_str = f"{ip}:{port}" - if not port: - raise PortNotProvided() - # Same maximum message length is used in the server - max_message_length = _get_max_c_int_limit() - self._channel = grpc.insecure_channel( - f"{ip}:{port}", - options=[ - ("grpc.max_send_message_length", max_message_length), - ("grpc.max_receive_message_length", max_message_length), - ], - ) self._metadata: List[Tuple[str, str]] = ( [("password", password)] if password else [] ) @@ -304,7 +366,7 @@ def __init__( # throws, we should not proceed. self.health_check.check_health() - self._slurm_job_id = launcher_args and launcher_args.get("slurm_job_id") + self._slurm_job_id = slurm_job_id self._id = f"session-{next(FluentConnection._id_iter)}" @@ -312,36 +374,13 @@ def __init__( FluentConnection._monitor_thread = MonitorThread() FluentConnection._monitor_thread.start() - # Move this service later. - # Currently, required by launcher to connect to a running session. - self._scheme_eval_service = self.create_grpc_service( - SchemeEvalService, self._error_state + self._connection_interface = _ConnectionInterface( + self.create_grpc_service, self._error_state ) - self.scheme_eval = service_creator("scheme_eval").create( - self._scheme_eval_service + fluent_host_pid, cortex_host, cortex_pid, cortex_pwd = ( + self._connection_interface.get_cortex_connection_properties() ) - self._cleanup_on_exit = cleanup_on_exit - self.start_transcript = start_transcript - from grpc._channel import _InactiveRpcError - - try: - logger.info(self.fluent_build_info) - logger.debug("Obtaining Cortex connection properties...") - fluent_host_pid = self.scheme_eval.scheme_eval("(cx-client-id)") - cortex_host = self.scheme_eval.scheme_eval("(cx-cortex-host)") - cortex_pid = self.scheme_eval.scheme_eval("(cx-cortex-id)") - cortex_pwd = self.scheme_eval.scheme_eval("(cortex-pwd)") - logger.debug("Cortex connection properties successfully obtained.") - except _InactiveRpcError: - logger.warning( - "Fluent Cortex properties unobtainable, force exit and other" - "methods are not going to work properly, proceeding..." - ) - cortex_host = None - cortex_pid = None - cortex_pwd = None - fluent_host_pid = None if ( (inside_container is None or inside_container is True) @@ -371,7 +410,6 @@ def __init__( ) self._remote_instance = remote_instance - self.launcher_args = launcher_args self._exit_event = threading.Event() @@ -389,22 +427,13 @@ def __init__( FluentConnection._exit, self._channel, self._cleanup_on_exit, - self.scheme_eval, + self._connection_interface, self.finalizer_cbs, self._remote_instance, self._exit_event, ) FluentConnection._monitor_thread.cbs.append(self._finalizer) - @property - def fluent_build_info(self) -> str: - """Get Fluent build info.""" - build_time = self.scheme_eval.scheme_eval("(inquire-build-time)") - build_id = self.scheme_eval.scheme_eval("(inquire-build-id)") - rev = self.scheme_eval.scheme_eval("(inquire-src-vcs-id)") - branch = self.scheme_eval.scheme_eval("(inquire-src-vcs-branch)") - return f"Build Time: {build_time} Build Id: {build_id} Revision: {rev} Branch: {branch}" - def _close_slurm(self): subprocess.run(["scancel", f"{self._slurm_job_id}"]) @@ -665,7 +694,7 @@ def exit( def _exit( channel, cleanup_on_exit, - scheme_eval, + connection_interface, finalizer_cbs, remote_instance, exit_event, @@ -676,7 +705,7 @@ def _exit( cb() if cleanup_on_exit: try: - scheme_eval.exec(("(exit-server)",)) + connection_interface.exit_server() except Exception: pass channel.close() diff --git a/src/ansys/fluent/core/launcher/container_launcher.py b/src/ansys/fluent/core/launcher/container_launcher.py index 8a91088d53e..9ec41012d4c 100644 --- a/src/ansys/fluent/core/launcher/container_launcher.py +++ b/src/ansys/fluent/core/launcher/container_launcher.py @@ -227,11 +227,11 @@ def __call__(self): port=port, password=password, cleanup_on_exit=self.cleanup_on_exit, - start_transcript=self.start_transcript, - launcher_args=self.argvals, + slurm_job_id=self.argvals and self.argvals.get("slurm_job_id"), inside_container=True, ), file_transfer_service=self.file_transfer_service, + start_transcript=self.start_transcript, ) if self.start_watchdog is None and self.cleanup_on_exit: diff --git a/src/ansys/fluent/core/launcher/launcher.py b/src/ansys/fluent/core/launcher/launcher.py index eeeb40b7524..ff022a2b1b3 100644 --- a/src/ansys/fluent/core/launcher/launcher.py +++ b/src/ansys/fluent/core/launcher/launcher.py @@ -359,7 +359,6 @@ def connect_to_fluent( port=port, password=password, cleanup_on_exit=cleanup_on_exit, - start_transcript=start_transcript, ) new_session = _get_running_session_mode(fluent_connection) @@ -374,4 +373,5 @@ def connect_to_fluent( return new_session( fluent_connection=fluent_connection, + start_transcript=start_transcript, ) diff --git a/src/ansys/fluent/core/launcher/pim_launcher.py b/src/ansys/fluent/core/launcher/pim_launcher.py index 50abe6a3346..680f3c03676 100644 --- a/src/ansys/fluent/core/launcher/pim_launcher.py +++ b/src/ansys/fluent/core/launcher/pim_launcher.py @@ -292,8 +292,7 @@ def launch_remote_fluent( channel=channel, cleanup_on_exit=cleanup_on_exit, remote_instance=instance, - start_transcript=start_transcript, - launcher_args=launcher_args, + slurm_job_id=launcher_args and launcher_args.get("slurm_job_id"), ) file_transfer_service = ( @@ -303,5 +302,7 @@ def launch_remote_fluent( ) return session_cls( - fluent_connection=fluent_connection, file_transfer_service=file_transfer_service + fluent_connection=fluent_connection, + file_transfer_service=file_transfer_service, + start_transcript=start_transcript, ) diff --git a/src/ansys/fluent/core/launcher/pyfluent_enums.py b/src/ansys/fluent/core/launcher/pyfluent_enums.py index a7a55727e4d..bb743df6878 100644 --- a/src/ansys/fluent/core/launcher/pyfluent_enums.py +++ b/src/ansys/fluent/core/launcher/pyfluent_enums.py @@ -164,7 +164,7 @@ def _get_running_session_mode( try: session_mode = FluentMode.get_mode( "solver" - if fluent_connection.scheme_eval.scheme_eval("(cx-solver-mode?)") + if fluent_connection._connection_interface.is_solver_mode() else "meshing" ) except Exception as ex: diff --git a/src/ansys/fluent/core/launcher/watchdog_exec b/src/ansys/fluent/core/launcher/watchdog_exec index 539a1448c10..db229c643d3 100644 --- a/src/ansys/fluent/core/launcher/watchdog_exec +++ b/src/ansys/fluent/core/launcher/watchdog_exec @@ -74,8 +74,7 @@ if __name__ == "__main__": "ip": ip, "port": int(port), "password": password, - "launcher_args": None, - "start_transcript": False, + "slurm_job_id": None, "cleanup_on_exit": True, } diff --git a/src/ansys/fluent/core/session.py b/src/ansys/fluent/core/session.py index 5864aa603ef..96ecbf9060d 100644 --- a/src/ansys/fluent/core/session.py +++ b/src/ansys/fluent/core/session.py @@ -3,7 +3,7 @@ import importlib import json import logging -from typing import Any, Optional, Union +from typing import Any, Dict, Optional, Union import warnings from ansys.fluent.core.fluent_connection import FluentConnection @@ -93,13 +93,25 @@ def __init__( self, fluent_connection: FluentConnection, file_transfer_service: Optional[Any] = None, + start_transcript: bool = True, + launcher_args: Optional[Dict[str, Any]] = None, ): """BaseSession. - Args: - fluent_connection (:ref:`ref_fluent_connection`): Encapsulates a Fluent connection. - file_transfer_service: Supports file upload and download. + Parameters + ---------- + fluent_connection (:ref:`ref_fluent_connection`): + Encapsulates a Fluent connection. + file_transfer_service : Optional + Supports file upload and download. + start_transcript : bool, optional + Whether to start the Fluent transcript in the client. + The default is ``True``, in which case the Fluent + transcript can be subsequently started and stopped + using method calls on the ``Session`` object. """ + self._start_transcript = start_transcript + self._launcher_args = launcher_args BaseSession._build_from_fluent_connection( self, fluent_connection, file_transfer_service ) @@ -113,7 +125,7 @@ def _build_from_fluent_connection( self._fluent_connection = fluent_connection self._file_transfer_service = file_transfer_service self._error_state = fluent_connection._error_state - self.scheme_eval = fluent_connection.scheme_eval + self.scheme_eval = fluent_connection._connection_interface.scheme_eval self.rp_vars = RPVars(self.scheme_eval.string_eval) self._preferences = None self.journal = Journal(self.scheme_eval) @@ -122,7 +134,7 @@ def _build_from_fluent_connection( fluent_connection._channel, fluent_connection._metadata ) self.transcript = Transcript(self._transcript_service) - if fluent_connection.start_transcript: + if self._start_transcript: self.transcript.start() self._datamodel_service_tui = service_creator("tui").create( @@ -254,6 +266,8 @@ def _create_from_server_info_file( cls, server_info_file_name: str, file_transfer_service: Optional[Any] = None, + start_transcript: bool = True, + launcher_args: Optional[Dict[str, Any]] = None, **connection_kwargs, ): """Create a Session instance from server-info file. @@ -264,9 +278,14 @@ def _create_from_server_info_file( Path to server-info file written out by Fluent server file_transfer_service : Optional Support file upload and download. + start_transcript : bool, optional + Whether to start the Fluent transcript in the client. + The default is ``True``, in which case the Fluent + transcript can be subsequently started and stopped + using method calls on the ``Session`` object. **connection_kwargs : dict, optional Additional keyword arguments may be specified, and they will be passed to the `FluentConnection` - being initialized. For example, ``cleanup_on_exit = True``, or ``start_transcript = True``. + being initialized. For example, ``cleanup_on_exit = True``. See :func:`FluentConnection initialization ` for more details and possible arguments. @@ -281,6 +300,8 @@ def _create_from_server_info_file( ip=ip, port=port, password=password, **connection_kwargs ), file_transfer_service=file_transfer_service, + start_transcript=start_transcript, + launcher_args=launcher_args, ) return session diff --git a/src/ansys/fluent/core/session_meshing.py b/src/ansys/fluent/core/session_meshing.py index bfdd47547a5..27c36bd823c 100644 --- a/src/ansys/fluent/core/session_meshing.py +++ b/src/ansys/fluent/core/session_meshing.py @@ -1,6 +1,6 @@ """Module containing class encapsulating Fluent connection.""" -from typing import Any, Optional +from typing import Any, Dict, Optional from ansys.fluent.core.fluent_connection import FluentConnection from ansys.fluent.core.session_pure_meshing import PureMeshing @@ -21,6 +21,8 @@ def __init__( self, fluent_connection: FluentConnection, file_transfer_service: Optional[Any] = None, + start_transcript: bool = True, + launcher_args: Optional[Dict[str, Any]] = None, ): """Meshing session. @@ -31,6 +33,8 @@ def __init__( super(Meshing, self).__init__( fluent_connection=fluent_connection, file_transfer_service=file_transfer_service, + start_transcript=start_transcript, + launcher_args=launcher_args, ) self.switch_to_solver = lambda: self._switch_to_solver() self.switched = False diff --git a/src/ansys/fluent/core/session_pure_meshing.py b/src/ansys/fluent/core/session_pure_meshing.py index 58778629df9..8e5e9d7109e 100644 --- a/src/ansys/fluent/core/session_pure_meshing.py +++ b/src/ansys/fluent/core/session_pure_meshing.py @@ -1,7 +1,7 @@ """Module containing class encapsulating Fluent connection.""" import functools -from typing import Any, Optional +from typing import Any, Dict, Optional import ansys.fluent.core as pyfluent from ansys.fluent.core.data_model_cache import DataModelCache, NameKey @@ -31,6 +31,8 @@ def __init__( self, fluent_connection: FluentConnection, file_transfer_service: Optional[Any] = None, + start_transcript: bool = True, + launcher_args: Optional[Dict[str, Any]] = None, ): """PureMeshing session. @@ -41,6 +43,8 @@ def __init__( super(PureMeshing, self).__init__( fluent_connection=fluent_connection, file_transfer_service=file_transfer_service, + start_transcript=start_transcript, + launcher_args=launcher_args, ) self._base_meshing = BaseMeshing( self.execute_tui, diff --git a/src/ansys/fluent/core/session_solver.py b/src/ansys/fluent/core/session_solver.py index 1903f085011..70f99a313da 100644 --- a/src/ansys/fluent/core/session_solver.py +++ b/src/ansys/fluent/core/session_solver.py @@ -5,7 +5,7 @@ import importlib import logging import threading -from typing import Any, Optional +from typing import Any, Dict, Optional import warnings from ansys.fluent.core.services import service_creator @@ -78,6 +78,8 @@ def __init__( self, fluent_connection, file_transfer_service: Optional[Any] = None, + start_transcript: bool = True, + launcher_args: Optional[Dict[str, Any]] = None, ): """Solver session. @@ -88,6 +90,8 @@ def __init__( super(Solver, self).__init__( fluent_connection=fluent_connection, file_transfer_service=file_transfer_service, + start_transcript=start_transcript, + launcher_args=launcher_args, ) self._build_from_fluent_connection(fluent_connection) @@ -206,7 +210,7 @@ def settings(self): flproxy=self._settings_service, version=self._version, file_transfer_service=self._file_transfer_service, - scheme_eval=self._fluent_connection.scheme_eval.scheme_eval, + scheme_eval=self.scheme_eval.scheme_eval, ) return self._settings_root @@ -249,7 +253,7 @@ def read_case_lightweight(self, file_name: str): import ansys.fluent.core as pyfluent self.file.read(file_type="case", file_name=file_name, lightweight_setup=True) - launcher_args = dict(self._fluent_connection.launcher_args) + launcher_args = dict(self._launcher_args) launcher_args.pop("lightweight_mode", None) launcher_args["case_file_name"] = file_name fut: Future = asynchronous(pyfluent.launch_fluent)(**launcher_args) diff --git a/src/ansys/fluent/core/session_solver_icing.py b/src/ansys/fluent/core/session_solver_icing.py index 6d72fe7bb99..4f2a8b95717 100644 --- a/src/ansys/fluent/core/session_solver_icing.py +++ b/src/ansys/fluent/core/session_solver_icing.py @@ -4,7 +4,7 @@ """ import importlib -from typing import Any, Optional +from typing import Any, Dict, Optional from ansys.fluent.core.fluent_connection import FluentConnection from ansys.fluent.core.session_solver import Solver @@ -21,6 +21,8 @@ def __init__( self, fluent_connection: FluentConnection, file_transfer_service: Optional[Any] = None, + start_transcript: bool = True, + launcher_args: Optional[Dict[str, Any]] = None, ): """SolverIcing session. @@ -31,6 +33,8 @@ def __init__( super(SolverIcing, self).__init__( fluent_connection=fluent_connection, file_transfer_service=file_transfer_service, + start_transcript=start_transcript, + launcher_args=launcher_args, ) self._flserver_root = None self._fluent_version = None diff --git a/src/ansys/fluent/core/session_solver_lite.py b/src/ansys/fluent/core/session_solver_lite.py index 5bdf7068ba5..2ca97de7b58 100644 --- a/src/ansys/fluent/core/session_solver_lite.py +++ b/src/ansys/fluent/core/session_solver_lite.py @@ -3,6 +3,8 @@ **********PRESENTLY SAME AS SOLVER WITH A SWITCH TO SOLVER*********** """ +from typing import Any, Dict, Optional + from ansys.fluent.core.session_solver import Solver @@ -14,6 +16,8 @@ class SolverLite(Solver): def __init__( self, fluent_connection=None, + start_transcript: bool = True, + launcher_args: Optional[Dict[str, Any]] = None, ): """SolverLite session. @@ -22,6 +26,8 @@ def __init__( """ super().__init__( fluent_connection=fluent_connection, + start_transcript=start_transcript, + launcher_args=launcher_args, ) self._tui_service = self._datamodel_service_tui self._settings_service = self.settings_service diff --git a/src/ansys/fluent/core/workflow.py b/src/ansys/fluent/core/workflow.py index b69241d4fa6..8bcda944cf3 100644 --- a/src/ansys/fluent/core/workflow.py +++ b/src/ansys/fluent/core/workflow.py @@ -24,9 +24,12 @@ def camel_to_snake_case(camel_case_str: str) -> str: try: return camel_to_snake_case.cache[camel_case_str] except KeyError: - _snake_case_str = re.sub( - "((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))", r"_\1", camel_case_str - ).lower() + if not camel_case_str.islower(): + _snake_case_str = re.sub( + "((?<=[a-z])[A-Z0-9]|(?!^)[A-Z](?=[a-z0-9]))", r"_\1", camel_case_str + ).lower() + else: + _snake_case_str = camel_case_str camel_to_snake_case.cache[camel_case_str] = _snake_case_str return _snake_case_str diff --git a/tests/test_new_meshing_workflow.py b/tests/test_new_meshing_workflow.py index aa8b4c617c0..a9d785ba9e8 100644 --- a/tests/test_new_meshing_workflow.py +++ b/tests/test_new_meshing_workflow.py @@ -4,6 +4,7 @@ import pytest from ansys.fluent.core import examples +from ansys.fluent.core.workflow import camel_to_snake_case from tests.test_datamodel_service import disable_datamodel_cache # noqa: F401 @@ -1230,3 +1231,22 @@ def test_new_meshing_workflow_without_dm_caching( with pytest.raises(RuntimeError): fault_tolerant.import_cad_and_part_management.arguments() assert watertight.import_geometry.arguments() + + +def test_camel_to_snake_case_convertor(): + assert camel_to_snake_case("ImportGeometry") == "import_geometry" + assert camel_to_snake_case("Prism2dPreferences") == "prism_2d_preferences" + assert camel_to_snake_case("Abc2DDef") == "abc_2d_def" + assert camel_to_snake_case("Abc2d") == "abc_2d" + assert camel_to_snake_case("abc2d") == "abc2d" + assert camel_to_snake_case("AbC2d5Cb") == "ab_c2d_5_cb" + assert camel_to_snake_case("abC2d5Cb") == "ab_c2d_5_cb" + assert camel_to_snake_case("abC2d5Cb555klOp") == "ab_c2d_5_cb_555kl_op" + assert camel_to_snake_case("a") == "a" + assert camel_to_snake_case("A") == "a" + assert camel_to_snake_case("a5$c") == "a5$c" + assert camel_to_snake_case("A5$C") == "a5$c" + assert camel_to_snake_case("A5Dc$") == "a5_dc$" + assert camel_to_snake_case("Abc2DDc$") == "abc_2d_dc$" + assert camel_to_snake_case("A2DDc$") == "a2d_dc$" + assert camel_to_snake_case("") == ""