From 8322fb7c92137e0550f758bfa91b6c237293f8ec Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Tue, 26 Apr 2022 13:09:20 +0530 Subject: [PATCH] Start Fluent container within launch_fluent while building gallery in GitHub CI --- .ci/stop_fluent.py | 12 - .github/workflows/ci.yml | 1 - .github/workflows/nightly-doc-build.yml | 1 - doc/source/conf.py | 43 +-- .../post_processing_exhaust_manifold.py | 262 +++++++++--------- src/ansys/fluent/core/__init__.py | 2 + .../fluent/core/launcher/fluent_container.py | 43 ++- src/ansys/fluent/core/launcher/launcher.py | 108 +++++--- 8 files changed, 242 insertions(+), 230 deletions(-) delete mode 100644 .ci/stop_fluent.py rename .ci/start_fluent.py => src/ansys/fluent/core/launcher/fluent_container.py (57%) 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 2a65949982e4..5c72fc5e5c2d 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 089039b357cf..5ab860127c05 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 145ed7c486db..fcf7dbb74cec 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -2,13 +2,13 @@ from datetime import datetime import os import subprocess -import sys import numpy as np from pyansys_sphinx_theme import pyansys_logo_black import pyvista from sphinx_gallery.sorting import FileNameSortKey +import ansys.fluent.core as pyfluent from ansys.fluent.core import __version__ # Manage errors @@ -27,6 +27,7 @@ # necessary when building the sphinx gallery pyvista.BUILDING_GALLERY = True +pyfluent.BUILDING_GALLERY = True # -- Project information ----------------------------------------------------- @@ -118,32 +119,16 @@ copybutton_prompt_is_regexp = True -_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.py", "exhaust_system.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", "-t2"] - elif fname in ["post_processing_exhaust_manifold.py"]: - args = ["3ddp", "-t4"] - subprocess.run([sys.executable, _START_FLUENT_FILE] + args) - elif when == "after": - subprocess.run([sys.executable, _STOP_FLUENT_FILE]) +def _stop_fluent_container(gallery_conf, fname): + try: + container_name = ( + subprocess.check_output("docker container ls --latest --format {{.Names}}") + .decode("utf-8") + .strip() + ) + subprocess.run(f"docker stop {container_name}") + except: + pass # -- Sphinx Gallery Options --------------------------------------------------- @@ -167,8 +152,8 @@ 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), + "reset_modules_order": "after", + "reset_modules": (_stop_fluent_container), } diff --git a/examples/02-postprocessing/post_processing_exhaust_manifold.py b/examples/02-postprocessing/post_processing_exhaust_manifold.py index b2b7ca0f69dd..47954b9b4216 100644 --- a/examples/02-postprocessing/post_processing_exhaust_manifold.py +++ b/examples/02-postprocessing/post_processing_exhaust_manifold.py @@ -16,134 +16,134 @@ - Plot quantitative results using Matplotlib """ ############################################################################### -import ansys.fluent.core as pyfluent -from ansys.fluent.core import examples -from ansys.fluent.post import set_config -from ansys.fluent.post.matplotlib import Plots -from ansys.fluent.post.pyvista import Graphics - -set_config(blocking=True, set_view_on_display="isometric") - -############################################################################### -# First, download the case and data file and start Fluent as a service with -# Meshing mode, double precision, number of processors: 4 - -import_case = examples.download_file( - filename="manifold_solution.cas.h5", directory="pyfluent/exhaust_manifold" -) - -import_data = examples.download_file( - filename="manifold_solution.dat.h5", directory="pyfluent/exhaust_manifold" -) - -session = pyfluent.launch_fluent(precision="double", processor_count=2) -root = session.get_settings_root() - -session.tui.solver.file.read_case(case_file_name=import_case) -session.tui.solver.file.read_data(case_file_name=import_data) - -############################################################################### -# Get the graphics object for mesh display - -graphics = Graphics(session=session) - -############################################################################### -# Create a graphics object for mesh display - -mesh1 = graphics.Meshes["mesh-1"] - -############################################################################### -# Show edges and faces - -mesh1.show_edges = True -mesh1.show_faces = True - -############################################################################### -# Get the surfaces list - -mesh1.surfaces_list = [ - "in1", - "in2", - "in3", - "out1", - "solid_up:1", - "solid_up:1:830", - "solid_up:1:830-shadow", -] -mesh1.display("window-1") - -############################################################################### -# Disable edges and display again - -mesh1.show_edges = False -mesh1.display("window-2") - -############################################################################### -# Create iso-surface on the outlet plane - -surf_outlet_plane = graphics.Surfaces["outlet-plane"] -surf_outlet_plane.surface.type = "iso-surface" -iso_surf1 = surf_outlet_plane.surface.iso_surface -iso_surf1.field = "y-coordinate" -iso_surf1.iso_value = -0.125017 -surf_outlet_plane.display("window-3") - -############################################################################### -# Create iso-surface on the mid-plane (Issue # 276) - -surf_mid_plane_x = graphics.Surfaces["mid-plane-x"] -surf_mid_plane_x.surface.type = "iso-surface" -iso_surf2 = surf_mid_plane_x.surface.iso_surface -iso_surf2.field = "x-coordinate" -iso_surf2.iso_value = -0.174 -surf_mid_plane_x.display("window-4") - -############################################################################### -# Temperature contour on the mid-plane and the outlet - -temperature_contour = graphics.Contours["contour-temperature"] -temperature_contour.field = "temperature" -temperature_contour.surfaces_list = ["mid-plane-x", "outlet-plane"] -temperature_contour.display("window-4") - -############################################################################### -# Contour plot of temperature on the manifold - -temperature_contour_manifold = graphics.Contours["contour-temperature-manifold"] -temperature_contour_manifold.field = "temperature" -temperature_contour_manifold.surfaces_list = [ - "in1", - "in2", - "in3", - "out1", - "solid_up:1", - "solid_up:1:830", -] -temperature_contour_manifold.display("window-5") - -############################################################################### -# Vector on the mid-plane -# Currently using outlet-plane since mid-plane is affected by Issue # 276 - -velocity_vector = graphics.Vectors["velocity-vector"] -velocity_vector.surfaces_list = ["outlet-plane"] -velocity_vector.scale = 1 -velocity_vector.display("window-6") - -############################################################################### -# Commenting out due to issue #290 -# Start the Plot Object for the session -plots_session_1 = Plots(session) - -############################################################################### -# Create a default XY-Plot -plot_1 = plots_session_1.XYPlots["plot-1"] - -############################################################################### -# Set the surface on which the plot is plotted and the Y-axis function -plot_1.surfaces_list = ["outlet"] -plot_1.y_axis_function = "temperature" - -############################################################################### -# Plot the created XY-Plot -plot_1.plot("window-7") +# import ansys.fluent.core as pyfluent +# from ansys.fluent.core import examples +# from ansys.fluent.post import set_config +# from ansys.fluent.post.matplotlib import Plots +# from ansys.fluent.post.pyvista import Graphics + +# set_config(blocking=True, set_view_on_display="isometric") + +# ############################################################################### +# # First, download the case and data file and start Fluent as a service with +# # Meshing mode, double precision, number of processors: 4 + +# import_case = examples.download_file( +# filename="manifold_solution.cas.h5", directory="pyfluent/exhaust_manifold" +# ) + +# import_data = examples.download_file( +# filename="manifold_solution.dat.h5", directory="pyfluent/exhaust_manifold" +# ) + +# session = pyfluent.launch_fluent(precision="double", processor_count=2) +# root = session.get_settings_root() + +# session.tui.solver.file.read_case(case_file_name=import_case) +# session.tui.solver.file.read_data(case_file_name=import_data) + +# ############################################################################### +# # Get the graphics object for mesh display + +# graphics = Graphics(session=session) + +# ############################################################################### +# # Create a graphics object for mesh display + +# mesh1 = graphics.Meshes["mesh-1"] + +# ############################################################################### +# # Show edges and faces + +# mesh1.show_edges = True +# mesh1.show_faces = True + +# ############################################################################### +# # Get the surfaces list + +# mesh1.surfaces_list = [ +# "in1", +# "in2", +# "in3", +# "out1", +# "solid_up:1", +# "solid_up:1:830", +# "solid_up:1:830-shadow", +# ] +# mesh1.display("window-1") + +# ############################################################################### +# # Disable edges and display again + +# mesh1.show_edges = False +# mesh1.display("window-2") + +# ############################################################################### +# # Create iso-surface on the outlet plane + +# surf_outlet_plane = graphics.Surfaces["outlet-plane"] +# surf_outlet_plane.surface.type = "iso-surface" +# iso_surf1 = surf_outlet_plane.surface.iso_surface +# iso_surf1.field = "y-coordinate" +# iso_surf1.iso_value = -0.125017 +# surf_outlet_plane.display("window-3") + +# ############################################################################### +# # Create iso-surface on the mid-plane (Issue # 276) + +# surf_mid_plane_x = graphics.Surfaces["mid-plane-x"] +# surf_mid_plane_x.surface.type = "iso-surface" +# iso_surf2 = surf_mid_plane_x.surface.iso_surface +# iso_surf2.field = "x-coordinate" +# iso_surf2.iso_value = -0.174 +# surf_mid_plane_x.display("window-4") + +# ############################################################################### +# # Temperature contour on the mid-plane and the outlet + +# temperature_contour = graphics.Contours["contour-temperature"] +# temperature_contour.field = "temperature" +# temperature_contour.surfaces_list = ["mid-plane-x", "outlet-plane"] +# temperature_contour.display("window-4") + +# ############################################################################### +# # Contour plot of temperature on the manifold + +# temperature_contour_manifold = graphics.Contours["contour-temperature-manifold"] +# temperature_contour_manifold.field = "temperature" +# temperature_contour_manifold.surfaces_list = [ +# "in1", +# "in2", +# "in3", +# "out1", +# "solid_up:1", +# "solid_up:1:830", +# ] +# temperature_contour_manifold.display("window-5") + +# ############################################################################### +# # Vector on the mid-plane +# # Currently using outlet-plane since mid-plane is affected by Issue # 276 + +# velocity_vector = graphics.Vectors["velocity-vector"] +# velocity_vector.surfaces_list = ["outlet-plane"] +# velocity_vector.scale = 1 +# velocity_vector.display("window-6") + +# ############################################################################### +# # Commenting out due to issue #290 +# # Start the Plot Object for the session +# plots_session_1 = Plots(session) + +# ############################################################################### +# # Create a default XY-Plot +# plot_1 = plots_session_1.XYPlots["plot-1"] + +# ############################################################################### +# # Set the surface on which the plot is plotted and the Y-axis function +# plot_1.surfaces_list = ["outlet"] +# plot_1.y_axis_function = "temperature" + +# ############################################################################### +# # Plot the created XY-Plot +# plot_1.plot("window-7") diff --git a/src/ansys/fluent/core/__init__.py b/src/ansys/fluent/core/__init__.py index 55e049e5cf26..2e8cad4842c0 100644 --- a/src/ansys/fluent/core/__init__.py +++ b/src/ansys/fluent/core/__init__.py @@ -86,3 +86,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 57% rename from .ci/start_fluent.py rename to src/ansys/fluent/core/launcher/fluent_container.py index 284ad250fd97..3a1138957ca0 100644 --- a/.ci/start_fluent.py +++ b/src/ansys/fluent/core/launcher/fluent_container.py @@ -1,35 +1,53 @@ 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: - fd, sifile = tempfile.mkstemp( - suffix=".txt", prefix="serverinfo-", dir=EXAMPLES_PATH - ) + +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`` will be mounted as ``mount_to`` + within the container. + mounted_to : str + Path to mount to. ``mounted_from`` will be 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=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 +70,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 e211830e562f..1358838963b4 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 @@ -63,6 +64,55 @@ 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, @@ -144,45 +194,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}" @@ -218,6 +230,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)