From bf0ba1c60e277a648a0639b1b05cc9ed893af5f8 Mon Sep 17 00:00:00 2001 From: "ANSYS\\spearson" Date: Tue, 31 May 2022 14:03:24 +0100 Subject: [PATCH 1/3] pim --- doc/source/conf.py | 1 + setup.py | 1 + src/ansys/fluent/core/launcher/launcher.py | 65 +++++++++++++++++++++- src/ansys/fluent/core/session.py | 14 +++++ 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index bf695a2c3780..93ed8423dcf8 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -64,6 +64,7 @@ "matplotlib": ("https://matplotlib.org/stable", None), "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None), "pyvista": ("https://docs.pyvista.org/", None), + "pypim": ("https://pypim.docs.pyansys.com/", None), } # SS01, SS02, SS03, GL08 all need clean up in the code to be reactivated. diff --git a/setup.py b/setup.py index 7c2edc413cdc..2d0f0324a292 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ shutil.copy2(_README_FILE, _DOCS_FILE) install_requires = [ + "ansys-platform-instancemanagement~=1.0", "grpcio>=1.30.0", "numpy>=1.21.5", "protobuf==3.20.1", diff --git a/src/ansys/fluent/core/launcher/launcher.py b/src/ansys/fluent/core/launcher/launcher.py index efcfa4d2ee5b..70e3920888f2 100644 --- a/src/ansys/fluent/core/launcher/launcher.py +++ b/src/ansys/fluent/core/launcher/launcher.py @@ -16,10 +16,12 @@ from ansys.fluent.core.launcher.fluent_container import start_fluent_container from ansys.fluent.core.session import Session from ansys.fluent.core.utils.logging import LOG +import ansys.platform.instancemanagement as pypim _THIS_DIR = os.path.dirname(__file__) _OPTIONS_FILE = os.path.join(_THIS_DIR, "fluent_launcher_options.json") FLUENT_VERSION = "22.2" +PIM_FLUENT_PRODUCT_VERSION = FLUENT_VERSION.replace(".", "") def _get_fluent_path(): @@ -113,6 +115,51 @@ def _build_fluent_launch_args_string(**kwargs) -> str: return launch_args_string +def launch_remote_fluent( + product_version: str = None, + cleanup_on_exit: bool = True, + meshing_mode: bool = False, + dimensionality: str = None, +): + + """Start Fluent remotely using the product instance management API. + + When calling this method, you need to ensure that you are in an + environment where PyPIM is configured. This can be verified with :func: + `pypim.is_configured `. + + Parameters + ---------- + version : str, optional + The Fluent version to run, in the 3 digits format, such as "212". + If unspecified, the version will be chosen by the server. + cleanup_on_exit : bool, optional + Exit Fluent when python exits or the Fluent Python instance is + garbage collected. + If unspecified, it will be cleaned up. + + Returns + ------- + ansys.fluent.core.session.Session + An instance of Session. + """ + pim = pypim.connect() + instance = pim.create_instance( + product_name="fluent-meshing" + if 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() + return Session( + channel=channel, cleanup_on_exit=cleanup_on_exit, remote_instance=instance + ) + + # pylint: disable=unused-argument def launch_fluent( version: str = None, @@ -206,7 +253,13 @@ def launch_fluent( """ argvals = locals() if start_instance is None: - start_instance = bool(int(os.getenv("PYFLUENT_START_INSTANCE", "1"))) + start_instance = bool( + int( + os.getenv( + "PYFLUENT_START_INSTANCE", "0" if pypim.is_configured() else "1" + ) + ) + ) if start_instance: exe_path = _get_fluent_exe_path() launch_string = exe_path @@ -248,6 +301,16 @@ def launch_fluent( if server_info_file.exists(): server_info_file.unlink() else: + if pypim.is_configured(): + LOG.info( + "Starting Fluent remotely. The startup configuration will be ignored." + ) + return launch_remote_fluent( + product_version=PIM_FLUENT_PRODUCT_VERSION, + cleanup_on_exit=cleanup_on_exit, + meshing_mode=meshing_mode, + dimensionality=version, + ) import ansys.fluent.core as pyfluent if pyfluent.BUILDING_GALLERY or os.getenv("PYFLUENT_LAUNCH_CONTAINER") == "1": diff --git a/src/ansys/fluent/core/session.py b/src/ansys/fluent/core/session.py index 51a463991c4f..2a881d5bb902 100644 --- a/src/ansys/fluent/core/session.py +++ b/src/ansys/fluent/core/session.py @@ -137,6 +137,7 @@ def __init__( channel: grpc.Channel = None, cleanup_on_exit: bool = True, start_transcript: bool = True, + remote_instance=None, ): """Instantiate a Session. @@ -165,7 +166,12 @@ def __init__( 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()``. """ + self._channel_str = None if channel is not None: self._channel = channel else: @@ -173,6 +179,7 @@ def __init__( ip = os.getenv("PYFLUENT_FLUENT_IP", "127.0.0.1") if not port: port = os.getenv("PYFLUENT_FLUENT_PORT") + self._channel_str = f"{ip}:{port}" if not port: raise ValueError( "The port to connect to Fluent session is not provided." @@ -229,6 +236,10 @@ def __init__( if start_transcript: self.start_transcript() + self._remote_instance = remote_instance + + self._remote_instance = remote_instance + @classmethod def create_from_server_info_file( cls, @@ -320,6 +331,9 @@ def exit(self) -> None: self._channel.close() self._channel = None + if self._remote_instance: + self._remote_instance.delete() + def __enter__(self): """Close the Fluent connection and exit Fluent.""" return self From 4b8167fbf4ad5f51709106088950db6e090c7364 Mon Sep 17 00:00:00 2001 From: "ANSYS\\spearson" Date: Tue, 31 May 2022 14:07:37 +0100 Subject: [PATCH 2/3] pim --- tests/test_launcher_remote.py | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/test_launcher_remote.py diff --git a/tests/test_launcher_remote.py b/tests/test_launcher_remote.py new file mode 100644 index 000000000000..dedac29345a4 --- /dev/null +++ b/tests/test_launcher_remote.py @@ -0,0 +1,58 @@ +"""Test the PyPIM integration.""" +from unittest.mock import create_autospec + +import grpc +from util.solver_workflow import new_solver_session # noqa: F401 + +from ansys.fluent.core.launcher import launcher +import ansys.platform.instancemanagement as pypim + + +def test_launch_remote_instance(monkeypatch, new_solver_session): + fluent = new_solver_session + # Create a mock pypim pretenting it is configured and returning a channel to an already running Fluent + mock_instance = pypim.Instance( + definition_name="definitions/fake-fluent", + name="instances/fake-fluent", + ready=True, + status_message=None, + services={"grpc": pypim.Service(uri=fluent._channel_str, headers={})}, + ) + pim_channel = grpc.insecure_channel( + fluent._channel_str, + ) + mock_instance.wait_for_ready = create_autospec(mock_instance.wait_for_ready) + mock_instance.build_grpc_channel = create_autospec( + mock_instance.build_grpc_channel, return_value=pim_channel + ) + mock_instance.delete = create_autospec(mock_instance.delete) + + mock_client = pypim.Client(channel=grpc.insecure_channel("localhost:12345")) + mock_client.create_instance = create_autospec( + mock_client.create_instance, return_value=mock_instance + ) + + mock_connect = create_autospec(pypim.connect, return_value=mock_client) + mock_is_configured = create_autospec(pypim.is_configured, return_value=True) + monkeypatch.setattr(pypim, "connect", mock_connect) + monkeypatch.setattr(pypim, "is_configured", mock_is_configured) + + # Start fluent with launch_fluent + # Note: This is mocking to start Fluent, but actually reusing the common one + # Thus cleanup_on_exit is set to false + fluent = launcher.launch_fluent(cleanup_on_exit=False) + + # Assert: PyFluent went through the pypim workflow + assert mock_is_configured.called + assert mock_connect.called + mock_client.create_instance.assert_called_with( + "fluent-3ddp", product_version=launcher.PIM_FLUENT_PRODUCT_VERSION + ) + assert mock_instance.wait_for_ready.called + mock_instance.build_grpc_channel.assert_called_with() + + # And it connected using the channel created by PyPIM + assert fluent._channel == pim_channel + + # and it kept track of the instance to be able to delete it + assert fluent._remote_instance == mock_instance From 5a0deb980339e7252aaf800e21d2ef6735e27b04 Mon Sep 17 00:00:00 2001 From: "ANSYS\\spearson" Date: Tue, 31 May 2022 15:32:37 +0100 Subject: [PATCH 3/3] pim --- src/ansys/fluent/core/session.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ansys/fluent/core/session.py b/src/ansys/fluent/core/session.py index 2a881d5bb902..e6d7874c4653 100644 --- a/src/ansys/fluent/core/session.py +++ b/src/ansys/fluent/core/session.py @@ -238,8 +238,6 @@ def __init__( self._remote_instance = remote_instance - self._remote_instance = remote_instance - @classmethod def create_from_server_info_file( cls,