Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/pypim #445

Merged
merged 3 commits into from
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
65 changes: 64 additions & 1 deletion src/ansys/fluent/core/launcher/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(".", "")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to be public?

Suggested change
PIM_FLUENT_PRODUCT_VERSION = FLUENT_VERSION.replace(".", "")
_PIM_FLUENT_PRODUCT_VERSION = FLUENT_VERSION.replace(".", "")

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it need to be public?

The only reason was that it is accessed in the added test. So, it's a moot point perhaps.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah ok, I see it now. If it's on purpose that's fine.



def _get_fluent_path():
Expand Down Expand Up @@ -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 <ansys.platform.instancemanagement.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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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":
Expand Down
12 changes: 12 additions & 0 deletions src/ansys/fluent/core/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -165,14 +166,20 @@ 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:
if not ip:
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."
Expand Down Expand Up @@ -229,6 +236,8 @@ def __init__(
if start_transcript:
self.start_transcript()

self._remote_instance = remote_instance
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason this is there 2x?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is a bad merge, resolving conflicts


@classmethod
def create_from_server_info_file(
cls,
Expand Down Expand Up @@ -320,6 +329,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
Expand Down
58 changes: 58 additions & 0 deletions tests/test_launcher_remote.py
Original file line number Diff line number Diff line change
@@ -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