diff --git a/.ci/stop_fluent.py b/.ci/stop_fluent.py deleted file mode 100644 index 4ec80ff754c6..000000000000 --- a/.ci/stop_fluent.py +++ /dev/null @@ -1,12 +0,0 @@ -import subprocess - - -def stop_fluent_container() -> None: - try: - subprocess.run(["docker", "stop", "fluent_server"]) - except OSError: - pass - - -if __name__ == "__main__": - stop_fluent_container() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc80e9647859..9213d24962f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,7 +137,6 @@ jobs: echo "fluentdocs.pyansys.com" >> doc/_build/html/CNAME env: ANSYSLMD_LICENSE_FILE: ${{ format('1055@{0}', secrets.LICENSE_SERVER) }} - PYFLUENT_FLUENT_PORT: 63084 PYFLUENT_START_INSTANCE: 0 - name: Upload HTML Documentation diff --git a/.github/workflows/nightly-doc-build.yml b/.github/workflows/nightly-doc-build.yml index d7a88752ebd1..c0201329ec7c 100644 --- a/.github/workflows/nightly-doc-build.yml +++ b/.github/workflows/nightly-doc-build.yml @@ -51,7 +51,6 @@ jobs: echo "dev.fluentdocs.pyansys.com" >> doc/_build/html/CNAME env: ANSYSLMD_LICENSE_FILE: ${{ format('1055@{0}', secrets.LICENSE_SERVER) }} - PYFLUENT_FLUENT_PORT: 63084 PYFLUENT_START_INSTANCE: 0 - name: Deploy diff --git a/doc/source/conf.py b/doc/source/conf.py index 54c2124d6c3e..584bffbf5ae3 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,14 +1,15 @@ """Sphinx documentation configuration file.""" from datetime import datetime -import os -import subprocess -import sys from pyansys_sphinx_theme import pyansys_logo_black from sphinx_gallery.sorting import FileNameSortKey +import ansys.fluent.core as pyfluent from ansys.fluent.core import __version__ +# necessary when building the sphinx gallery +pyfluent.BUILDING_GALLERY = True + # -- Project information ----------------------------------------------------- project = "ansys.fluent.core" @@ -97,39 +98,6 @@ templates_path = ["_templates"] -_THIS_DIR = os.path.dirname(__file__) -_START_FLUENT_FILE = os.path.normpath( - os.path.join(_THIS_DIR, "..", "..", ".ci", "start_fluent.py") -) -_STOP_FLUENT_FILE = os.path.normpath( - os.path.join(_THIS_DIR, "..", "..", ".ci", "stop_fluent.py") -) - - -def _start_or_stop_fluent_container(gallery_conf, fname, when): - start_instance = bool(int(os.getenv("PYFLUENT_START_INSTANCE", "1"))) - if not start_instance: - if when == "before": - if fname in [ - "mixing_elbow_settings_api.py", - "mixing_elbow_tui_api.py", - ]: - args = ["3ddp", "-t4", "-meshing"] - elif fname in [ - "exhaust_system_settings_api.py", - "exhaust_system_tui_api.py", - ]: - args = ["3ddp", "-t2", "-meshing"] - elif fname in [ - "parametric_static_mixer_1.py", - "parametric_static_mixer_2.py", - "parametric_static_mixer_3.py", - ]: - args = ["3ddp", "-t4"] - subprocess.run([sys.executable, _START_FLUENT_FILE] + args) - elif when == "after": - subprocess.run([sys.executable, _STOP_FLUENT_FILE]) - # -- Sphinx Gallery Options --------------------------------------------------- sphinx_gallery_conf = { @@ -152,8 +120,6 @@ def _start_or_stop_fluent_container(gallery_conf, fname, when): "image_scrapers": ("pyvista", "matplotlib"), "ignore_pattern": "flycheck*", "thumbnail_size": (350, 350), - "reset_modules_order": "both", - "reset_modules": (_start_or_stop_fluent_container), } diff --git a/src/ansys/fluent/core/__init__.py b/src/ansys/fluent/core/__init__.py index c87087763894..6ac58b2caed0 100644 --- a/src/ansys/fluent/core/__init__.py +++ b/src/ansys/fluent/core/__init__.py @@ -88,3 +88,5 @@ def disable_logging_to_file() -> None: except: pass + +BUILDING_GALLERY = False diff --git a/.ci/start_fluent.py b/src/ansys/fluent/core/launcher/fluent_container.py similarity index 59% rename from .ci/start_fluent.py rename to src/ansys/fluent/core/launcher/fluent_container.py index 284ad250fd97..803694d3e97b 100644 --- a/.ci/start_fluent.py +++ b/src/ansys/fluent/core/launcher/fluent_container.py @@ -1,35 +1,57 @@ import os +import socket import subprocess -import sys import tempfile import time from typing import List -from ansys.fluent.core import EXAMPLES_PATH +def _get_free_port() -> int: + sock = socket.socket() + sock.bind(("", 0)) + return sock.getsockname()[1] -def start_fluent_container(args: List[str]) -> None: + +def start_fluent_container( + mounted_from: str, mounted_to: str, args: List[str] +) -> int: + """Start a Fluent container. + + Parameters + ---------- + mounted_from : str + Path to mount from. ``mounted_from`` is mounted as ``mount_to`` + within the container. + mounted_to : str + Path to mount to. ``mounted_from`` is mounted as ``mount_to`` + within the container. + args : List[str] + List of Fluent launch arguments + + Returns + ------- + int + gPRC server port exposed from container + """ fd, sifile = tempfile.mkstemp( - suffix=".txt", prefix="serverinfo-", dir=EXAMPLES_PATH + suffix=".txt", prefix="serverinfo-", dir=mounted_from ) os.close(fd) timeout = 100 license_server = os.environ["ANSYSLMD_LICENSE_FILE"] - port = os.environ["PYFLUENT_FLUENT_PORT"] + port = _get_free_port() try: subprocess.run( [ "docker", "run", - "--name", - "fluent_server", "-d", "--rm", "-p", f"{port}:{port}", "-v", - f"{EXAMPLES_PATH}:{EXAMPLES_PATH}", + f"{mounted_from}:{mounted_to}", "-e", f"ANSYSLMD_LICENSE_FILE={license_server}", "-e", @@ -52,12 +74,9 @@ def start_fluent_container(args: List[str]) -> None: break time.sleep(1) timeout -= 1 + return port except OSError: pass finally: if os.path.exists(sifile): os.remove(sifile) - - -if __name__ == "__main__": - start_fluent_container(sys.argv[1:]) diff --git a/src/ansys/fluent/core/launcher/launcher.py b/src/ansys/fluent/core/launcher/launcher.py index c586094ce804..acf56300020c 100644 --- a/src/ansys/fluent/core/launcher/launcher.py +++ b/src/ansys/fluent/core/launcher/launcher.py @@ -13,6 +13,7 @@ import time from typing import Any, Dict +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 @@ -67,6 +68,56 @@ def _get_subprocess_kwargs_for_fluent(env: Dict[str, Any]) -> Dict[str, Any]: 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("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 + LOG.warning( + "Default value %s is chosen for %s as the passed " + "value %s is outside allowed values %s.", + argval, + k, + old_argval, + allowed_values, + ) + else: + LOG.warning( + "%s = %s is discarded as it is outside " + "allowed values %s.", + k, + argval, + 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)) + return launch_args_string + + # pylint: disable=unused-argument def launch_fluent( version: str = None, @@ -148,45 +199,7 @@ def launch_fluent( if start_instance: exe_path = _get_fluent_exe_path() launch_string = exe_path - all_options = None - with open(_OPTIONS_FILE, encoding="utf-8") as fp: - all_options = json.load(fp) - for k, v in all_options.items(): - argval = argvals.get(k) - default = v.get("default") - if argval is None and v.get("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 - LOG.warning( - "Default value %s is chosen for %s as the passed " - "value %s is outside allowed values %s.", - argval, - k, - old_argval, - allowed_values, - ) - else: - LOG.warning( - "%s = %s is discarded as it is outside " - "allowed values %s.", - k, - argval, - 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_string += v["fluent_format"].replace("{}", str(argval)) + launch_string += _build_fluent_launch_args_string(**argvals) server_info_filepath = _get_server_info_filepath() try: launch_string += f" {additional_arguments}" @@ -225,6 +238,18 @@ def launch_fluent( if server_info_file.exists(): server_info_file.unlink() else: - ip = argvals.get("ip", None) - port = argvals.get("port", None) - return Session(ip=ip, port=port, cleanup_on_exit=cleanup_on_exit) + import ansys.fluent.core as pyfluent + + if pyfluent.BUILDING_GALLERY: + args = _build_fluent_launch_args_string(**argvals).split() + # Assumes the container OS will be able to create the + # EXAMPLES_PATH of host OS. With the Fluent docker + # container, the following currently works only in linux. + port = start_fluent_container( + pyfluent.EXAMPLES_PATH, pyfluent.EXAMPLES_PATH, args + ) + return Session(port=port, cleanup_on_exit=cleanup_on_exit) + else: + ip = argvals.get("ip", None) + port = argvals.get("port", None) + return Session(ip=ip, port=port, cleanup_on_exit=cleanup_on_exit)